constgetArg=(args,name)=>{constmatch=args.match(newRegExp('--'+name+'=(\\d+)'))if (match===null){thrownewError('Missing argument '+name)}returnparseInt(match[1])}letwidth=0letheight=0letmines=0try{constargs=process.argv.slice(2).join('')width=getArg(args,'width')height=getArg(args,'height')mines=getArg(args,'mines')if (width<1||height<1){thrownewError('Field size must be positive')}}catch (e){console.error(e)process.exit(1)}
while (mines>0){constmineX=Math.round(Math.random()*(width-1))constmineY=Math.round(Math.random()*(height-1))if (field[mineY][mineX]!=='m'){field[mineY][mineX]='m'getNeighbouringCoords(mineX,mineY).filter(([y,x])=>field[y][x]!=='m').forEach(([y,x])=>{field[y][x]++})mines--}}
constcharacterMap={m:'💣',// I kinda developed an aversion to that emoji.0:'⬜',1:'1️⃣ ',2:'2️⃣ ',3:'3️⃣ ',4:'4️⃣ ',5:'5️⃣ ',6:'6️⃣ ',7:'7️⃣ ',8:'8️⃣ ',}
接下来,我们定义一个用于渲染场地的函数。它应该首先清除 CLI 输出,并预先渲染顶部和底部墙壁:
constrenderField=(playerX,playerY)=>{console.clear()console.log('🧱'.repeat(width+2))// ...console.log('🧱'.repeat(width+2))console.log('Press ENTER to uncover a field, SPACE to place a flag')}
constuncoverCoords=(x,y)=>{// Uncover the field by defaultuncoveredField[y][x]=trueconstneighbours=getNeighbouringCoords(x,y)// Only if the field is a 0, so if it has no adjacent mines,// ask its neighbours to uncover.if (field[y][x]===0){neighbours.forEach(([y,x])=>{// Only uncover fields that have not yet been uncovered.// Otherwise we would end up with an infinite loop.if (uncoveredField[y][x]!==true){// Recursive call.uncoverCoords(x,y)}})}}
constreadlineModule=require('readline')readlineModule.emitKeypressEvents(process.stdin)process.stdin.setRawMode(true)process.stdin.on('keypress',(character,key)=>{// Do stuff})
然而,由于标准输入处于原始模式,使用 Ctrl+C 终止当前脚本不起作用。按住 Ctrl 键再按 C 键也被视为一次按键操作。因此,我们需要自己实现这个功能:
// ...process.stdin.on('keypress',(character,key)=>{// More stuffif (key.name==='c'&&key.ctrl){process.exit(0)}})
process.stdin.on('keypress',(character,key)=>{if (!hasLost&&!hasWon){// Do not move past right wallif (key.name==='right'&&playerX<width-1){playerX++}// Do not move past left wallif (key.name==='left'&&playerX>0){playerX--}// Do not move past down wallif (key.name==='down'&&playerY<height-1){playerY++}// Do not move past up wallif (key.name==='up'&&playerY>0){playerY--}// Uncovering fieldsif (key.name==='return'){uncoverCoords(playerX,playerY)// The player seems to have found a mineif (field[playerY][playerX]==='m'){hasLost=true// Uncover all fields in case the player has lostuncoveredField=Array(height).fill([]).map(()=>Array(width).fill(true))}}// Placing a flagif (key.name==='space'){flaggedField[playerY][playerX]=!flaggedField[playerY][playerX]hasWon=checkIfWon()}}// Show the player what just happened on the fieldrenderField(playerX,playerY)if (hasLost){console.log('Lost :(')}if (hasWon){console.log('Won :)')}if (key.name==='c'&&key.ctrl){process.exit(0)}})