怎么用Js写一个简单的五子棋小游戏(JS,开发技术)

时间:2024-05-02 16:08:55 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

怎么用Js写一个简单的五子棋小游戏

这里的五子棋只做一些基础的功能,对于相对专业的规则不做处理。

那么该五子棋实现的规则和功能如下:

  • 整体功能采用canvas实现

  • 行列都规定 20 个数量,那么棋子的行列数量是 20 + 1

  • 棋盘数据采用稀疏数组格式

  • 棋子:0 为黑色,1 为白色

  • 可以悔棋

  • 胜负结束判断

棋盘绘制

怎么用Js写一个简单的五子棋小游戏

<template><divclass="gobang"><canvasid="my-canvas"ref="canvasRef"width="640"height="640"@click="canvasClick"></canvas></div></template><scriptlang="ts"setup>typeGobangData=(0|1|undefined)[][]/*一些常量*///canvasdom元素constcanvasRef=ref<InstanceType<typeofHTMLCanvasElement>>()//行列数constrcs=20//行列的间隔距离constgap=30//棋子的半径constradius=12//棋盘的边距constpadding=20//是否结束标记constgameOver=ref(false)//当前下棋方letcurrent=ref<0|1>(1)//canvas的2d实例letctx:CanvasRenderingContext2D//初始化棋盘数据letdata:GobangData=newArray(rcs+1).fill(0).map(()=>newArray(rcs+1))</script><stylelang="scss"scope>.gobang{width:640px;margin:0auto;}.header{margin-bottom:10px;display:flex;justify-content:space-between;.btnsbutton{margin-left:10px;padding:05px;}}#my-canvas{background-color:#e6a23c;border-radius:4px;}</style>

棋盘绘制

/***绘制棋盘*@paramctxcanvas的2d实例*@paramnumber行列数*@paramgap行列间隔距离*@parampadding棋盘边距*/constdrawChessboard=(ctx:CanvasRenderingContext2D,rcs:number,gap:number,padding:number)=>{ctx.beginPath()ctx.lineWidth=1//行for(leti=0;i<=rcs;i++){ctx.moveTo(padding+gap*i,padding)ctx.lineTo(padding+gap*i,padding+gap*rcs)}//列for(leti=0;i<=rcs;i++){ctx.moveTo(padding,padding+gap*i)ctx.lineTo(padding+gap*rcs,padding+gap*i)}ctx.strokeStyle='#000'ctx.stroke()ctx.closePath()//绘制中心圆点ctx.beginPath()ctx.arc(padding+gap*rcs/2,padding+gap*rcs/2,5,0,2*Math.PI)ctx.fillStyle='#000'ctx.fill()ctx.closePath()}

棋子的绘制

我们需要在行列线条交接的地方需要放置棋子,所以我们每次绘制需要循环棋盘的数据,根据棋盘数据在指定的地方绘制棋子

/***绘制棋子,先循环列,再循环行*@paramctxcanvas的2d实例*@paramdata棋盘数据*@paramnumber行列数*@paramgap行列间隔距离*@parampadding棋盘边距*@paramradius棋子的半径*/constdrawPieces=(ctx:CanvasRenderingContext2D,data:GobangData,gap:number,padding:number,radius=12)=>{constm=data.length,n=data[0].lengthfor(leti=0;i<m;i++){constcj=i*gap+padding+6-paddingconstsj=padding+i*gapfor(letj=0;j<n;j++){//值为undefined时跳过if(data[i][j]===undefined){continue}constci=j*gap+padding+6-paddingconstsi=padding+j*gapif(!data[i][j]){//值为1时,绘制黑棋drawBlackPieces(ctx,ci,cj,si,sj,radius)}else{//值为0时,绘制黑棋drawWhitePieces(ctx,ci,cj,si,sj,radius)}}}}

黑白子的绘制,只是颜色不一样

//绘制白子functiondrawWhitePieces(ctx:CanvasRenderingContext2D,ci:number,cj:number,si:number,sj:number,radius=12){ctx.beginPath()constlg2=ctx.createRadialGradient(ci,cj,5,ci,cj,20)//向圆形渐变上添加颜色lg2.addColorStop(0.1,'#fff')lg2.addColorStop(0.9,'#ddd')ctx.fillStyle=lg2ctx.arc(si,sj,radius,0,2*Math.PI)ctx.fill()ctx.closePath()}//绘制黑子functiondrawBlackPieces(ctx:CanvasRenderingContext2D,ci:number,cj:number,si:number,sj:number,radius=12){ctx.beginPath()constlg2=ctx.createRadialGradient(ci,cj,5,ci,cj,20)//向圆形渐变上添加颜色lg2.addColorStop(0.1,'#666')lg2.addColorStop(0.9,'#000')ctx.fillStyle=lg2ctx.arc(si,sj,radius,0,2*Math.PI)ctx.fill()ctx.closePath()}

其中cicj是用于棋子上渐变的坐标,sisj是用于棋子绘制的圆心坐标。

在点击canvas的时候获取相对于棋盘数据的坐标点

constcanvasClick=(e:MouseEvent)=>{if(gameOver.value){return}const{offsetX,offsetY}=econstposi=getPostions(offsetX,offsetY,gap,padding,radius)//当前位置在放置棋子范围内且没有放置棋子if(posi&&!data[posi[0]][posi[1]]){data[posi[0]][posi[1]]=current.valueinit()pushStack(data)constres=isOver(data)if(res){gameOver.value=truesetTimeout(()=>{constmsg=(Array.isArray(res)?`${data[res[0]][res[1]]?'白':'黑'}方获胜!`:'平局!')alert('游戏结束,'+msg)},50)}}}/***根据点击的坐标来获取棋盘数据的坐标*@paramoffsetX相对于父级元素的x=>列位置*@paramoffsetY相对于父级元素的Y=>行位置*@paramgap行列间隔距离*/constgetPostions=(offsetX:number,offsetY:number,gap:number,padding:number,r=12):[number,number]|false=>{constx=Math.round((offsetY-padding)/gap)consty=Math.round((offsetX-padding)/gap)//x1,y1为圆心坐标constx1=x*gap+padding,y1=y*gap+paddingconstnr=Math.pow(Math.pow(x1-offsetY,2)+Math.pow(y1-offsetX,2),0.5)if(nr<=r){return[x,y]}returnfalse}

这里来判断点击的当前位置是否是有效的,并且具体坐标的规则是:

  • 首先需要获取当前点最靠近哪一个棋子的圆心坐标

  • 然后因为棋子的半径是 12,所以点击的位置距离棋子圆心的距离不能超过 12

  • 满足则返回具体坐标,不满足则返回 false

是否结束

游戏结束分为两种情况:

  • 所有格子全部填满,平局

  • 已有相同的 5 颗棋子连成一条线,判胜负

在每一次棋子放下之后,就需要判断一次是否结束,我们每次需要判断一个坐标点的八个方向是否有相同的 4 颗棋子连成一条线。但是我们是依照从左至右,从上往下的顺序来检查的,所以具体检查只需要四个方向即可。

/***判断是否结束*从当前点查询八个方向的连续5个位置是否能连城线*但是在具体的逻辑判断中,是从左往右,从上往下一次判断的,*所以在真正的执行过程中,只需要判断4个方向即可*这里选择的四个方向是:右上、右、右下、下*@param{GobangData}data棋盘数据*/constisOver=(data:GobangData)=>{constm=data.length,n=data[0].lengthletnullCnt=m*nfor(leti=0;i<m;i++){for(letj=0;j<n;j++){if(data[i][j]!==undefined){nullCnt--if(getPostionResult(data,i,j,m,n)){return[i,j]}}}}//是否所有格子都已已有棋子return!nullCnt}/***判读当前坐标是否满足结束要求*@param{GobangData}data棋盘数据*@param{number}xx轴*@param{number}yy轴*@param{number}m最大行数*@param{number}n最大列数*@returns{boolean}*/functiongetPostionResult(data:GobangData,x:number,y:number,m:number,n:number){//右上右右下下constds=[[-1,1],[0,1],[1,1],[1,0]]constval=data[x][y]for(leti=0;i<ds.length;i++){const[dx,dy]=ds[i]letnx=x,ny=y,flag=truefor(leti=0;i<4;i++){nx+=dxny+=dy//是否是有效坐标,且值是否一样if(!(nx>=0&&nx<m&&ny>=0&&ny<n)||data[nx][ny]!==val){flag=falsebreak}}//已有5颗连成一条线if(flag){returntrue}}returnfalse}

关于是否结束的优化

是否结束还有一个优化的点,就是我们不需要判断所有坐标点是否满足,我们只需要判断最后一个放置棋子的点是否满足结束条件,但是如果只判断单个点的话,我们需要判断这个点的八个方向,所以可以优化下:

//右上左下右左右下左上下上constds=[[[-1,1],[1,-1]],[[0,1],[0,-1]],[[1,1],[-1,-1]],[[1,0],[-1,0]]]/***判读当前坐标是否满足结束要求*@param{GobangData}data棋盘数据*@param{number}xx轴*@param{number}yy轴*@param{number}m最大行数*@param{number}n最大列数*@returns{boolean}*/functiongetPostionResult(data:GobangData,x:number,y:number,m:number,n:number){constval=data[x][y]for(leti=0;i<ds.length;i++){const[[lx,ly],[rx,ry]]=ds[i]letnx=x,ny=y,cnt=1for(letj=0;j<4;j++){nx+=lxny+=lyif(!(nx>=0&&nx<m&&ny>=0&&ny<n)||data[nx][ny]!==val){break}cnt++}nx=xny=yfor(letj=0;j<4;j++){nx+=rxny+=ryif(!(nx>=0&&nx<m&&ny>=0&&ny<n)||data[nx][ny]!==val){break}cnt++}if(cnt>=5){returntrue}}returnfalse}/***判断是否结束*从当前点查询八个方向的连续5个位置是否能连城线*所有格子是否全部填满*最后下棋的坐标是否连城线*@param{GobangData}data棋盘数据*@param{[number,number]}posi最后一个是否满足结束的坐标点*/exportconstisOver=(data:GobangData,posi:[number,number])=>{constm=data.length,n=data[0].lengthletnullCnt=m*n//先判断最后一个点是否满足结束if(getPostionResult(data,posi[0],posi[1],m,n)){returnposi}for(leti=0;i<m;i++){for(letj=0;j<n;j++){if(data[i][j]!==undefined){nullCnt--}}}return!nullCnt}

悔棋功能

悔棋,也就是撤销功能,在放子的时候,保存当前的棋盘数据的快照,在悔棋的时候,拿到前一个快照的数据渲染出来。在做数据深拷贝的时候,用 JSON 的字符串解析方法,和 lodash 的深拷贝方法,都会讲原稀疏数组的空值都会填满,会破坏稀疏数组的结构定义,所以就自己根据场景写了一个拷贝方法:

//深拷贝稀疏数组functioncloneDeep<TextendsGobangData>(data:T):T{constm=data.length,n=data[0].lengthconstres=newArray(m).fill(0).map(()=>newArray(n))asTfor(leti=0;i<m;i++){for(letj=0;j<n;j++){if(data[i][j]!==undefined){res[i][j]=data[i][j]}}}returnres}//缓存constcacheData:GobangData[]=[cloneDeep<GobangData>(data)]constcacheIndex=ref(0)constpushStack=(data:GobangData)=>{cacheData.push(cloneDeep<GobangData>(data))cacheIndex.value++}constpopStack=()=>{if(cacheIndex.value&&!gameOver.value){data=cloneDeep(cacheData[--cacheIndex.value])cacheData.length=cacheIndex.value+1init()}}
 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:怎么用Js写一个简单的五子棋小游戏的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:JS如何实现简单可拖动的模态框下一篇:

4 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18