iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 16
0

昨天我們已經讓蛇的頭部可以動起來,並且可以接受方向鍵的操作來改變方向。接下來我們要讓這隻蛇有身體,讓他成為一條名符其實的蛇。

前情提要

Day12 - 貪吃蛇篇:蛇的原理及資料結構規劃 我們有提到,蛇的身體是一個佇列(Queue), 我們要用陣列搭配 push() 和 shift() 這兩個方法,來實現具有先進先出(FIFO, First-In-First-Out, FIFO)行為模式的陣列。push() 方法會將一或多個值加入至一個陣列中。shift() 方法會移除並回傳陣列的第一個元素。

陣列· 從ES6開始的JavaScript學習生活 - GitBook
js開發:數組的push()、pop()、shift()和unshift()(轉)

snake-moving

先來複習一下我們設計的 snake 物件

const snake = {
   headPosition: {
       x: 0,
       y: 0,
   },
   body: [],
   maxLength: 2,
   direction: {
       x: 1,
       y: 0,
   },
   speed: SNAKE_INITIAL_SPEED,
};

畫出蛇的身體

處理蛇的身體的資料

在 snake 物件當中, body 這個陣列是用來儲存蛇的身體每一個元素的位置的物件,內容大概會是這樣:

body = [ {0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0} ];

透過 body 陣列當中地每個元素,我們可以把蛇畫在畫面上指定的位置,所以假設蛇的頭在 headPosition = {5, 0},當蛇往右移動,也就是 direction = {1 , 0} 的時候

headPosition = {5, 0};
direction = {1, 0};
body = [ {0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0} ];
maxLength = 5;

原本蛇的頭所在的位置節點 {5, 0} 需要被 push() 進去 body 裡面,但是這時候蛇的身體長度 body.length 已經超過了蛇的最大長度 maxLength ,所以最尾巴的節點 {0, 0} 需要從 body 裡面被移除,這邊用 shift() 方法來移除,而頭的位置會依照移動方向更新到 {6, 0},如下:

headPosition = {6, 0};
direction = {1, 0};
body = [ {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0} ];
maxLength = 5;

以上就是蛇移動一步的演示,也就是我們希望透過程式達到的效果。

containers/SnakeGame/reducer.js

function snakeGameReducer(state = initialState, action) {
    switch (action.type) {
        case SET_SNAKE_MOVING: {
            const direction = state.getIn(['snake', 'direction']);
            const headPositionX = state.getIn(['snake', 'headPosition', 'x']);
            const headPositionY = state.getIn(['snake', 'headPosition', 'y']);
            const maxLength = state.getIn(['snake', 'maxLength']);
            return state
                // update snake head position
                .updateIn(['snake', 'headPosition'], (headPosition) =>
                    headPosition
                        .set('x', updatePosition(headPosition.get('x') + direction.get('x'))) // 新的頭的位置是舊的位置加上方向向量
                        .set('y', updatePosition(headPosition.get('y') + direction.get('y')))
                )
                // update snake body
                .updateIn(['snake', 'body'], (body) => {
                    let updatedBody = body.push(fromJS({   // 把頭加入到 body 陣列來更新
                        x: headPositionX,
                        y: headPositionY,
                    }));
                    if (updatedBody.size > maxLength) {    // 若身體長度大於最大長度
                        updatedBody = updatedBody.shift(); // 從尾巴拿掉一個元素
                    }
                    return fromJS(updatedBody);
                });
        }
        // ...
    }
}

snakeGameReducer

地圖上畫出蛇的身體

更新完資料之後,記得在畫面把身體畫出來,這邊複習一下昨天 Day15 畫頭的方式,就是在地圖上頭的位置的那個方格,改變他的樣式,塗上白色。身體的部分也是一模一樣,我們在地圖上找到身體的座標,改變他的樣式,塗上白色,如下:

const updateGameView = (snake, block) => {
   //draw snake head
   if (snake.getIn(['headPosition', 'x']) === block.get('x') &&
       snake.getIn(['headPosition', 'y']) === block.get('y')) {
       return 'snake-game__map-block-item snake-game__draw-snake-body';
   }
   const snakeBody = snake.get('body');
   if (snakeBody.size > 1) { // body 裡面有東西才需要畫身體
       const found = snakeBody.find((bodyPos) => {
           return bodyPos.get('x') === block.get('x') &&
               bodyPos.get('y') === block.get('y');
       });
       // draw snake body
       if (found) { // 若方格的位置等於身體的位置,就把身體畫出來,塗成白色
           return 'snake-game__map-block-item snake-game__draw-snake-body';
       }
   }
   return 'snake-game__map-block-item';
};
<div className="snake-game__map-wrapper">
    {
        blocks.map((rows) => (
            rows.map((block) => (
                <div
                    key={block.get('id')}
                    className={updateGameView(snake, block)}
                >
                </div>
            ))
        ))
    }
</div>

今日成果

完成之後,我們就有一條像樣的貪吃蛇啦!
snakeGame__draw-snake-body

參考程式碼

Snake - Github


上一篇
Day15 - 貪吃蛇篇:讓蛇的頭動起來
下一篇
Day17 - 貪吃蛇篇:產生食物(上菜啦~~)
系列文
以經典小遊戲為主題之ReactJS應用練習30

尚未有邦友留言

立即登入留言