Board 雷区组件
此组件根据输入的参数动态生成游戏面板(雷区) table,包含列和方块组件,Square 代表每个可操作的单元格,给其配置了位置坐标、状态、操纵函数等属性。
const Board = props => { // 细胞(最小单位) const Square = props => { return ( <td onClick={() => { props.updateSquareValue(props) }} className={`td-${props.colCoord}-${props.rowCoord} square`} > {props.show ? props.value : ''} </td> ) } // 列 const trList = square.map((item, index) => { return ( <tr key={index}> {item.map((subItem, tdIndex) => { return ( <Square updateSquareValue={updateSquareValue} colCoord={index} rowCoord={tdIndex} value={square[index][tdIndex].value} key={`${index}-${tdIndex}`} show={false} hasClearance={false} /> ) })} </tr> ) }) return ( <table className="board-container" cellSpacing="0"> <thead></thead> <tbody>{trList}</tbody> </table> ) }
随机生成地雷数据,并初始化
creatBoardParams 函数根据输入的地雷数量,为每一个地雷随机分配坐标,返回包含所有坐标数据的数组。
initSquareValue 根据输入的宽和高,给每一个方块设置默认值0,然后第三个参数 mineArray 就是 creatBoardParams 函数所生成的随机地雷坐标,将相关方块值更新。
const creatBoardParams = () => { const mineArray = [] for (let i = 0; i < mineNum; i++) { mineArray.push({ col: Number(Number(Math.random() * (width - 1 || 0)).toFixed(0)), row: Number(Number(Math.random() * (height - 1 || 0)).toFixed(0)), }) } setMineCoord(mineArray) setTimeout(() => { setSquare(initSquareValue(width, height, mineArray)) }) } const initSquareValue = (width, height, mineArray) => { let square = [] for (let i = 0; i < height; i++) { let a = [] for (let j = 0; j < width; j++) { a.push({ value: 0 }) } square.push(a) } mineArray.forEach(i => { square[i['col']][i['row']].value = 10 }) return square }
获取并计算邻居方块的地雷数量(递归)
因为要判断每一个方块的8个邻居状态,这里用递归来处理,为防止递归函数无休止的进行,必须在函数内有终止条件,这就要求你必须要考虑好边界条件,不然将引起内存溢出直到程序崩溃。
定义 num 变量用来保存其周围邻居地雷数量,递归每个邻居,如果邻居为空,则以邻居为原点继续递归
根据每一个 Square 的最终 value 来判断其状态,并更新其显示状态。
const updateSquareValue = props => { let dom = document.querySelector(`.td-${props.colCoord}-${props.rowCoord}`) const v = dom.innerText if (v !== '') { return } if (props.value === 10) { dom.style.background = `#fff url(${STATUSMAP.get(10)})` dom.style.backgroundSize = 'cover' setTimeout(() => { // if (window.confirm('游戏结束,是否初始化服务?')) { // init() // } }, 100) return } let neighbor = [] let num = 0 for (let i = props.colCoord - 1; i <= props.colCoord + 1; i++) { for (let j = props.rowCoord - 1; j <= props.rowCoord + 1; j++) { if ( i < 0 || j < 0 || (i === props.colCoord && j === props.rowCoord) || i >= height || j >= width ) { continue } neighbor.push({ colCoord: i, rowCoord: j }) if ( mineCoord.filter(item => { return item.col === i && item.row === j }).length > 0 ) { num++ } } } // if (STATUSMAP.get(num) === '空') { console.log(num) if (num === 0) { neighbor.forEach(s => { window.requestAnimationFrame(() => { updateSquareValue(s) }) }) } dom.innerText = STATUSMAP.get(num) }
生成游戏面板、重置、游戏结束
通过定义的 creatBoardParams() 、init() 函数来创建、重置游戏,当点击到包含地雷的方块时,停止递归函数,并confirm 提示游戏结束
const init = () => { setWidth(16) // 默认面板宽度16 setHeight(16) // 默认面板高度16 setMineCoord([]) // 默认地雷坐标数据[] setMineNum(50) // 默认地雷数量 50 setSquare([]) // 默认方块数据 } const initSquareValue = (width, height, mineArray) => { let square = [] for (let i = 0; i < height; i++) { let a = [] for (let j = 0; j < width; j++) { a.push({ value: 0 }) } square.push(a) } mineArray.forEach(i => { square[i['col']][i['row']].value = 10 }) return square }
总结
扫雷游戏的主要逻辑就完成了,基于react技术栈,实现这样一个程序, 只需要两百行左右 jsx 代码就可以了,而且还有巨大优化空间。
你学会了么?