欢迎在Solidjs Post系列中创建扫雷游戏的第三部分。在first part中,我们使用平坦的阵列和数学操作构建了游戏板。在second part中,我们集中于实施“零开放”功能所需的递归迭代过程。
在这篇文章中,我们旨在将游戏化元素纳入游戏中,以使其对用户更具吸引力和愉悦感。
我们将通过添加矿山计数器来跟踪那些讨厌的炸弹,并帮助您维持标记的炸弹来调整内容。另外,我们将介绍游戏结束的逻辑和方便的新计时器组件,该组件将跟踪您玩的时间,直到炸弹开始引爆或已标记所有矿山。
我们有很多事情要做,所以让我们继续!
嘿!有关您将要阅读的内容的更多内容,请在Twitter上查看@mattibarzeev ð»
代码可以在此GitHub存储库中找到:
https://github.com/mbarzeev/solid-minesweeper
地雷柜台
Minesweeper Game有一个游戏面板,其中包括地雷计数器。
计数器从板上的最初矿山的初始数量开始,每次玩家都标记瓷砖时,即使商标实际上并不在其中有一个地雷的瓷砖上,矿山的数量也会相应减少。
我们首先在标题中渲染游戏面板:
<header class={styles.header}>
<div class={styles.gamePanel}></div>
<div class={styles.board}>
<For each={tilesArray()}>
{(tileData: TileData) => (
<Tile data={tileData} onTileContextMenu={toggleTileMark} onTileClicked={onTileClicked} />
)}
</For>
</div>
</header>
为了显示剩余的矿山数量,我们需要创建一个固体信号,以使其具有反应性:
const TOTAL_MINES = 50;
const [remainingMines, setRemainingMines] = createSignal<number>(TOTAL_MINES);
,渲染将是:
<div class={styles.gamePanel}>
<span>{remainingMines()}</span>
</div>
现在,要正确设置剩余地雷的总数,我们需要在每次播放器切换标记时进行更新,我想这样做,以此作为修改瓷砖数组时的副作用。
为了实现这一目标,我们需要聆听tilesArray
的更改并检查标记的瓷砖以计算其余地雷。它可能似乎没有优化,但它会以一种确定的方式使用游戏状态来工作 - 我的意思是,如果我们给游戏一个状态,它将正确渲染。
为此,我们将使用solid s createefect():
createEffect(() => {
const markedTiles = tilesArray().filter((tile) => tile.isMarked);
setRemainingMines(TOTAL_MINES - markedTiles.length);
});
这种效果每次更改瓷砖信号时都会触发这种效果,滤除标记的图块,然后从total_mines中扣除以获取剩余的矿山号。
引爆一个地雷
当玩家不小心打开一个拥有我的瓷砖时,游戏结束了。
这是瓷砖单击处理程序的代码:
const onTileClicked = (index: number) => {
const tile: TileData = tilesArray()[index];
// If the tile is marked, un-mark it
tile.isMarked = false;
// If the tile has a mine, it's game over
if (tile.value === MINE_VALUE) {
gameOver();
} else {
let indices = [index];
const tileValue = tile.value;
if (tileValue === 0) {
// get the indices that need to be opened...
indices = getTotalZeroTilesIndices(index);
}
openTiles(indices);
}
};
您可以看到,当我们检测到该值是mine_value时,我们称为gameOver()
函数。
我们要在游戏功能中要做的是引爆点击矿,然后在板上的其余地雷引爆。
我们首先将另一个属性添加到Tiledata,即isDetonated:boolean;
:
export type TileData = {
index: number;
value: TileValue;
isOpen: boolean;
isMarked: boolean;
isDetonated: boolean;
};
这将有助于我们指出瓷砖被引爆。我们显然将其启动到错误:
// Convert the boardArray to TilesArray
const [tilesArray, setTilesArray] = createSignal<TileData[]>(
boardArray.map((item, index) => ({
index,
value: getTileValue(index),
isOpen: false,
isMarked: false,
isDetonated: false,
}))
);
我们还添加了CSS类,以防瓷砖在瓷砖组件中引爆:
classList={{[styles.exposed]: data.isOpen || data.isMarked, [styles.detonated]: data.isDetonated}}
.value.detonated {
display: block;
background-color: red;
}
现在,当玩家引爆矿山时,我们将其中的所有瓷砖标记为引爆的,我们将它们全部打开,因此,当游戏超过整个游戏板上时,我们都会公开:
const gameOver = () => {
setTilesArray((prevArray) => {
const newArray = prevArray.map((tile) => {
if (tile.value === MINE_VALUE) {
return {...tile, isDetonated: true, isOpen: true};
}
return {...tile, isOpen: true};
});
return newArray;
});
};
这是到目前为止的结果:
找到所有地雷
是时候处理玩家找到所有矿山的情况了。换句话说,这意味着每个带有矿山的瓷砖都标有一个标志,并且标记的瓷砖数量等于木板中的矿山总数。
在createEffect
中,我们以前做出了我们添加以下代码,以检查游戏是否赢了:
createEffect(() => {
if (isGameOver) return;
const markedTiles = tilesArray().filter((tile) => tile.isMarked);
setRemainingMines(TOTAL_MINES - markedTiles.length);
// If the marked tiles are actually mines and they equal to the total number
// of mines in the board, the game is won
if (markedTiles.length === TOTAL_MINES) {
try {
markedTiles.forEach((tile) => {
if (tile.value !== MINE_VALUE) {
throw new Error();
}
});
gameWon();
} catch (error) {
// Do nothing, the game is not won
}
}
});
简而言之,当标记瓷砖的数量等于总矿山数量时,我们检查它们是否都是矿山。如果不是,我们打破了循环。如果是,我们称为gamewon()方法。
请注意,我们在做任何事情之前先检查Isgameover。这是为了防止当我们想在游戏结束时对图块数组做些事情时,例如在gameWon
函数中,我们打开整个瓷砖。
这是gameWon
函数的代码:
const gameWon = () => {
isGameOver = true;
setTilesArray((prevArray) => {
const newArray = prevArray.map((tile) => {
return {...tile, isOpen: true};
});
return newArray;
});
};
计时器
是时候添加计时器了。
目前,它将是愚蠢的 - 它将从页面的刷新开始,并在游戏结束时停止。
我们从信号和计时器间隔开始:
let timerInterval: number;
const [timerSeconds, setTimerSeconds] = createSignal(0);
const startTimer = () => {
timerInterval = setInterval(() => {
setTimerSeconds(timerSeconds() + 1);
}, 1000);
};
,我们在主应用程序文件上调用startTimer()
。
对于计时器,我们构建了一个专用组件,该组件以不错的格式显示时间。请注意,传递给组件的秒数不是一个数字,而是固体访问者类型:
import {Accessor} from 'solid-js';
const Timer = ({seconds}: {seconds: Accessor<number>}) => {
return <div>{getDisplayTimeBySeconds(seconds())}</div>;
};
const getDisplayTimeBySeconds = (seconds: number) => {
const min = Math.floor(seconds / 60);
const sec = seconds % 60;
return `${getDisplayableTime(min)}:${getDisplayableTime(sec)}`;
};
function getDisplayableTime(timeValue: number): string {
return timeValue < 10 ? `0${timeValue}` : `${timeValue}`;
}
export default Timer;
我们在主应用程序中使用这样的组件:
<div class={styles.scoring}>
<span>{remainingMines()}</span>
<Timer seconds={timerSeconds} />
</div>
以及当游戏结束时,我们清除了间隔。拥有游戏状态并具有效果可能会更好,但是我们将其保存到重构阶段。
好吧,我认为我们做到了并实现了这篇文章的目标!
那么我们到底有什么?
我们有一个矿床,我们有一个计时器,我们有办法表明比赛何时结束或赢得胜利,甚至为矿山提供了一个不错的炸弹表情符号(对不起,无法找到我的矿山)。还不错ð
请继续关注下一篇文章,我们将放置一些模式并尝试完成游戏!
代码可以在此GitHub存储库中找到:
https://github.com/mbarzeev/solid-minesweeper
嘿!有关更多内容,例如您刚刚阅读的内容,请在Twitter上查看@mattibarzeev ð»