昨天我們已經讓蛇的頭部可以動起來,並且可以接受方向鍵的操作來改變方向。接下來我們要讓這隻蛇有身體,讓他成為一條名符其實的蛇。
在 Day12 - 貪吃蛇篇:蛇的原理及資料結構規劃 我們有提到,蛇的身體是一個佇列(Queue), 我們要用陣列搭配 push() 和 shift() 這兩個方法,來實現具有先進先出(FIFO, First-In-First-Out, FIFO)行為模式的陣列。push() 方法會將一或多個值加入至一個陣列中。shift() 方法會移除並回傳陣列的第一個元素。
陣列· 從ES6開始的JavaScript學習生活 - GitBook
js開發:數組的push()、pop()、shift()和unshift()(轉)
先來複習一下我們設計的 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);
});
}
// ...
}
}
更新完資料之後,記得在畫面把身體畫出來,這邊複習一下昨天 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>
完成之後,我們就有一條像樣的貪吃蛇啦!