当前位置: 首页 > news >正文

开源项目 —— 原生JS实现斗地主游戏 ——代码极少、功能都有、直接粘贴即用

目录

效果如下

 目录结构

 GameEntity.js

 GrawGame.js

konva.min.js

PlayGame.js

veriable.js

index.html

结语:


前期回顾

卡通形象人物2 写代码-睡觉 丝滑如德芙_0.活在风浪里的博客-CSDN博客本文实现了包含形象的卡通小人吃、睡、电脑工作的网页动画https://blog.csdn.net/m0_57904695/article/details/128981376?spm=1001.2014.3001.5501

本文实现了包含形象斗地主网页游戏,js循环动画,简单生动的画面设计。非常丝滑有意思,欢迎对此代码感兴趣的朋友前来下载参考。

如果大家非常好奇该段代码所带来的网页游戏效果的话,那就直接拷贝到自己电脑上,不上传了资源了,方便大家直接玩乐,如果对你有些微帮助还请收藏以备下次及时找到!

  本文直接复制可以用,

效果如下

 目录结构

 GameEntity.js


/*** 实体类;有些实体其中的行为,* 可能没有严格符合面向对象规范,* 原因在于对象与过程之间的界限难以区分*/// 玩家
class Player{constructor(BoardList, name, color){this.Color = color// Board对象this.BoardList = BoardListthis.name = namethis.GroupBoard = null}// 通过ID获取牌GetBoardOfID(id){if(this.BoardList.length>0){for(let i=0;i<this.BoardList.length;i++){if(this.BoardList[i].id == id)return i}}return -1}// 根据ID删除节点DeleteIDOfBoard(id){if(this.BoardList.length<1) returnlet index_ = this.GetBoardOfID(id)this.BoardList.splice(index_, 1)}// 将手中的牌按照顺序排列BoardListSort(){BoardSort(this.BoardList)}// 提示出牌AutoSendBoard(){// 提示出牌需要满足几个条件,// 首先上家没有出牌时,那么按照最大的类型出牌(炸弹除外),如Y2>Y1// 如果上家有出牌,那么需要判断当前牌组之中是否有相对应类型的牌// 玩家需要自己维护自己所有的牌总量(机器人由程序维护),如一手牌组当中有几个飞机几个顺子}// 将当前的牌聚类为一个个的牌组合;返回牌组合数组// 如:987654333 return: Y5,Y3,Y2ClusterType(){}
}// 牌对象
// type ♥,♠,♦,♣
class Board{constructor(surface, type, id){this.surface = surfacethis.type = typethis.id = type + idthis.DrawBoardFront = CreateBorad(width/2-30, height/2-45, this)this.DrawBoardFront._id = id// id必须是要唯一的this.DrawBoardBack = BoardBack(width/2-30, height/2-45, this)this.DrawBoardBack._id = '0'+id}}// 组合牌
class BoardCombin{constructor(BoardList, Type, Value, length){this.BoardList = BoardListthis.Type = Typethis.Value = Valuethis.Length = length}
}// 这里可以优化,使用有限状态机会更好,逻辑更清晰
// 判定出牌边界,类型一致,牌的数量一致,不小于桌面价值量
// 单:    1;     Y1
// 对:    2;     Y2  *
// 三带:  3~5;   Y3 
// 顺:    5~12;  Y4
// 连对:   6~16;  Y5  *
// 飞机:   6~16;  Y6
// 四带二: 6~8;   Y7  *
// 炸弹:   4      Y8  *
// 王炸:   2      Y8  *
// 牌组分类器
class BoardType{constructor(BoardList, Boards){this.Boards = BoardListthis.BoardList = new Array()if(Boards!=null) this.BoardList = Boards// 将牌对象划为简单的字面量this.BoardListValue = new Array();for(let i=0;i<BoardList.length;i++){this.BoardList.push( BoardList[i].surface )this.BoardListValue.push(BoardMarkMap.get(BoardList[i].surface))}}// 获取出牌的类型,判定牌是否严格属于哪一类型GetType(){var length = this.BoardList.length;// 单牌if(length === 1){return this.FiltrateSign(this.BoardList)}// 对子,王炸if(length === 2){return this.FiltrateTow(this.BoardList)}// 三带,炸弹if(length>=3 && length<=5){return this.FiltrateThree(this.BoardList)}// 飞机,连对,顺子,四带二if(length>=5 && length<=16){return this.FiltrateLine(this.BoardList)}}// 单牌过滤FiltrateSign(BoardList_){var value_ = BoardMarkMap.get(BoardList_[0])return new BoardCombin(this.Boards, "Y1", value_, 1)}// 双牌过滤=》王炸FiltrateTow(BoardList_){if(BoardList_[0]===BoardList_[1]){var value_ = BoardMarkMap.get(BoardList_[0])return new BoardCombin(this.Boards, "Y2", value_, 2)} else{return this.FiltrateKingMax(BoardList_)}}// 三带过滤=》顺子=》炸弹FiltrateThree(BoardList_){var temp = BoardList_.join('')// 其中任一一张牌出现三次以上var reg = /(\d|J|Q|K|A)\1{2}/var index = temp.search(reg)if(!reg.test(temp)) {// 如果没有匹配到三带,那么有可能是顺子if(temp.length===5) return this.FiltrateLine(BoardList_)return null};var value_ = BoardMarkMap.get(BoardList_[index])// 判断是三不带|三带一|三带对temp = temp.replace(reg, '')if(temp.length==0)return new BoardCombin(this.Boards, "Y3", value_, 3)if(temp.length==1 && temp!=BoardList_[index])return new BoardCombin(this.Boards, "Y3", value_, 4)else if(temp.length==1){return this.FiltrateBomb(BoardList_);}if(temp.length==2 && temp[0] == temp[1])return new BoardCombin(this.Boards, "Y3", value_, 5)return null}// 顺子过滤=》连对=》飞机=》四带二FiltrateLine(BoardList_){var temp = BoardList_.join('')// 如果牌组数量大于5,那么更有可能是连对或者飞机,四带二if(temp.length>5){var tempData = null;// 过滤连对,过滤四带二,只有偶数才可if(temp.length%2===0){tempData = this.FiltrateLineTwo(BoardList_)if(tempData != null) return tempDatavar tempData = this.FiltrateFour(BoardList_)if(tempData != null) return tempData}// 飞机过滤tempData = this.FiltrateAir(BoardList_)if(tempData != null) return tempData}// 如果出现2,小鬼,大鬼那么就不是顺子var reg = /(2|C|G)/if(reg.test(temp)) return null;var value_ = this.BoardListValue[0]for(var i=1; i<BoardList_.length; i++){// 顺子必须是连续的,即每个数之间相差要等于1if(this.BoardListValue[i-1]-this.BoardListValue[i]!=1)return null;}return new BoardCombin(this.Boards,'Y4', value_, BoardList_.length)}// 飞机过滤// 飞机可带两张单牌,或者两个对子,亦或者不带FiltrateAir(BoardList_){var temp = BoardList_.join('')// 其中任多张牌出现三次var reg = /(0|[3-9]|J|Q|K|A)\1{2}/g // 三带var tempList_1 = temp.match(reg)if(tempList_1==null) return nullvar recode = 0// 飞机至少要是两个连起来的三带for(var i=1; i<tempList_1.length; i++){var i1 = BoardMarkMap.get(tempList_1[i][0])var i2 = BoardMarkMap.get(tempList_1[i-1][0])if(i2-i1==1){temp = temp.replace(tempList_1[i],'')temp = temp.replace(tempList_1[i-1],'')}elserecode++}var len = tempList_1.length-recodeif(len<2) return null// 返回牌组对象var value_ = BoardMarkMap.get(tempList_1[0][0])// 三不带if(temp.length===0)return new BoardCombin(this.Boards, 'Y6', value_, BoardList_.length)// 判断剩余的牌,剩余的牌有可能是单牌也可能是对子var reg_Two = /(\d|J|Q|K|A|2)\1{1}/g // 对子var tempList_2 = temp.match(reg_Two)// 飞机带对子,每个飞机可以带一个对子所以必须数量一致if(tempList_2!=null && tempList_2.length!=len && temp.length!=len)return nullif(tempList_2==null && temp.length!=len)return nullreturn new BoardCombin(this.Boards, 'Y6', value_, BoardList_.length)}// 四带二,四可带两张or两对FiltrateFour(BoardList_){if(BoardList_.length>8) return null// 因为四带二是由炸弹组成,匹配四张牌var temp = BoardList_.join('')// 其中任一一张牌出现四次var reg = /(\d|J|Q|K|A)\1{3}/var index = temp.search(reg)if(index==-1) return null// 将四张一样的牌剔除掉temp = temp.replace(reg,'')var reg_Two = /(\d|J|Q|K|A|C|G)\1{1}/g// 匹配剩余的牌;var temp_list2 = temp.match(reg_Two)if(temp.length==4 && temp_list2.length!=2) return null if(temp.length==2 && temp_list2.length==0)return null// 获取四带二的价值面var value_ = BoardMarkMap.get(BoardList_[index])return new BoardCombin(this.Boards, 'Y7', value_, BoardList_.length)}// 连对FiltrateLineTwo(BoardList_){var temp = BoardList_.join('')// 连对边缘判断,包含2,小鬼,大鬼就不是连对,且连对为偶数if((/(2|C|G)/).test(temp) || temp.length%2!=0) return null;var reg = /(\d|J|Q|K)\1{1}/gif(temp.replace(reg,'')!=='')return null;var tempList = temp.match(reg)if(tempList.length>=3){// 判断连续的对子是否为顺序的for(let j=1; j<tempList.length; j++){var tempData_1 = BoardMarkMap.get(tempList[j][0])var tempData_2 = BoardMarkMap.get(tempList[j-1][0])if(tempData_2-tempData_1!==1)return null;}}var value_ = BoardMarkMap.get(tempList[0][0])return new BoardCombin(this.Boards, 'Y5', value_, BoardList_.length)}// 炸弹FiltrateBomb(BoardList_){var temp = BoardList_.join('')// 其中任一一张牌出现四次var reg = /(\d|J|Q|K)\1{3}/if(!reg.test(temp)) return nullvar value_1 = BoardMarkMap.get(BoardList_[0])return new BoardCombin(this.Boards, 'Y8', value_1, 4)}// 王炸FiltrateKingMax(BoardList_){if(BoardList_[0]==="G" && BoardList_[1]==="C"){var value_1 = BoardMarkMap.get(BoardList_[0])var value_2 = BoardMarkMap.get(BoardList_[1])return new BoardCombin(this.Boards, "Y8", value_1+value_2, 2)}return null}// 将牌变为所有该类型GetAllType(type, value_){switch(type){case 'Y1':return this.FiltrateSign_All(value_)}}// 返回最小的一张单牌FiltrateSign_All(value_){for(let i=this.BoardListValue.length; i>0; i--){if(this.BoardListValue[i-1]>value_)return [this.Boards[i-1]]}return null}}// 玩家出牌记录器
class BoardPlayer{constructor(BoardList, PlayerIndex){if(BoardList instanceof Map)BoardList = Array.from(BoardList.values())// 先将牌面进行排序BoardSort(BoardList)let Boards = new BoardType(BoardList)// 再将牌变为合法的规则牌this.CurrentBoardCombin = Boards.GetType()this.CurrentBoards = Boards.Boardsthis.PlayerIndex = PlayerIndex}Show(){let typeShow = this.CurrentBoardCombinif(typeShow==null) {TxtInfo.innerText = '无效牌'return null}TxtInfo.innerText = BoardTypeMap.get(typeShow.Type)+'-'+typeShow.Valuereturn typeShow}
}// 已出牌池
class BoardPond{constructor(){this.CurrentBoardPond = new Array()}// 出牌,将牌置入出牌池SetAddBoard(BoardPlayer){this.CurrentBoardPond.unshift(BoardPlayer)}// 获取牌顶的类型GetCurrentBoard(){return this.CurrentBoardPond[0].CurrentBoardCombin}
}

 GrawGame.js

/*** 游戏的画面及动作绘制*///------------------- 绘制各种图形和动作// 绘制牌
function CreateBorad(x, y, Board){var group = new Konva.Group()var text = Board.surfacevar color = 'black'var width_ = 25var x_ = x+5if(text=='G'){color = 'red'}if(text=='C'||text=='G') {width_ = 14text='Joke'}if(text=='0') {text = '10' x_ = x+8}var type = Board.typevar rect = new Konva.Rect({x: x,y: y,stroke: '#555',fill: 'white',strokeWidth: 1,shadowColor: 'black',shadowBlur: 0,shadowOffset: { x: 5, y: 5 },shadowOpacity: 0.3,width: BoardWidth,height: BoardHight,});group.add(rect)if(type=='♠' || type=='♣') color = 'black'else if(type=='♥' || type=='♦') color = 'red'var BoardTxt = new Konva.Text({x: x+5,y: y+5,text: text,fontSize: 18,width: width_,fill: color});group.add(BoardTxt)if(text!='Joke'){group.add(new Konva.Text({x: x_,y: y+20,text: type,fontSize: 20,fill: color}))group.add(new Konva.Text({x: x+BoardWidth*0.33,y: y+25,text: type,fontSize: BoardWidth*0.666+5,fill: color}))}else{// 绘制大符号group.add(new Konva.Text({x: x+BoardWidth*0.266,y: y+30,text: type,fontSize: 25,fill: color}))}return group;
}// 选中牌动画绘制;IsPass:是否绕过该判断,直接对牌进行操作,一般用于牌的初始化
function GoOrOut(node, Board, IsPass=false){// console.log(node)if(!IsLockClick && !IsPass) returnlet id = 0let BoardNode = null// 有可能是直接操作的对象if(node.target){id = node.target.parent._idBoardNode = node.target.parent}else{id = node._idBoardNode = node}let tempI = 20if(!TempBorad.has(id)){TempBorad.set(id, Board)}else{tempI = -20TempBorad.delete(id)}var tween = new Konva.Tween({node: BoardNode,duration: 0.005,y: BoardNode.attrs.y-tempI,});tween.play();
}// 取消选中的牌,将牌归位
function RestoreBoard(){return new Promise((a, b)=>{IsLockClick = truelet TempBorad_ = Array.from(TempBorad)for(let i=0;i<TempBorad_.length;i++){// console.log(TempBorad_[i])GoOrOut(TempBorad_[i][1].DrawBoardFront, TempBorad_[i][1], true)}IsLockClick = falsea()})}// 绘制牌的反面
function BoardBack(x, y){var group = new Konva.Group()var rect = new Konva.Rect({x: x,y: y,stroke: '#555',fill: 'white',strokeWidth: 1,shadowColor: 'black',shadowBlur: 0,shadowOffset: { x: 5, y: 5 },shadowOpacity: 0.3,width: BoardWidth,height: BoardHight,})group.add(rect)for(var i=0; i<10; i++){let tempPoints = new Array()for(var j=0; j<10; j++){if(j%2==0){tempPoints.push(x+ i*BoardWidth/10+BoardWidth/10)}else{tempPoints.push(x+ i*BoardWidth/10)}tempPoints.push(y+BoardHight/9*j)}var redLine = new Konva.Line({points: tempPoints,stroke: 'block',strokeWidth: 1,lineCap: 'round',lineJoin: 'round'});group.add(redLine)}return group
}// 旋转牌并移动角度并移动位置
// 动画执行到百分之多少就开始停止阻塞
function RotateBoard(node, rotation, duration, x, y, stopblock){return new Promise((a, b)=>{if(stopblock==null||stopblock==undefined||stopblock>1) stopblock = 1if(stopblock<0) stopblock = 0var temp = CalcXAndY(x, y, node.children[0].attrs.x, node.children[0].attrs.y)let oldX = temp.xlet oldY = temp.yvar tween = new Konva.Tween({node: node,duration: duration,x: oldX,y: oldY,rotation: rotation});tween.play();setTimeout(() => {a()}, duration*1000*stopblock);})
}// 绘制倒计时的秒表
function DrawTime(){var simpleText = new Konva.Text({x: width / 2 - 40,y: height / 2 - 50,id: 'Time',text: EveryTime,fontSize: 40,fontFamily: 'Calibri',stroke: 'black',fill: 'white',padding: 5,align: 'center'});return simpleText
}// 绘制地主和农民的标签
function ClassTitle(){let x1 = width*0.05+3let y1 = 5let x2 = width*0.95 - BoardWidth +3let y2 = 5let myx = (width)/2 - 18 let myy = height-20BoardLayer.add(Draw(x1, y1, Loandlords_Paly_==1?'地主':'农民', Loandlords_Paly_==1?'blue':'black',  'white'))BoardLayer.add(Draw(x2, y2, Loandlords_Paly_==2?'地主':'农民', Loandlords_Paly_==2?'blue':'black', 'white'))BoardLayer.add(Draw(myx, myy, Loandlords_Paly_==0?'地主':'农民', Loandlords_Paly_==0?'blue':'black', 'white'))function Draw( x, y, text, bgcolor, txtcolor){var tooltip = new Konva.Label({x: x,y: y,opacity: 0.75});tooltip.add(new Konva.Tag({fill: bgcolor,lineJoin: 'round',shadowColor: 'black',shadowBlur: 10,shadowOffsetX: 10,shadowOffsetY: 10,shadowOpacity: 0.5}));tooltip.add(new Konva.Text({text: text,fontFamily: 'Calibri',fontSize: 15,padding: 3,fill: txtcolor}));return tooltip}
}// 绘制桌子
function DrawTable(){var play0Rect = new Konva.Rect({x: (width/2)-(((20-1)*25+BoardWidth)/2)-10,y: (height)-height*0.05-BoardHight - 10,width: ((20-1)*25+BoardWidth)+20,height: BoardHight + 20,cornerRadius: [10, 10, 10, 10],fill: PlayerList[0].Color,});var play1Rect = new Konva.Rect({x: 30,y: 20,width: BoardWidth+30,height: 18*17,cornerRadius: [10, 10, 10, 10],fill: PlayerList[1].Color});var play2Rect = new Konva.Rect({x: width - (BoardWidth+60),y: 20,width: BoardWidth+30,height: 18*17,cornerRadius: [10, 10, 10, 10],fill: PlayerList[2].Color,});Tab_ = new Konva.Rect({x: ((width/2) - 600/2),y: ((height/2) - 200/2 - 40),width: 600,height: 200,cornerRadius: [10, 10, 10, 10],fill: 'DarkGreen',});BoardLayer.add(play0Rect);BoardLayer.add(play1Rect);BoardLayer.add(play2Rect);BoardLayer.add(Tab_);
}// 绘制桌子颜色
function DrawTabColor(pIndex){var tween = new Konva.Tween({node: Tab_,duration: 1,fill: PlayerList[pIndex].Color});tween.play()
}//-------------------// 计算X和目标X,Y和目标Y相距多少
function CalcXAndY(x1, y1, x2, y2){let tempX = x1-x2let tempY = y1-y2return { x: tempX, y: tempY }
}// 发牌动画
function DrawSendBoard(){return new Promise(async (a, b)=>{// 发牌给玩家var GroupWidth = (width/2)-((16*25+BoardWidth)/2)var BaseY = (height)-height*0.05-BoardHight// 发牌给上家var BaseX_1 = width*0.05var BaseY_1 = BoardHight*0.3// 发牌给下家var BaseX_2 = width*0.95 - BoardWidthvar BaseY_2 = BoardHight*0.3var i = 0for(let i=0; i<17; i++){await RotateBoard(PlayerList[1].BoardList[i].DrawBoardBack, 0, 0.25, BaseX_1, BaseY_1+i*10, 0.05)RotateBoard(PlayerList[1].BoardList[i].DrawBoardFront, 0, 0.25, BaseX_1, BaseY_1+i*10, 0)RotateBoard(PlayerList[2].BoardList[i].DrawBoardBack, 0, 0.25, BaseX_2, BaseY_2+i*10, 0.05) RotateBoard(PlayerList[2].BoardList[i].DrawBoardFront, 0, 0.25, BaseX_2, BaseY_2+i*10, 0) RotateBoard(PlayerList[0].BoardList[i].DrawBoardFront, 0, 0.25, i*25+GroupWidth, BaseY, 0.05)}a()})
}// 将牌整理到合适位置
function InitLocationBoard(player){return new Promise(async (a,b)=>{if(player.name=='玩家一'){player.BoardListSort()IsLockClick = falselet len = player.BoardList.lengthlet width_ = (width/2)-(((len-1)*25+BoardWidth)/2)var BaseY = (height)-height*0.05-BoardHightfor(let i=0; i<player.BoardList.length; i++){player.BoardList[i].DrawBoardFront.remove()let tempDraw = player.BoardList[i].DrawBoardFrontMyBoardGroup.add(tempDraw)if(tempDraw)await RotateBoard(tempDraw, 0, 0.1, i*25+width_, BaseY, 0.2)}}a()})}// 玩家出牌信息绘制区
function PlayerMessage(player, BoardCombin){let Group_ = player.Grouplet Boards = BoardCombin.CurrentBoardslet len = Boards.lengthlet x, y;// 玩家一出牌动画function PalyerMySend(){return new Promise(async (result, reject)=>{Group_.destroyChildren()x = (width/2)-(((len-1)*25+BoardWidth)/2)y = 270-BoardHightfor(let i = 0; i<len; i++){let node = Boards[i].DrawBoardFront// 注销事件node.off('mouseenter.select')node.off('mousedown.click')node.remove()Group_.add(node)await RotateBoard(node, 0, 0.2, i*25+x, y, 0)player.DeleteIDOfBoard(Boards[i].id)}result()})}// 玩家二或三出牌动画function PalyerTwoOrThreeSend(player, BoardCombin){return new Promise(async (result, reject)=>{Group_.destroyChildren()if(player.name=='玩家二')x = ((width/2) - 600/2)elsex = ((width/2) + 600/2 - BoardWidth)y = ((height/2) - 200/2 - 40)for(let i = 0; i<len; i++){let node1 = Boards[i].DrawBoardFrontlet node2 = Boards[i].DrawBoardBacknode1.remove()node2.remove()Group_.add(node1)Group_.add(node2)RotateBoard(node1, 0, 0.2, i*25+x, y, 0)await RotateBoard(node2, 0, 0.2, i*25+x, y, 0)player.DeleteIDOfBoard(Boards[i].id)node1.show()node2.hide()}BoardLayer.draw()result()})}return new Promise(async (result, reject)=>{if(BoardCombin==null){// 画不要result()return}if(player.name=='玩家一'){await PalyerMySend(player, BoardCombin)}else{await PalyerTwoOrThreeSend(player, BoardCombin)}await InitLocationBoard(player)result()})
}

konva.min.js

这里代码比较长,已上传主页资源,也可以评论区找我要~

PlayGame.js

/*** 游戏主体逻辑,包括开始游戏,结束游戏等* 主要是开始游戏到循环游戏直到游戏结束的状态变化* 也包括初始化整个游戏*/// 牌面初始化
function InitBoard(){// 将牌打乱for(let i=53;i>0;i--){BoardListGameInit[i].DrawBoardFront.hide()BoardListGameInit[i].DrawBoardBack.hide()let randomIndex = Math.floor( Math.random()*i+1)BoardListGameInit.push(BoardListGameInit[randomIndex])BoardListGameInit.splice(randomIndex,1)}// 地主牌for(let i=0; i<6; i++){let temp = 3if(i==0) temp-=1PlayerList[0].BoardList.push(...BoardListGameInit.splice(0, temp))PlayerList[1].BoardList.push(...BoardListGameInit.splice(0, temp))PlayerList[2].BoardList.push(...BoardListGameInit.splice(0, temp))}// 将牌排列整齐BoardSort(BoardListGameInit)PlayerList[0].BoardListSort()PlayerList[1].BoardListSort()PlayerList[2].BoardListSort()}// 按钮闭包
var Button_ = (function(){// 注册事件let EventListener = new Array()// 显示按钮且添加事件function ShowButtonAndEvent(text1, text2, buttonLeftEvent, buttonRightEvent){// 添加事件之前需要先注销掉之前的事件HideButtonAndDeEvent()ShowAndHide(true)ButtonOne1.innerText = text1ButtonOne2.innerText = text2ButtonOne1.addEventListener('click',buttonLeftEvent)ButtonOne2.addEventListener('click',buttonRightEvent)EventListener[0] = buttonLeftEventEventListener[1] = buttonRightEvent}// 隐藏按钮且删除事件function HideButtonAndDeEvent(){ShowAndHide(false)// 移除事件if(EventListener.length>0){ButtonOne1.removeEventListener('click',EventListener[0])ButtonOne2.removeEventListener('click',EventListener[1])EventListener.splice(0,1)EventListener.splice(0,1)}}// 隐藏或显示function ShowAndHide(IsShow){ButtonDiv.style.display = IsShow? 'block':'none'}return {ShowButtonAndEvent: ShowButtonAndEvent,HideButtonAndDeEvent: HideButtonAndDeEvent,ShowAndHide: ShowAndHide}
})()// 倒计时闭包
var TimeSecond = (function(){let i = EveryTime// 绘制秒表let timeControl = null// 是否暂停let IsPause = false// 动画IDlet RAFId = nullfunction Show(){if(!timeControl){timeControl = DrawTime()BoardLayer.add(timeControl)}timeControl.show()DrawStart()}let oldTime = new Date()let newDate = 0// 开始倒计时function DrawStart(){newDate = new Date()if(newDate-oldTime > 1000 && !IsPause){// console.log()timeControl.text(i<10?'0'+i:i)BoardLayer.draw()i--oldTime = new Date()}if(i<0){Close()// 默认当该倒计时结束,那么触发第一个按钮的点击事件ButtonOne1.click()return} RAFId = window.requestAnimationFrame(DrawStart)}// 关闭且初始化function Close(){if(RAFId)window.cancelAnimationFrame(RAFId)i = 20IsPause = falsetimeControl.hide()BoardLayer.draw()RAFId = null}return {timeControl: timeControl,Show: Show,Close: Close,IsPause: IsPause}
})()// 发牌
async function SendBorad(){// 玩家牌组// 在玩家牌组上面注册一个组合,如果在该组合上面点击,那么将开启选牌// 离开该组合将注销let IsSelect = false // 是否可以选中牌MyBoardGroup = new Konva.Group()NotMeBoard_Group = new Konva.Group()MyBoardGroup.on('mousedown', ()=> { IsSelect=true })MyBoardGroup.on('mouseup mouseleave', ()=> { IsSelect=false;if(TempBorad!=null)new BoardPlayer(TempBorad, 0).Show()})// 给地主牌注册事件,因为该地主牌有可能是玩家的手牌for(let i=0;i<3; i++){BoardListGameInit[i].DrawBoardFront.show()BoardListGameInit[i].DrawBoardBack.hide()// 注册事件BoardListGameInit[i].DrawBoardFront.on('mouseenter.select', (node)=>{if (IsSelect && Loandlords_Paly_==0)GoOrOut(node, BoardListGameInit[i])})BoardListGameInit[i].DrawBoardFront.on('mousedown.click', (node)=>{if(Loandlords_Paly_==0)GoOrOut(node, BoardListGameInit[i])})}// 生成具体的牌对象for(let i = 0;i<3; i++){for(let j=0; j<PlayerList[i].BoardList.length;j++){let Board = PlayerList[i].BoardList[j]let group = nullif(i==0){group = Board.DrawBoardFrontgroup.on('mouseenter.select', (node)=>{if (IsSelect)GoOrOut(node, Board)})group.on('mousedown.click', (node)=>{GoOrOut(node, Board)})// console.log(group)MyBoardGroup.add(group)}else{group = Board.DrawBoardBacklet DrawBoardFront = Board.DrawBoardFrontDrawBoardFront.hide()NotMeBoard_Group.add(group)NotMeBoard_Group.add(DrawBoardFront)}group.show()}}BoardLayer.add(MyBoardGroup)BoardLayer.add(NotMeBoard_Group)// 开始发牌动画await DrawSendBoard(BoardLayer)StartGame()
}// 开始玩地主
async function StartGame(){// 将地主牌放到左上角for(let i = 0;i<3;i++){BoardLayer.add(BoardListGameInit[i].DrawBoardFront)BoardLayer.add(BoardListGameInit[i].DrawBoardBack)RotateBoard(BoardListGameInit[i].DrawBoardFront, 0, 0.2, width*0.15+i*BoardWidth, 0)RotateBoard(BoardListGameInit[i].DrawBoardBack, 0, 0.2, width*0.15+i*BoardWidth, 0.2)}// 叫地主setTimeout(() => {TimeSecond.Show()Loandlords_Paly()}, 500);// 将决定一个地主出来function Loandlords_Paly(){// 显示按钮且绑定事件Button_.ShowButtonAndEvent('不叫','叫地主', async ()=>{Loandlords_Paly_ = Math.floor( Math.random()*2+1)// 将倒计时关闭TimeSecond.Close()Button_.HideButtonAndDeEvent(true)// 必须等待动画完成await new Promise(async (a,b)=>{for(let i = 0; i<3; i++){BoardListGameInit[i].DrawBoardBack.show()PlayerList[Loandlords_Paly_].BoardList.push(BoardListGameInit[i])NotMeBoard_Group.add(BoardListGameInit[i].DrawBoardBack)if(Loandlords_Paly_==1)await RotateBoard(BoardListGameInit[i].DrawBoardBack, 0, 0.2, width*0.05, (BoardHight*0.3)+(17*10)+i*10)elseawait RotateBoard(BoardListGameInit[i].DrawBoardBack, 0, 0.2, (width*0.95 - BoardWidth), (BoardHight*0.3)+(17*10)+i*10)PlayerList[Loandlords_Paly_].BoardListSort()a()}})StartFunGame()}, async ()=>{// 将倒计时关闭TimeSecond.Close()Button_.HideButtonAndDeEvent(true)Loandlords_Paly_ = 0// 叫地主;那么将地主牌添加到玩家手牌中PlayerList[0].BoardList.push(...BoardListGameInit)// 整理牌await InitLocationBoard(PlayerList[0])StartFunGame()})}
}// 开始游戏循环
function StartFunGame(){// 实例化一个已经出了的牌池BoardPond_ = new BoardPond()// 绘制地主和农民ClassTitle()PlayerGroupCreate()BoardLayer.draw()// 玩家出牌的下标let CurrentIndex = Loandlords_Paly_// 周期回合数;用以记录当前是否已走完一个周期(即三名玩家)let Bout = 0// 绑定出牌不出牌事件Button_.ShowButtonAndEvent('不出','出牌', async ()=>{// 如果当前的回合是玩家,且第一张出牌的也是玩家,// 如果玩家还没出牌,那么需要随机出一张牌if(!CurrentBorad && CurrentIndex==0){console.log('玩家随机出牌')// 将牌复原的合适的位置await RestoreBoard()}// 当前已过完一整个周期;那么牌堆处设置为空if(Bout==1){Bout = 0CurrentBorad = null}elseBout++// 将倒计时关闭TimeSecond.Close()// 下一个回合NextGame()}, async ()=>{let Current_index = NextIndex(CurrentIndex, -1)let myBoardif(Current_index == 0){myBoard = new BoardPlayer(TempBorad, 0)TempBorad.clear()}else // 机器人{myBoard = new BoardPlayer(RobotBoards, Current_index)RobotBoards = null}// 判定是否可以出牌if(JudgeBoard(myBoard)){// 将倒计时关闭TimeSecond.Close()Button_.ShowAndHide(false)// 将牌顶换为当前牌CurrentBorad = myBoard// console.log(myBoard)BoardPond_.SetAddBoard(myBoard)await PlayerMessage(PlayerList[Current_index], myBoard)}else {console.log('不能出牌')return}// 新回合的开始重新设置Bout = 0NextGame()})// 游戏回合function NextGame(){if(PlayEnd()) returnlet CurrentIndex_ = CurrentIndexTimeSecond.Show()DrawTabColor(CurrentIndex)let isRobat = falseif(CurrentIndex==0){IsLockClick = trueButton_.ShowAndHide(true)}else{IsLockClick = falseButton_.ShowAndHide(false)isRobat = true}PlayerList[CurrentIndex].Group.removeChildren()CurrentIndex = NextIndex(CurrentIndex)if(isRobat){setTimeout(() => {let _value = 0if(CurrentBorad!=null)_value = CurrentBorad.CurrentBoardCombin.Value// 机器人出牌RobotBoards = new BoardType(PlayerList[CurrentIndex_].BoardList).GetAllType('Y1', _value)if(RobotBoards==null)ButtonOne1.click()elseButtonOne2.click()}, 1000);}}NextGame()
}// 游戏结束
function PlayEnd(){let isEnd = falseif(PlayerList[0].BoardList.length==0){alert('游戏结束;你赢了')isEnd = true}if(PlayerList[1].BoardList.length==0){alert('游戏结束;你上家赢了')isEnd = true}if(PlayerList[2].BoardList.length==0){alert('游戏结束;你下家赢了')isEnd = true}if(isEnd){IsLockClick = falseTimeSecond.Close()Button_.HideButtonAndDeEvent()}return isEnd
}// 下一个玩家
function NextIndex(CurrentIndex_, Line=1){CurrentIndex_-=Lineif(CurrentIndex_<0)CurrentIndex_=2else if(CurrentIndex_>2)CurrentIndex_=0return CurrentIndex_
}// 判定系统,判定是否可以出牌
// 当牌顶为空,且牌顶数量与牌顶的真实价值小于当前出牌真实价值
// 且类型一致
function JudgeBoard(CurrentBoard_){if(CurrentBoard_.CurrentBoardCombin==null) return falseif(CurrentBorad==null) return trueif(CurrentBorad.CurrentBoardCombin.Value < CurrentBoard_.CurrentBoardCombin.Value)if(CurrentBorad.CurrentBoardCombin.Type == CurrentBoard_.CurrentBoardCombin.Type)if(CurrentBorad.CurrentBoardCombin.Length < CurrentBoard_.CurrentBoardCombin.Length || CurrentBoard_.CurrentBoardCombin.Type != 'Y8')return truereturn false
}// 玩家们的手牌绘制区
function PlayerGroupCreate(){PlayerList[0].Group = new Konva.Group()PlayerList[1].Group = new Konva.Group()PlayerList[2].Group = new Konva.Group()BoardLayer.add(PlayerList[0].Group)BoardLayer.add(PlayerList[1].Group)BoardLayer.add(PlayerList[2].Group)
}// 将牌按照大小进行排序
function BoardSort(BoardList){// 先将牌面进行排序let len = BoardList.lengthfor (var i = 0; i < len - 1; i++) {for (var j = 0; j < len - 1 - i; j++) {let value_1 = BoardMarkMap.get(BoardList[j].surface)let value_2 = BoardMarkMap.get(BoardList[j+1].surface)if (value_1 < value_2) {        // 相邻元素两两对比var temp = BoardList[j+1];        // 元素交换BoardList[j+1] = BoardList[j];BoardList[j] = temp;}}}
}// 开始加载游戏
window.onload = function(){ButtonDiv = document.getElementById('ButtonDiv')ButtonOne1 = document.getElementById('CallLandLord')ButtonOne2 = document.getElementById('RobLandLord')TxtInfo = document.getElementById('txtInfo')// 首先必须添加一个场景Stage = new Konva.Stage({container: '#bodyPlayer',   // id of container <div>width: width,height: height});BoardLayer = new Konva.Layer()Stage.add(BoardLayer)DrawTable()InitBoard()SendBorad()}// 执行动画,第一个参数,动画执行函数
function RAF(){}

veriable.js

/*** 作者:0.活在风浪里 v1.5.0* 变量初始化,记录有关游戏的所有全局变量*///---------------------------------------游戏基本变量// 牌面映射,字面量=》价值量
var BoardMarkMap = new Map();
BoardMarkMap.set('3', 3)
BoardMarkMap.set('4', 4)
BoardMarkMap.set('5', 5)
BoardMarkMap.set('6', 6)
BoardMarkMap.set('7', 7)
BoardMarkMap.set('8', 8)
BoardMarkMap.set('9', 9)
BoardMarkMap.set('0', 10) // 10
BoardMarkMap.set('J', 11)
BoardMarkMap.set('Q', 12)
BoardMarkMap.set('K', 13)
BoardMarkMap.set('A', 14)
BoardMarkMap.set('2', 15)
BoardMarkMap.set('C', 50) // 小鬼
BoardMarkMap.set('G', 100) // 大鬼// 牌组合类型映射
var BoardTypeMap = new Map()
BoardTypeMap.set('Y1', '单牌')
BoardTypeMap.set('Y2', '对子')
BoardTypeMap.set('Y3', '三带')
BoardTypeMap.set('Y4', '顺子')
BoardTypeMap.set('Y5', '连对')
BoardTypeMap.set('Y6', '飞机')
BoardTypeMap.set('Y7', '四带二')
BoardTypeMap.set('Y8', '炸弹')
// BoardTypeMap.set('Y9', '王炸')// 牌的绘制容器管理,可以通过该容器索引到任何一张牌的绘制内容
var BoardDrawMap = new Map()// keys集合;获取牌的字面量如K,J,Q....
var BoardMarkMapKes = Array.from(BoardMarkMap.keys())//---------------------------------------游戏结构性逻辑变量(包括绘制的逻辑变量)// 画布的大小
const width = 844
const height = 390// 牌有关的全局变量
const BoardWidth = 45
const BoardHight = 80// 按钮的大小
const ButtonWidth = 120
const ButtonPadding = 20// 每个回合等待的时间
const EveryTime = 20
// 按钮的容器:控制整体是否显示与否
var ButtonDiv = document.getElementById('ButtonDiv')
// 初始:叫地主=》过牌
var ButtonOne1 = document.getElementById('CallLandLord')
// 初始:不叫=》出牌
var ButtonOne2 = document.getElementById('RobLandLord')
// 头顶信息框
var TxtInfo = document.getElementById('txtInfo')// 玩家手牌图层
var MyBoardGroup = null
// 机器人手牌图层
var NotMeBoard_Group = null//---------------------------------------游戏内容逻辑变量
// 牌面绘制映射
var GrawBoard = new Map()
// 牌面选中集合
var TempBorad = new Map()
// 是否锁住点击事件
var IsLockClick = false
// 有几个玩家发完牌
var SendBoard = 0 
// 机器人牌面
var RobotBoards// 玩家
var PlayerList = new Array()
// 自己,
PlayerList[0] = new Player([],'玩家一', 'SaddleBrown')
// 左边上家
PlayerList[1] = new Player([],'玩家二', 'DarkOliveGreen')
// 右边下家
PlayerList[2] = new Player([],'玩家三', 'MediumSlateBlue')// 场景
var Stage = null
// 是否已出牌或者叫了地主
var isSelect = false
// 地主索引=》对应的是玩家的下标
var Loandlords_Paly_ = -1// 图层
var BoardLayer = null
// 当前桌面牌堆;如果为null,则表明当前可以随意出牌
// 不然下一家必须接牌;类型:BoardPond
var CurrentBorad = null
// 牌池
var BoardPond_ = null
// 中间的桌子
var Tab_ = null// 生成各种牌
var BoardListGameInit = new Array();
for(var i=0;i<15;i++){var key = BoardMarkMapKes[i]if(key=='C'){BoardListGameInit.push(new Board(key, '🧛', i*4))continue}else if(key=='G'){BoardListGameInit.push(new Board(key, '🧙‍♂️', 53))continue}BoardListGameInit.push(new Board(key, '♠', i*4))BoardListGameInit.push(new Board(key, '♥', i*4+1))BoardListGameInit.push(new Board(key, '♣', i*4+2))BoardListGameInit.push(new Board(key, '♦', i*4+3))
}

index.html

<!DOCTYPE html>
<head><meta http-equiv="Content-Type" content="text/html" charset="UTF-8"><meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" ><title></title><script src="script/konva.min.js"></script><script src="script/GrawGame.js" ></script><script src="script/GameEntity.js" ></script><script src="script/veriable.js" ></script><script src="script/PlayGame.js" ></script><style>body {position: fixed;width: 100%;height: 390px;padding: 0;margin: 0;overflow: hidden;}.ButtonDiv{width: 90px;height: 20px;text-align: center;border-radius: 5px;padding: 5px;cursor: pointer;position:absolute;}#CallLandLord{top: 230px;left: calc(50% - 120px);background-color: aqua;}#RobLandLord{top: 230px;left: calc(50%);background-color: rgb(244, 158, 11);}#ButtonDiv{display: none;}@media screen and (orientation: portrait) {body {position: absolute;width: 100vh;height: 390px;top: 0;left: 100vw;-webkit-transform: rotate(90deg);-moz-transform: rotate(90deg);-ms-transform: rotate(90deg);transform: rotate(90deg);transform-origin: 0% 0%;}}@media screen and (orientation: landscape) {body {position: absolute;top: 0;left: 0;width: 100vw;height: 390px;}}
</style>
</head>
<body ><div id="bodyPlayer" style="margin: 0 auto; max-width: 844px; width: 100%; height: 390px; border: solid 1px;"></div><div style="width: 300px; text-align: center;position: absolute; top: 3%; left: calc(50% - 150px);" id="txtInfo"> 信息 </div><div id="ButtonDiv"><div id="CallLandLord" class="ButtonDiv" > 不叫 </div><div id="RobLandLord" class="ButtonDiv" > 叫地主 </div></div><script></script>
</body></html>

以上则是全部代码,复制项目直接运行index.html,

结语:

本文到这也要搁笔了,感谢你们阅读至此!感谢阅读,多多提供意见参考,评论区随意放肆评论,我会一 一读,真诚参考,

 

http://www.lryc.cn/news/2984.html

相关文章:

  • Linux第四讲
  • Redis 持久化
  • Python语言零基础入门教程(十三)
  • 江苏五年制专转本应该复习几轮?
  • 微信小程序的优化方案之主包与分包的研究
  • 从手工测试转型web自动化测试继而转型成专门做自动化测试的学习路线。
  • 【计组笔记03】计算机组成原理之系统五大部件介绍、主存模型和CPU结构介绍
  • 微信小程序解析用户加密数据
  • 毕业四年换了3份软件测试工作,我为何仍焦虑?
  • 嵌入式C基础知识(7)
  • 大数据系列之:安装pulsar详细步骤
  • 色彩-基础理论
  • 1629_MIT_6.828_xv6_chapter1操作系统的组织
  • 基于Golang哈希算法监控配置文件变化
  • 关于一笔画问题的一些思考(欧拉路Fleury算法、逐步插入回路法、以及另一种可能的解法)
  • vlookup怎么用详细步骤,看这一篇就够了
  • 雅思经验(9)之小作文常用词汇总结
  • 【Python语言基础】——Python NumPy 数组创建
  • 【大数据】Hadoop-Kms 安装及相关详细配置,看完你就会了
  • SpringCloud分布式框架
  • Csss属性display,visibility区别,对渲染页面的影响
  • 怎么给笔记本电脑外接两台显示器?
  • 生成树协议 — STP
  • git必会的知识点
  • 【hello, world】计算机系统漫游
  • 1. SpringMVC 简介
  • 《解谜三星堆:开启中华文明之门》-范勇 笔记
  • 锐捷(十四)mpls vxn optionc的关键问题所在和具体问题分析
  • Python语言零基础入门教程(十四)
  • Https 协议超强讲解(一)