0


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

前期回顾

卡通形象人物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,

结语:

拜拜!全文完

标签: 游戏 Js 前端

本文转载自: https://blog.csdn.net/m0_57904695/article/details/128982118
版权归原作者 0.活在风浪里 所有, 如有侵权,请联系我们删除。

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

还没有评论