目录
效果如下
目录结构
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 = BoardList
this.name = name
this.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) return
let index_ = this.GetBoardOfID(id)
this.BoardList.splice(index_, 1)
}
// 将手中的牌按照顺序排列
BoardListSort(){
BoardSort(this.BoardList)
}
// 提示出牌
AutoSendBoard(){
// 提示出牌需要满足几个条件,
// 首先上家没有出牌时,那么按照最大的类型出牌(炸弹除外),如Y2>Y1
// 如果上家有出牌,那么需要判断当前牌组之中是否有相对应类型的牌
// 玩家需要自己维护自己所有的牌总量(机器人由程序维护),如一手牌组当中有几个飞机几个顺子
}
// 将当前的牌聚类为一个个的牌组合;返回牌组合数组
// 如:987654333 return: Y5,Y3,Y2
ClusterType(){
}
}
// 牌对象
// type ♥,♠,♦,♣
class Board{
constructor(surface, type, id){
this.surface = surface
this.type = type
this.id = type + id
this.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 = BoardList
this.Type = Type
this.Value = Value
this.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 = BoardList
this.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 tempData
var 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++){
// 顺子必须是连续的,即每个数之间相差要等于1
if(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 null
var 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],'')
}else
recode++
}
var len = tempList_1.length-recode
if(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 null
if(tempList_2==null && temp.length!=len)
return null
return 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}/g
if(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 null
var 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.Boards
this.PlayerIndex = PlayerIndex
}
Show(){
let typeShow = this.CurrentBoardCombin
if(typeShow==null)
{
TxtInfo.innerText = '无效牌'
return null
}
TxtInfo.innerText = BoardTypeMap.get(typeShow.Type)+'-'+typeShow.Value
return 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.surface
var color = 'black'
var width_ = 25
var x_ = x+5
if(text=='G'){
color = 'red'
}
if(text=='C'||text=='G') {
width_ = 14
text='Joke'
}
if(text=='0') {
text = '10'
x_ = x+8
}
var type = Board.type
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)
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) return
let id = 0
let BoardNode = null
// 有可能是直接操作的对象
if(node.target){
id = node.target.parent._id
BoardNode = node.target.parent
}else{
id = node._id
BoardNode = node
}
let tempI = 20
if(!TempBorad.has(id)){
TempBorad.set(id, Board)
}else{
tempI = -20
TempBorad.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 = true
let 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 = false
a()
})
}
// 绘制牌的反面
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 = 1
if(stopblock<0)
stopblock = 0
var temp = CalcXAndY(x, y, node.children[0].attrs.x, node.children[0].attrs.y)
let oldX = temp.x
let oldY = temp.y
var 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+3
let y1 = 5
let x2 = width*0.95 - BoardWidth +3
let y2 = 5
let myx = (width)/2 - 18
let myy = height-20
BoardLayer.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-x2
let tempY = y1-y2
return { 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.05
var BaseY_1 = BoardHight*0.3
// 发牌给下家
var BaseX_2 = width*0.95 - BoardWidth
var BaseY_2 = BoardHight*0.3
var i = 0
for(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 = false
let len = player.BoardList.length
let width_ = (width/2)-(((len-1)*25+BoardWidth)/2)
var BaseY = (height)-height*0.05-BoardHight
for(let i=0; i<player.BoardList.length; i++){
player.BoardList[i].DrawBoardFront.remove()
let tempDraw = player.BoardList[i].DrawBoardFront
MyBoardGroup.add(tempDraw)
if(tempDraw)
await RotateBoard(tempDraw, 0, 0.1, i*25+width_, BaseY, 0.2)
}
}
a()
})
}
// 玩家出牌信息绘制区
function PlayerMessage(player, BoardCombin){
let Group_ = player.Group
let Boards = BoardCombin.CurrentBoards
let len = Boards.length
let x, y;
// 玩家一出牌动画
function PalyerMySend(){
return new Promise(async (result, reject)=>{
Group_.destroyChildren()
x = (width/2)-(((len-1)*25+BoardWidth)/2)
y = 270-BoardHight
for(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)
else
x = ((width/2) + 600/2 - BoardWidth)
y = ((height/2) - 200/2 - 40)
for(let i = 0; i<len; i++){
let node1 = Boards[i].DrawBoardFront
let node2 = Boards[i].DrawBoardBack
node1.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 = 3
if(i==0) temp-=1
PlayerList[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 = text1
ButtonOne2.innerText = text2
ButtonOne1.addEventListener('click',buttonLeftEvent)
ButtonOne2.addEventListener('click',buttonRightEvent)
EventListener[0] = buttonLeftEvent
EventListener[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
// 动画ID
let RAFId = null
function 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 = 20
IsPause = false
timeControl.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 = null
if(i==0){
group = Board.DrawBoardFront
group.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.DrawBoardBack
let DrawBoardFront = Board.DrawBoardFront
DrawBoardFront.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)
else
await 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 = 0
CurrentBorad = null
}else
Bout++
// 将倒计时关闭
TimeSecond.Close()
// 下一个回合
NextGame()
}, async ()=>{
let Current_index = NextIndex(CurrentIndex, -1)
let myBoard
if(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 = 0
NextGame()
})
// 游戏回合
function NextGame(){
if(PlayEnd()) return
let CurrentIndex_ = CurrentIndex
TimeSecond.Show()
DrawTabColor(CurrentIndex)
let isRobat = false
if(CurrentIndex==0){
IsLockClick = true
Button_.ShowAndHide(true)
}else{
IsLockClick = false
Button_.ShowAndHide(false)
isRobat = true
}
PlayerList[CurrentIndex].Group.removeChildren()
CurrentIndex = NextIndex(CurrentIndex)
if(isRobat){
setTimeout(() => {
let _value = 0
if(CurrentBorad!=null)
_value = CurrentBorad.CurrentBoardCombin.Value
// 机器人出牌
RobotBoards = new BoardType(PlayerList[CurrentIndex_].BoardList)
.GetAllType('Y1', _value)
if(RobotBoards==null)
ButtonOne1.click()
else
ButtonOne2.click()
}, 1000);
}
}
NextGame()
}
// 游戏结束
function PlayEnd(){
let isEnd = false
if(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 = false
TimeSecond.Close()
Button_.HideButtonAndDeEvent()
}
return isEnd
}
// 下一个玩家
function NextIndex(CurrentIndex_, Line=1){
CurrentIndex_-=Line
if(CurrentIndex_<0)
CurrentIndex_=2
else if(CurrentIndex_>2)
CurrentIndex_=0
return CurrentIndex_
}
// 判定系统,判定是否可以出牌
// 当牌顶为空,且牌顶数量与牌顶的真实价值小于当前出牌真实价值
// 且类型一致
function JudgeBoard(CurrentBoard_){
if(CurrentBoard_.CurrentBoardCombin==null) return false
if(CurrentBorad==null) return true
if(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 true
return 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.length
for (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,
结语:
本文到这也要搁笔了,感谢你们阅读至此!感谢阅读,多多提供意见参考,评论区随意放肆评论,我会一 一读,真诚参考,