JavaScript小游戏实例:统一着色
设计如下的简单小游戏。
在面板(画布)中放置10行10列共100个小方块,每个小方块随机在5种颜色中选一种颜色进行着色,在面板的下方,放置对应的5种颜色色块,如图1所示。
图1 “统一着色”游戏界面
游戏要求通过每次对包含最左上角方块的相邻同色区域方块改变着色的方式,最终使得面板中的100个小方块着色统一。
例如,图1中包含最左上角方块的相邻同色区域方块只有1个,此时对其有效改变着色应该是选择“绿色”(下方相邻的方块颜色)或“棕色”(左边相邻的方块颜色),这样扩大了包含最左上角方块的相邻同色区域;否则,不扩大包含最左上角方块的相邻同色区域,应是无效的着色选择。
单击面板下方5个色块中的某个色块,就可以将包含最左上角方块的相邻同色区域的所有方块颜色改为所单击色块的颜色。一次游戏过程如图2所示。
图2 “统一着色”游戏过程示例
定义一个数组
var colors = ["Aquamarine","Blue","BlueViolet","Brown","Chartreuse","Chocolate","Cyan",
"DeepPink","Fuchsia","Green","Orange","Purple","Red","SkyBlue","Yellow"];
保存游戏可供选择的颜色。
每局游戏选择5个色块,定义数组 var options=[];保存在colors数组中选择的5种颜色所对应的下标。为了达到随机选择5种颜色,采用对颜色数组进行简单洗牌的方法来完成。
定义数组var blocks=[];保存面板中100个小方块的着色,第i行第j列小方块的着色保存在数组元素blocks[10*i+j]中。初始时,每个小方块的着色在选定的5种颜色中随机选择一种,一个简单的循环可完成。
for (var i=0;i<100;i++)
blocks[i]=options[Math.floor(Math.random()*5)];
单击面板下方5个小色块中某个色块后,调用函数setColor()完成改变着色的过程。
改变着色的区域是包含最左上角方块的相邻同色区域的所有方块,因此关键问题是怎么找到这个相邻同色区域中的所有方块。可采用广度优先搜索算法的思想来完成。算法描述为:
记下最左上角方块的初始着色srcColor;
队列初始化;
最左上角方块编号 0入队;
while (队列非空)
{
队头元素出队,赋给k;
改变方块k的着色(修改blocks[k]的值为所单击色块的颜色编号destColor);
if (方块k右边的方块k+1存在且颜色为srcColor)
方块编号k+1入队;
if (方块k下边的方块k+10存在且颜色为srcColor)
方块编号k+10入队;
}
实际上,一个方块相邻的方块有上下左右四个,但算法中只需考虑右边和下边,无需考虑左边和上边。这是因为本游戏的着色改变总是改变包含最左上角方块的相邻区域,即搜索总是从最左上角方块开始,而最左上角方块只能向右边和下边扩展,之后每个方块也只需向右边和下边扩展即可,无需向左边和上边扩展(因为左边和上边的方块已被扩展处理过)。
获胜判断就更简单了,因为获胜的条件是所有方块的着色统一,即数组blocks的元素值全部相同,因此用循环检查元素blocks[1]~blocks[99],若它们的值全部与blocks[0]相同,则获胜,否则只要有一个元素与blocks[0]不同,就没有达到着色统一的获胜要求。编写为一个简单的函数。
function checkWin()
{
for (var i=1;i<blocks.length;i++)
if (blocks[i]!=blocks[0]) return false;
return true;
}
另外,为避免游戏过程中的无效色块单击,记录游戏的步数(色块单击次数),若35步内不能完成统一着色,判定游戏失败。
完整的HTML代码如下。
<!DOCTYPE html> <html> <head> <title>统一着色</title> <style> .num { padding: 28px 0; border: 1px solid ; cursor: pointer; } .new-game { cursor: pointer; text-decoration: underline; color: #00bcd4; font:bold 24px Georgia, serif; } #mess { width:480px; padding:5px; border:2px solid gray; margin:0px; font:bold 24px Georgia, serif; color: #FF6600; } </style> </head> <body> <div class="new-game" onclick="newGame();">New Game</div><br/> <canvas id="myCanvas" width="500" height="500" style="border:3px double #996633;"> </canvas><br/> <table cellpadding="0" cellspacing="20"> <tbody><tr> <td width="60"><div id="c0" class="num" onclick="setColor(this);"></div></td> <td width="60"><div id="c1" class="num" onclick="setColor(this);"></div></td> <td width="60"><div id="c2" class="num" onclick="setColor(this);"></div></td> <td width="60"><div id="c3" class="num" onclick="setColor(this);"></div></td> <td width="60"><div id="c4" class="num" onclick="setColor(this);"></div></td> </tr> </tbody> </table><br/> <div id="mess"> </div><br/> <script type="text/javascript"> var canvas=document.getElementById('myCanvas'); ctx= canvas.getContext('2d'); var colors = ["Aquamarine","Blue","BlueViolet","Brown","Chartreuse","Chocolate","Cyan","DeepPink","Fuchsia ","Green","Orange","Purple","Red","SkyBlue","Yellow"]; var running = false; var steps=0; function shuffle(arr) // 数组arr洗牌 { for (var i = arr.length-1; i>=0; i--) { var j = Math.floor(Math.random() * i); var t=arr[i]; arr[i]=arr[j]; arr[j]=t; } } var colorNum=[]; for (var i=0;i<colors.length;i++) colorNum[i]=i; var options=[]; var blocks=[]; function newGame() { shuffle(colorNum); options=colorNum.slice(0,5); for (var i=0;i<100;i++) blocks[i]=options[Math.floor(Math.random()*5)]; running=true; steps=0; draw(); document.getElementById("c0").style.backgroundColor =colors[options[0]]; document.getElementById("c1").style.backgroundColor =colors[options[1]]; document.getElementById("c2").style.backgroundColor =colors[options[2]]; document.getElementById("c3").style.backgroundColor =colors[options[3]]; document.getElementById("c4").style.backgroundColor =colors[options[4]]; document.getElementById("mess").innerHTML ="Steps:"+steps+"/35"; } function draw() { for (var i=0;i<10;i++) { for (var j=0;j<10;j++) { ctx.fillStyle = colors[blocks[i*10+j]]; ctx.fillRect(j*50,i*50,50,50); } } ctx.fillStyle ="rgba(255,255,255,0.85)"; ctx.fillRect(20,20,10,10); } function setColor(d) { if (!running) { document.getElementById("mess").innerHTML ="请单击 New Game 开始新游戏!"; return; } var index= parseInt(d.id.charAt(1)); var srcColor=blocks[0]; var destColor=options[index]; if (srcColor==destColor) { document.getElementById("mess").innerHTML ="Steps:"+steps+"/35 未进行颜色改变,无效单击!"; return; } var queue=[]; var front=0; var rear=0; queue[rear++]=0; while (front<rear) { var k=queue[front++]; blocks[k]=destColor; if (k%10!=9 && blocks[k+1]==srcColor) // 右边的方格 queue[rear++]=k+1; if (k<90 && blocks[k+10]==srcColor) // 下边的方格 queue[rear++]=k+10; } draw(); steps++; document.getElementById("mess").innerHTML ="Steps:"+steps+"/35"; if (checkWin()) { running=false; ctx.font = '50px PingFang SC'; ctx.fillStyle = "red"; ctx.textAlign = 'center'; ctx.baseline = 'middle'; ctx.fillText('You Win!',canvas.width/2, canvas.height/2); } else { if (steps==35) { running=false; ctx.font = '50px PingFang SC'; ctx.fillStyle = "black"; ctx.textAlign = 'center'; ctx.baseline = 'middle'; ctx.fillText('You failed!',canvas.width/2, canvas.height/2); } } } function checkWin() { for (var i=1;i<blocks.length;i++) if (blocks[i]!=blocks[0]) return false; return true; } newGame(); </script> </body> </html>