使用Canvas实现一个在线发牌游戏 [纯前端、附源码]

写在开头

  • 一位作者开源了这个游戏,纯前端实现,原生Canvas

  • 希望大家给他点个star,源码地址:https://github.com/leeseean/sic-bo

  • 这个项目克隆很慢,因为比较大,如果你想知道怎么克隆快,可以看今天公众号第二条推文 《如何把github的clone速度提升到1MB/S》

为什么要推荐这个项目

  • 在我看来,这个作者是有一定技术实力的,对canvas理解和使用,以及浏览器渲染机制,都是比较了解的,还有原生dom操作能力都可以

  • 里面大量使用了canvas的路径绘制、填充,以及requestAnimationFrame

技术栈

  • 使用原生javascript + html +css

  • 主要绘制是canvas实现

项目初始状态

怎么玩这个游戏

  • 点击想押注的地方

  • 筹码会有一个动画飞向你押注的区域

  • 押注完成后,定时开始摇骰子,开奖

大概实现

  • 初始调用 init函数,生成canvas画布,挂载onclick事件

   init() {
        let _this = this;
        _this.loadImage();
        _this.scale = screen.width < 1500 ? screen.width / 1920 : 1;
        let scale = _this.scale;
        //拿到画布
        let canvas_fly = document.getElementById('canvas_fly'); //渲染飞出去的筹码
        canvas_fly.width = document.body.clientWidth;
        canvas_fly.height = 912 * scale || 800;
        _this.ctxFly = canvas_fly.getContext('2d');
        let canvas_stop = document.getElementById('canvas_stop'); //渲染未确认投注放桌面上的筹码
        canvas_stop.width = document.body.clientWidth;
        canvas_stop.height = 912 * scale || 800;
        _this.ctxStop = canvas_stop.getContext('2d');
        let canvas_betted = document.getElementById('canvas_betted'); //渲染确认投注的筹码
        canvas_betted.width = document.body.clientWidth;
        canvas_betted.height = 912 * scale || 800;
        _this.ctxBetted = canvas_betted.getContext('2d');

        //拿到chipsImgObj对象
        let img = new Image();
        img.onload = function () {
            _this.chipsImgObj = this; //拿到chipsImgObj对象
        }
        img.src = './images/chips.png';

        //确定所用筹码
        $('.chips>.chip').off('click').on('click', function (e) {
            $(this).addClass('on').siblings('.chip').removeClass('on');
            _this.priceNum = +$(this).attr('priceNum');
        });
        $('.chips>.chip10').trigger('click'); //默认筹码10

        const pieceIntervalOver = { //连续点击翻倍生不生效的flag
            'betFor': false,
            'betted': false,
            'pieceCount': 0,
        };
        const cancelOk = {
            ok: false,
            count: 0,
        }; //取消完毕,默认false
        const resetOk = {
            ok: false,
            count: 0,// 防止重复点击
        }
        //点击桌面选号
        $('[rel="selectCode"]').off('click').on('click', function (e) {
            if (cancelOk.ok || cancelOk.count === 0) {
                cancelOk.count = 0;
            } else {
                return; //没取消完毕不准过去
            }

            _this.flyState = 'flyTo';
            let code = $(this).attr('value');
            let method = $(this).attr('method');
            let startPos = {
                x: $(`.chips .chip${_this.priceNum}`).offset().left,
                y: $(`.chips .chip${_this.priceNum}`).offset().top,
            };
            let endPos = {
                x: $(this).offset().left + $(this)[0].offsetWidth * scale / 2 - $('.chips>.chip').width() * scale / 2,
                y: $(this).offset().top + $(this)[0].offsetHeight * scale / 2 - $('.chips>.chip').height() * scale / 2,
            };
            _this.eachBetCount[code] = _this.eachBetCount[code] || 0;
            _this.eachBetCount[code] += _this.priceNum;
            _this.betOrderRecords[code] = { //记录order
                method: method,
                code: code,
                price: _this.priceNum,
                amount: _this.eachBetCount[code],
                piece: _this.eachBetCount[code],
            };
            let clickedElemOption = { //被点击元素的相关数据
                priceNum: _this.priceNum,
                code: code,
                position: {
                    x: $(this).offset().left,
                    y: $(this).offset().top,
                },
                width: $(this).outerWidth(),
                height: $(this).outerHeight(),
            };
            _this.betForRecords.push({ //记录投注
                elemOption: copyJSON(clickedElemOption),
                priceNum: _this.priceNum,
                startPos,
                endPos,
            });

            _this.chipFly(_this.ctxFly, _this.ctxStop, _this.chipsImgObj, _this.priceNum, startPos, endPos, clickedElemOption, 10);
            $('.betMoneyAmount').text(_this.calculateBetMoney());
        });
        //取消投注
        $('.cancelButton').off('click').on('click', function (e) {
            if (cancelOk.ok || cancelOk.count === 0) {
                cancelOk.ok = false;
            } else {
                return; //没取消完毕不准过去
            }
            if ((pieceIntervalOver.betFor && pieceIntervalOver.betted) || pieceIntervalOver.pieceCount === 0) {

            } else {
                return; //上次翻倍全部渲染结束才能进行下一次点击操作,没结束操作无效                
            }
            cancelOk.count += 1;

            pieceIntervalOver.pieceCount = 0;
            if (_this.betForRecords.length === 0) {
                return;
            }
            _this.flyState = 'flyBack';
            //timechunk 分时函数
            let i = 0;
            let interval = setInterval(() => {
                if (i === _this.betForRecords.length) {
                    cancelOk.ok = true;
                    cancelOk.count = 0; //回到0
                    _this.betForRecords.length = 0;
                    _this.betOrderRecords = {};
                    _this.eachBetCount = {};
                    _this.ctxStop.clearRect(0, 0, document.body.clientWidth, document.body.clientHeight);
                    $('.betMoneyAmount').text(_this.calculateBetMoney());
                    return clearInterval(interval);
                }
                let record = _this.betForRecords[i];
                _this.chipFly(_this.ctxFly, _this.ctxStop, _this.chipsImgObj, record.priceNum, record.endPos, record.startPos, record.elemOption, 10);
                i++;
            }, 10);
        });
        //重置投注
        $('.resetButton').off('click').on('click', function (e) {
            if (resetOk.ok || resetOk.count === 0) {
                resetOk.ok = false;
            } else {
                return; //没取消完毕不准过去
            }
            if ((pieceIntervalOver.betFor && pieceIntervalOver.betted) || pieceIntervalOver.pieceCount === 0) {

            } else {
                return; //上次翻倍全部渲染结束才能进行下一次点击操作,没结束操作无效                
            }
            resetOk.count += 1;

            pieceIntervalOver.pieceCount = 0;
            if (_this.betForRecords.length === 0 && _this.bettedRecords.length === 0) {
                return;
            }
            _this.flyState = 'flyBack';
            //timechunk 分时函数
            let i = 0;
            let interval = setInterval(() => {
                if (i === _this.betForRecords.length) {
                    _this.betForRecords.length = 0;
                    _this.betOrderRecords = {};
                    _this.eachBetCount = {};
                    _this.ctxStop.clearRect(0, 0, document.body.clientWidth, document.body.clientHeight);
                    $('.betMoneyAmount').text(_this.calculateBetMoney());
                    clearInterval(interval);
                    let j = 0;
                    const _interval = setInterval(() => {
                        if (j === _this.bettedRecords.length) {
                            resetOk.ok = true;
                            resetOk.count = 0; //回到0
                            _this.bettedRecords.length = 0;
                            _this.ctxBetted.clearRect(0, 0, document.body.clientWidth, document.body.clientHeight);
                            $('.betMoneyAmount').text(_this.calculateBetMoney());
                            return clearInterval(_interval);
                        }
                        console.log(j)
                        const bettedRecord = _this.bettedRecords[j];
                        _this.chipFly(_this.ctxFly, _this.ctxBetted, _this.chipsImgObj, bettedRecord.priceNum, bettedRecord.endPos, bettedRecord.startPos, bettedRecord.elemOption, 10);
                        j++;
                    }, 10);
                    return;
                }
                const betForRecord = _this.betForRecords[i];
                _this.chipFly(_this.ctxFly, _this.ctxStop, _this.chipsImgObj, betForRecord.priceNum, betForRecord.endPos, betForRecord.startPos, betForRecord.elemOption, 10);
                i++;
            }, 10);

        });
        //确认投注
        $('.betButton').off('click').on('click', function (e) {
            if (_this.betForRecords.length === 0) {
                alert('请先下注!');
                return;
            }
            if (cancelOk.ok || cancelOk.count === 0) {
            } else {
                return; //没取消完毕不准过去
            }
            if ((pieceIntervalOver.betFor && pieceIntervalOver.betted) || pieceIntervalOver.pieceCount === 0) {

            } else {
                return; //上次翻倍全部渲染结束才能进行下一次点击操作,没结束操作无效                
            }
            _this.flyState = 'betted';
            _this.bettedRecords = _this.bettedRecords.concat(_this.betForRecords);
            _this.betForRecords.forEach((record) => {
                _this.chipFly(_this.ctxFly, _this.ctxBetted, _this.chipsImgObj, record.priceNum, record.endPos, record.endPos, record.elemOption, 30);
            });
            _this.ctxStop.clearRect(0, 0, document.body.clientWidth, document.body.clientHeight);
            _this.betForRecords.length = 0;
            _this.betOrderRecords = {};
            _this.eachBetCount = {};
        });
        //翻倍投注 
        $('.pieceButtoon').off('click').on('click', function (e) {
            let bettedLen = _this.bettedRecords.length;
            let betForLen = _this.betForRecords.length;
            if (bettedLen === 0 && betForLen === 0) {
                return;
            }
            if (cancelOk.ok || cancelOk.count === 0) {
            } else {
                return; //没取消完毕不准过去
            }
            if ((pieceIntervalOver.betFor && pieceIntervalOver.betted) || pieceIntervalOver.pieceCount === 0) {
                pieceIntervalOver.betFor = false;
                pieceIntervalOver.betted = false;
                pieceIntervalOver.pieceCount += 1;
            } else {
                return; //上次翻倍全部渲染结束才能进行下一次点击操作,没结束操作无效                
            }

            //timechunk分时函数,防止短时间多次触发卡死浏览器
            let i = 0;
            let interval_bet = setInterval(() => {
                if (i === bettedLen) {
                    pieceIntervalOver.betted = true;
                    return clearInterval(interval_bet);
                }
                let code = _this.bettedRecords[i]['elemOption']['code'];
                _this.priceNum = _this.bettedRecords[i]['priceNum'];
                $(`[rel="selectCode"][value="${code}"]`).trigger('click'); //自动桌面选号点击
                i++;
            }, 10);
            let j = 0;
            let interval_betted = setInterval(() => {
                if (j === betForLen) {
                    pieceIntervalOver.betFor = true;
                    return clearInterval(interval_betted);
                }
                let code = _this.betForRecords[j]['elemOption']['code'];
                _this.priceNum = _this.betForRecords[j]['priceNum'];
                $(`[rel="selectCode"][value="${code}"]`).trigger('click'); //自动桌面选号点击
                j++;
            }, 10);
        });
    },
  • init函数初始化中将canvas画笔挂载到this中

_this.ctxStop = canvas_stop.getContext('2d');
_this.ctxBetted = canvas_betted.getContext('2d');
...
  • 确认投注后,清空未确认投注放桌面上的筹码画布

 $('.betButton').off('click').on('click'、、、
 _this.ctxStop.clearRect(0, 0, document.body.clientWidth, document.body.clientHeight);
  • 如果没有下注,就提示:

  if (_this.betForRecords.length === 0) {
                alert('请先下注!');
                return;
            }

最后,希望大家多看看这个源码,了解canvas使用

  • 记得给作者点个star哦,这是一个入门canvas的一个很好的开源项目,点击左下角的阅读原文就可以进入到源码地址啦:https://github.com/leeseean/sic-bo

  • 如果深入点的话,可以再学习一下canvas的像素控制,或者pixijs的使用

  • 在线体验这个游戏的地址是:

  • https://leeseean.github.io/sic-bo/
    

如果感觉写得不错,关注下微信公众号 [前端巅峰]

  • 我是Peter,架构设计过20万人端到端加密超级群功能的桌面IM软件,我的微信:CALASFxiaotan

  • 另外欢迎收藏我的资料网站:前端生活社区:https://qianduan.life,感觉对你有帮助,可以右下角点个在看,关注一波公众号:[前端巅峰]

你可能感兴趣的:(使用Canvas实现一个在线发牌游戏 [纯前端、附源码])