五子棋游戏源码和核心算法的讲解(简易五子棋web版)
最下面有所有的源码
感想:写扫雷游戏的主要原因是因为这段时间刚好迷上了扫雷,便有了写出这个游戏的想法。
写代码的过程中,我觉得比较重要的算法有两部分:1、初始化游戏时,若该格子不是雷,那么格子中的数字怎么计算得来
2、游戏时,点到空白的格子,怎么将空白格子所在的空白区域翻开(空白格子周围又有空白格子)
一、样式
二、
三、一些变量的定义以及元素的获得
//画布
var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
//翻牌按钮
var fanpaiBtn=document.getElementById("fanpai");
//插旗按钮
var chaqiBtn=document.getElementById("chaqi");
//雷的数目提醒段
var Pcount=document.getElementById("p");
//重新开始按钮
var newGameBtn=document.getElementById("newGame");
//模式选择
var model=document.getElementById("model");
//背景颜色
var colorCanvas=document.getElementById("colorCanvas");
//格子的颜色
var geZiColor="#11b1ff";
var flag=true;//true-翻牌按钮(默认) false-插旗按钮
var Lcount=30;//雷的数目
var Fcount=0;//旗子的数目
var Tu=[];//Tu[i][j][0]存储数字和炸弹(-1)Tu[i][j][1]存储是否可以翻牌(0->还没翻,1已经翻了)Tu[i][j][2]-是否有插旗,0是没插旗1插旗
for(var i=0;i<30;i++){
Tu[i]=[];
for(var j=0;j<15;j++){
Tu[i][j]=[];
for(var k=0;k<3;k++){
Tu[i][j][k]=0;
}
}
}
四、图的初始化操作(包含初始化雷的算法)
//初始化图
function initTu(){
flag=true;//默认翻牌按钮
if(flag){
fanpaiBtn.style.borderColor="#cc0000";
chaqiBtn.style.borderColor="#000000";
}
p.innerHTML="剩余雷的数目:"+Lcount;
for(var i=0;i<30;i++){
for(var j=0;j<15;j++){
for(var k=0;k<3;k++){
Tu[i][j][k]=0;
}
}
}
fanpaiBtn.style.borderColor="#cc0000";
//画布加颜色
context.fillStyle=geZiColor;
for(var i=0;i<30;i++){
for(var j=0;j<15;j++){
context.fillRect(j*30+2,i*30+1,27,27);
}
}
//画线
context.strokeStyle="#000000";
context.beginPath();
for(var i=0;i<15;i++){
//竖线
context.moveTo(30+i*30,0);
context.lineTo(30+i*30,900)
context.stroke();
//横线
context.moveTo(0,30+i*30);
context.lineTo(450,30+i*30)
context.stroke();
context.moveTo(0,30*15+i*30);
context.lineTo(450,30*15+i*30)
context.stroke();
}
context.closePath();
//生成Lcount个雷
for(var i=0;i=0&&x-1>=0){
//不是雷 是数字就+1
if(Tu[y-1][x-1][0]!=-1)Tu[y-1][x-1][0]++;
}
if(y-1>=0){
if(Tu[y-1][x][0]!=-1)Tu[y-1][x][0]++;
}
if(y-1>=0&&x+1<15){
if(Tu[y-1][x+1][0]!=-1)Tu[y-1][x+1][0]++;
}
if(x+1<15){
if(Tu[y][x+1][0]!=-1)Tu[y][x+1][0]++;
}
if(y+1<30&&x+1<15){
if(Tu[y+1][x+1][0]!=-1)Tu[y+1][x+1][0]++;
}
if(y+1<30){
if(Tu[y+1][x][0]!=-1)Tu[y+1][x][0]++;
}
if(y+1<30&&x-1>=0){
if(Tu[y+1][x-1][0]!=-1)Tu[y+1][x-1][0]++;
}
if(x-1>=0){
if(Tu[y][x-1][0]!=-1)Tu[y][x-1][0]++;
}
}else{
i--;//不是雷时,这次循环没有得到雷,无效,i--
}
}
}
初始化时画布加颜色可以一次性画一整个画布,不使用循环,我为了使得格子之间有间隙(看起来比较好看)就使用了循环。
生成雷的算法思想:
格子上的数字是周围八个位置雷的个数,也就是说雷的存在影响了数字的大小,反过来想,不以数字为中心点,以雷为中心点,则周围的八个位置上会因为中心点的雷的影响,而数字加1。所以该算法主要是在生成的雷时候,也使雷的周围的格子(非雷)的数字加1。
五、点到空白格子时的处理代码
//点击到空白时的处理函数
function blank(i,j){
//翻开当前的空白格子
context.fillStyle="#ffffff";//面白色
context.fillRect(30*j+1,30*i+1,28,28);
Tu[i][j][1]=1;//标记被翻了
Iswin();
if(i-1>=0){
if(Tu[i-1][j][1]==0){//没有被翻过
if(Tu[i-1][j][0]==0){//空白
blank(i-1,j);
}else{//没有被翻过但不是空白->翻开(因为空白的四周没有炸弹所以不需要考虑是否是炸弹而直接翻开)
context.fillStyle="#ffffff";//面白色
context.fillRect(30*j+1,30*(i-1)+1,28,28);
context.fillStyle="#000000";
context.font="20px Arial";
context.fillText(Tu[i-1][j][0],8+30*j,23+30*(i-1));
Tu[i-1][j][1]=1;//标记已经被翻开
Iswin();
}
}
}
if(i+1<30){
if(Tu[i+1][j][1]==0){//没有被翻过
if(Tu[i+1][j][0]==0){//空白
blank(i+1,j);
}else{//没有被翻过但是不是空白->翻开(因为空白的四周没有炸弹所以不需要考虑是否是炸弹而直接翻开)
context.fillStyle="#ffffff";//面白色
context.fillRect(30*j+1,30*(i+1)+1,28,28);
context.fillStyle="#000000";
context.font="20px Arial";
context.fillText(Tu[i+1][j][0],8+30*j,23+30*(i+1));
Tu[i+1][j][1]=1;//标记已经被翻开
Iswin();
}
}
}
if(j-1>=0){
if(Tu[i][j-1][1]==0){//没有被翻过
if(Tu[i][j-1][0]==0){//空白
blank(i,j-1);
}else{//没有被翻过但是不是空白(因为空白的四周没有炸弹所以不需要考虑是否是炸弹而直接翻开)
context.fillStyle="#ffffff";//面白色
context.fillRect(30*(j-1)+1,30*i+1,28,28);
context.fillStyle="#000000";
context.font="20px Arial";
context.fillText(Tu[i][j-1][0],8+30*(j-1),23+30*i);
Tu[i][j-1][1]=1;//标记已经被翻开
Iswin();
}
}
}
if(j+1<15){
if(Tu[i][j+1][1]==0){//没有被翻过
if(Tu[i][j+1][0]==0){//空白
blank(i,j+1);
}else{//没有被翻过但是不是空白(因为空白的四周没有炸弹所以不需要考虑是否是炸弹而直接翻开)
context.fillStyle="#ffffff";//面白色
context.fillRect(30*(j+1)+1,30*i+1,28,28);
context.fillStyle="#000000";
context.font="20px Arial";
context.fillText(Tu[i][j+1][0],8+30*(j+1),23+30*i);
Tu[i][j+1][1]=1;//标记已经被翻开
Iswin();
}
}
}
if(j+1<15&&i+1<30){
if(Tu[i+1][j+1][1]==0){//没有被翻过
if(Tu[i+1][j+1][0]==0){//空白
blank(i+1,j+1);
}else{//没有被翻过但是不是空白(因为空白的四周没有炸弹所以不需要考虑是否是炸弹而直接翻开)
context.fillStyle="#ffffff";//面白色
context.fillRect(30*(j+1)+1,30*(i+1)+1,28,28);
context.fillStyle="#000000";
context.font="20px Arial";
context.fillText(Tu[i+1][j+1][0],8+30*(j+1),23+30*(i+1));
Tu[i+1][j+1][1]=1;//标记已经被翻开
Iswin();
}
}
}
if(j+1<15&&i-1>=0){
if(Tu[i-1][j+1][1]==0){//没有被翻过
if(Tu[i-1][j+1][0]==0){//空白
blank(i-1,j+1);
}else{//没有被翻过但是不是空白(因为空白的四周没有炸弹所以不需要考虑是否是炸弹而直接翻开)
context.fillStyle="#ffffff";//面白色
context.fillRect(30*(j+1)+1,30*(i-1)+1,28,28);
context.fillStyle="#000000";
context.font="20px Arial";
context.fillText(Tu[i-1][j+1][0],8+30*(j+1),23+30*(i-1));
Tu[i-1][j+1][1]=1;//标记已经被翻开
Iswin();
}
}
}
if(j-1>=0&&i-1>=0){
if(Tu[i-1][j-1][1]==0){//没有被翻过
if(Tu[i-1][j-1][0]==0){//空白
blank(i-1,j-1);
}else{//没有被翻过但是不是空白(因为空白的四周没有炸弹所以不需要考虑是否是炸弹而直接翻开)
context.fillStyle="#ffffff";//面白色
context.fillRect(30*(j-1)+1,30*(i-1)+1,28,28);
context.fillStyle="#000000";
context.font="20px Arial";
context.fillText(Tu[i-1][j-1][0],8+30*(j-1),23+30*(i-1));
Tu[i-1][j-1][1]=1;//标记已经被翻开
Iswin();
}
}
}
if(j-1>=0&&i+1<30){
if(Tu[i+1][j-1][1]==0){//没有被翻过
if(Tu[i+1][j-1][0]==0){//空白
blank(i+1,j-1);
}else{//没有被翻过但是不是空白(因为空白的四周没有炸弹所以不需要考虑是否是炸弹而直接翻开)
context.fillStyle="#ffffff";//面白色
context.fillRect(30*(j-1)+1,30*(i+1)+1,28,28);
context.fillStyle="#000000";
context.font="20px Arial";
context.fillText(Tu[i+1][j-1][0],8+30*(j-1),23+30*(i+1));
Tu[i+1][j-1][1]=1;//标记已经被翻开
Iswin();
}
}
}
}
该算法的主要思想:
点到空白格子时,将其周围的8个格子都翻开(空白格子的周围八个位置都没有雷,所以不用担心翻到雷),若8个格子中又有空白格子,就以新出现的格子为中心,翻开其周围的8个格子(已经翻开了的就不再翻开)【递归思想】。
以上代码中8个if就是格子周围的八个方向。内层if判断该方向上的格子是不是空白,是就以它为中心继续翻开(递归)。当8个方向都不是空白时结束递归。
六、其他代码
//画方块(翻牌操作)
function drawS(i,j){//j-x(横) i-y(纵)
if(Tu[i][j][1]==0&&Tu[i][j][2]==0){//没翻过且没插旗,才可以翻牌
if(Tu[i][j][0]==0){//空白
blank(i,j);
}else if(Tu[i][j][0]==-1){//炸弹
context.fillStyle="#000000";
context.fillRect(j*30+2,i*30+1,27,27);
Tu[i][j][1]=1;//标记已经翻过牌子了
alert("点到炸弹,输了");
gameOver();
}else{//数字
//context.fillText("0",8,23);//第0个格子
//context.fillRect(0,0,30,30);第一个格子
context.fillStyle="#ffffff";//面白色
context.fillRect(j*30+2,i*30+1,27,27);
context.fillStyle="#000000";
context.font="20px Arial";
context.fillText(Tu[i][j][0],8+30*j,23+30*i);
Tu[i][j][1]=1;//标记已经翻过牌子了
Iswin();
}
}
}
//画旗子
function drawQizi(i,j){
//插旗,并将旗子放上去
//旗面
context.beginPath();
context.moveTo(13+30*j,i*30+3);
context.lineTo(26+30*j,12+30*i);
context.lineTo(13+30*j,12+30*i);
context.fillStyle="#ff0000";
context.closePath();
context.fill();
//旗杆
context.beginPath();
context.lineWidth=3;
context.moveTo(13+30*j,i*30+3);
context.lineTo(13+30*j,i*30+18);
context.closePath();
context.stroke();
//旗台
context.beginPath();
context.fillStyle="#ff0000";
context.fillRect(5+30*j,18+30*i,20,8);
context.closePath();
}
//插旗
function chaQiAction(i,j){
//翻过的进行插旗动作-无效(没反应)
if(Tu[i][j][1]==0){//没翻过
if(Tu[i][j][2]==0){//没插着旗
drawQizi(i,j);
Tu[i][j][2]=1;//表示插旗了
Fcount++;
}else{//插着旗
//取消插旗,并将旗子去掉
context.fillStyle=geZiColor;
context.fillRect(j*30+2,i*30+1,27,27);
Tu[i][j][2]=0;
Fcount--;
}
p.innerHTML="剩余雷的数目:"+(Lcount-Fcount);
}
}
//图的点击事件
canvas.onclick=function(e){
var x=e.offsetX;//横
var y=e.offsetY;//纵
var i=Math.floor(y/30);//纵
var j=Math.floor(x/30);//横
if(flag){//翻牌操作
drawS(i,j);
}else{//插旗动作
chaQiAction(i,j);
}
}
//按钮点击事件\
//插旗
chaqiBtn.onclick=function(){
flag=false;
this.style.borderColor="#cc0000";
fanpaiBtn.style.borderColor="#000000";
}
//翻牌
fanpaiBtn.onclick=function(){
flag=true;
this.style.borderColor="#cc0000";
chaqiBtn.style.borderColor="#000000";
}
//赢了的判断函数
function Iswin(){
//赢的条件是翻了30*15-Lcount次游戏还没输那就赢了
var fanle=0;
for(var i=0;i<30;i++){
for(var j=0;j<15;j++){
fanle+=Tu[i][j][1];
}
}
if(fanle==(30*15-Lcount)){
alert("赢了!!!扫雷达人");
gameOver();
}
}
//游戏结束
function gameOver(){
for(var i=0;i<30;i++){
for(var j=0;j<15;j++){
if(Tu[i][j][0]==-1){//所有的雷显示出来
context.fillStyle="#000000";
context.fillRect(30*j,30*i,30,30);
Tu[i][j][1]=1;//标记已经翻过牌子了
}
}
}
}
//重新开始按钮
newGameBtn.onclick=function(){
canvas.height=canvas.height;
initTu();
}
//模式选择
model.onchange=function(){
if(this.value=="0"){Lcount=30;}
if(this.value=="1"){Lcount=50;}
if(this.value=="2"){Lcount=90;}
if(this.value=="3"){Lcount=100;}
canvas.height=canvas.height;
initTu();
}
//格子颜色的改变
colorCanvas.onchange=function(){
geZiColor=this.value;
canvas.height=canvas.height;
initTu();
}
//加载
window.onload=function(){
initTu();
}
所有的源码【在上面的基础添加了一些功能】
扫雷
有发现什么bug的话,可以在下面留言,或者上面的算法有错误,或者有更好的算法,想法都可以在下面留言。
链接:https://pan.baidu.com/s/1VpF4lT9lk-i8kCjVe2BHvg
提取码:qc1b
复制这段内容后打开百度网盘手机App,操作更方便哦