html5的canvas实现五子棋小游戏

2,570次阅读

共计 4504 个字符,预计需要花费 12 分钟才能阅读完成。

html5也出来也很久了,也是 html 标记语言的第五次重大修改。html5带来了很多原来没有的新特性,可以通过 https://www.w3.org/TR/html5/ 查看带的特性,目前大部分浏览器都支持 html5(IE9 以上)。这篇文章我们将使用canvas 来实现一个五子棋游戏。canvas提供了绘制 2D 和 3D(WebGL)的接口, 游戏的图形显示我使用 canvas 来实现,逻辑功能还是使用 javaScript 来控制。

html5 的 canvas 实现五子棋小游戏

游戏分析

五子棋的棋盘和围棋一样只是格数多少的问题,本例中我们使用围棋 19 路这个格局 (其实就是一个 19*19 的矩阵)。通过格子的大小来绘制线条,棋子的直径就是格子的大小,使用棋手对象来代表棋子(本例我们新建两个棋手对象,通过更新下棋状态来通知棋手下棋),使用二维数组( 矩阵 ) 来记录棋手下棋的位置。最终我们还要判断胜负关系, 本例的判断方法是 判断当前棋子的上、下、左、右、左上、右下、右上、左下 的八个方向中各自对应的组合方向是否能形成 5 子

创建棋盘

新建一个 Desk 类, 类的主要功能就是创建画布和渲染棋盘,以及一些开放功能获取配置、获取画布对象。


function Desk(standard, options) {
    var defaults = _.merge({
            size: 50,
            standard: standard
        }, options),
        width = height = standard  * parseInt(defaults.size, 10),
        radius = parseInt(defaults.size, 10) / 2,
        desk   = document.createElement(\'div\'),
        /**
            *
            * @type {HTMLCanvasElement}
            */
        canvas = document.createElement(\'canvas\'),
        ctx = null;
    defaults["width"] = width;
    defaults["height"]= height;
    defaults["radius"]= radius;
    canvas.setAttribute(\'id\', \'deskCanvas\');
    canvas.setAttribute(\'width\', width   "px");
    canvas.setAttribute(\'height\',height   "px");
    canvas.style.cursor = "pointer";
    desk.style.margin = "10px auto";
    desk.style.width = width   "px";

    /**
        *
        * @param key
        * @returns {*|null}
        */
    this.getOption = function(key) {return defaults[key] || null;
    };

    /**
        *
        * @returns {HTMLCanvasElement}
        */
    this.getCanvas = function() {return canvas;};
    this.getContext= function(driver) {if (ctx === null) {return canvas.getContext(driver || "2d");
        }
        return ctx;
    };
    this.setContext = function(content) {ctx = content;};

    desk.appendChild(canvas);
    document.body.insertBefore(desk, document.body.firstChild);
}
Desk.prototype = {draw: function() {
           /**
            *
            * @type {CanvasRenderingContext2D|WebGLRenderingContext}
            * @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D
            */
        var ctx = this.getContext(),
            offset = 0,
            size = this.getOption(\'size\'),
            radius = this.getRadius(),
            width = this.getOption(\'width\'),
            height = this.getOption(\'height\'),
            standard = this.getOption(\'standard\');
        this.setContext(ctx);
        // 设置填充颜色
        ctx.fillStyle = "#f6f2e9";
        // 使用 fillStyle 设置的颜色来填充矩形区域
        ctx.fillRect(0, 0, this.getOption("width"), this.getOption("height"));
        ctx.strokeStyle = "#999"; // 线条颜色
        ctx.lineWidth = 1;
        ctx.beginPath();
        for (var i = 1; i <= standard; i) {
            offset = i * size - size   radius;
            // 绘制竖线
            ctx.moveTo(offset, radius);
            ctx.lineTo(offset, height - radius);
            // 绘制横线
            ctx.moveTo(radius, offset);
            ctx.lineTo(width - radius, offset);
        }
        ctx.stroke();
        ctx.closePath();},
    getRadius: function() {return this.getOption("radius");
    },
    getSize: function() {return this.getOption("size");
    }
};

我们只需要 new Desk(19).draw() 就可以渲染出一个棋盘。

Note::
坐标点(x, y)的值不能从 0,0 开始, 要从棋子的半径位置开始增加, 写在边界棋子会绘制不全。

棋子绘制

新建一个 Chess 类,实现绘制棋子功能,初始化矩阵,获取坐标, 计算游结束。

function Chess(desk) {var position = [];
    this.standard = desk.getOption(\'standard\') - 1;
    this.size     = desk.getSize();
    this.desk = desk;
    for (var y = 0; y <= this.standard; y) {position[y] = [];
        for (var x = 0; x <= this.standard; x) {position[y][x] = null;
        }
    }
    this.addPosition = function(object) {var x = Math.floor(object.point.x / this.size),
            y = Math.floor(object.point.y / this.size);
        if (position[y][x] !== null) {return false;}
        position[y][x] = object;
        return true;
    };
    this.getPosition = function() {return position;};
    this.ctx = this.desk.getContext();
    this.radius = this.desk.getRadius();}
Chess.prototype = {
    /**
        * 绘制棋子
        * @param player
        * @returns {*}
        */
    draw: function(player) {var point = this.roundCirclePoint(player.getXY());
        if (typeof point[0] === "object") {return this.noticeReact(point);
        }
        var object = {player: player, point: point};
        if (!this.addPosition(object)) {return null;}
        this.ctx.beginPath();
        this.ctx.fillStyle = player.color;
        this.ctx.arc(point.x, point.y, this.radius, 0, 2 * Math.PI);
        this.ctx.fill();
        this.ctx.closePath();
        if (this.over(object)) {alert("玩家:"   player.name   "获得胜利");
        }
        return point;
    },
    /**
        * 绘制提示点
        * @param points
        */
    noticeReact: function(points) {
        this.ctx.strokeStyle = "#f4645f";
        for (var i in points) {var m = points[i];
            this.ctx.strokeRect(m.x - (this.radius / 2), m.y - (this.radius / 2), this.size - this.radius, this.size - this.radius
            );
        }
        return null;
    },

    /**
        * 转换坐标
        * @param point
        * @returns {*}
        */
    roundCirclePoint: function(point) {var x = Math.floor((point.x - this.radius) / this.size),
            y = Math.floor((point.y - this.radius) / this.size),
            circle = [];
        if (x < 0) x = 0;
        if (y < 0) y = 0;
        // 查找附近四个圆点中心
        for(var i = x; i <= x 1; i) {for(var j = y; j <= y 1; j) {
                circle.push({
                    x: i * 50   this.radius,
                    y: j * 50   this.radius
                });
            }
        }
        for (var index in circle) {var m = circle[index];
            var distance = Math.pow(m.x - point.x, 2)   Math.pow(m.y - point.y, 2);
            // 计算点是否在圆内
            if (distance < Math.pow(this.radius, 2)) {return m;}
        }
        return circle;
    },
    /**
        * 判断游戏结束
        * 通过判断当前棋子的八向连续相同的四子,或者正反方向的连续相同的棋子的和大于四
        * @param object
        * @returns {boolean}
        */
    over: function(object) {
        var player = object.player,
            point  = object.point,
            x = Math.floor(point.x / this.size),
            y = Math.floor(point.y / this.size),
            position = this.getPosition(),
            hx = vy = sh = sv = _hx = _vy = _sh = _sv = 0;
        for (var i = 1; i <= 4; i) {if (x i < this.standard && position[y][x i] && position[y][x i].player === player && (i - hx) === 1) {hx  ;}
            if (y i < this.standard && position[y i][x] && position[y i][x].player === player && (i - vy) === 1) {vy  ;}
            if (y i < this.standard && x i < this.standard && position[y i][x i] && position[y i][x i].player === player && (i - sv) === 1) {sv  ;}
            if (y i < this.standard && x-i> -1 && position[y i][x-i] && position[y i][x-i].player === player && (i - sh) === 1) {sh  ;}
        }
        for (var i = -1; i>= -4; i--) {if (x i> -1 && position[y][x i] && position[y][x i].player === player && (i   _hx) === -1) {_hx  ;}
            if (y i> -1 && position[y i][x] && position[y i][x].player === player && (i   _vy) === -1) {_vy  ;}
            if (y i> -1 && x i > -1 && position[y i][x i] && position[y i][x i].player === player && (i   _sv) === -1) {_sv  ;}
            if (y i> -1 && x-i = 4 || vy   _vy >= 4 || sv   _sv >= 4 || sh   _sh >= 4;
    }
};

玩家

下棋肯定少不了玩家,这里我们通过玩家点击画布获取位置再调用棋子类绘制玩家的棋子,同时更新玩家的状态。默认玩家都是等待开始的状态,当玩点击的时候更新为下棋的状态,下完的时候更新其它玩家的状态同时更新自己的状态。

   /**
    *
    * @param name
    * @constructor
    */
function Player(name, color, chess) {
    this.name = name;
    this.color = color;
    this.state = \'waitStart\';
    var x = y = 0;
    this.setXY = function(xv, yv) {x = xv; y = yv;};
    this.getXY = function() {return {x: x, y: y};};
    this.chess = chess;
}
Player.prototype = {
    /**
        *
        */
    playChess: function() {if (this.state === \'waiting\' || this.state === \'waitStart\' || this.state === \'waitPlay\') {return ;}
        var circlePoint = this.chess.draw(this);
        if (circlePoint !== null) {Events.trigger("notify", ctrl, this);
        } else {this.updateState("waitPlay");
        } 
    },
    updateState: function(state) {this.state = state;},
    getState: function() {return this.state;}
};

Note:
this.updateState("waitPlay") 当前用户点击的区域无法匹配到正常的圆点,把玩家的状态更新为等待玩的状态。

控制

控制类主要是针对玩家的,获取当前要下棋的用户,通知回调。


function Control() {this.player = [];        
};
Control.prototype = {addPlayer: function(player) {this.player.push(player);
    },
    getPlaying: function() {for (var i in this.player) {var state = this.player[i].getState();
            if (state === \'waitPlay\') {return this.player[i];
            }
        }   
        var player = this.player[0];
        player.updateState("waitPlay");
        return player;
    },
    notify: function(player) {for (var i in this.player) {if (this.player[i] !== player) {this.player[i].updateState("waitPlay");
            } else {this.player[i].updateState("waiting");
            }
        }
    },
    getPlayer: function() {return this.player;}
};

通过以上的几个类我们就完成了从棋盘到玩家再到下棋的整个流程。

事例

var desk = new Desk(19);
var chess = new Chess(desk);
var playerOne = new Player("Tom", "#FFF", chess);
var playerTwo = new Player("Seven", "#000", chess);
var ctrl = new Control();
ctrl.addPlayer(playerOne);
ctrl.addPlayer(playerTwo);
desk.draw();
Events.on(\'click\', function(e) {var player = ctrl.getPlaying();
    player.updateState("playing");
    player.setXY(e.offsetX, e.offsetY);
    player.playChess();}, desk.getCanvas());
Events.on("notify", function(player) {ctrl.notify(player);
});

总结

在整个五子棋代码中实际是没有难点之处的,主要还是个人思路要清晰。大家也可以给我指出不足的地方。下期我们使用这个和 websocket 做一个在线的五子棋游戏。

源代码

https://github.com/TianLiangZhou/loocode-example/tree/master/gobang

推荐阅读

  1. https://www.w3.org/TR/html5/
  2. https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
  3. https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D
  4. http://www.html5gamedevs.com/
  5. http://bucephalus.org/text/CanvasHandbook/CanvasHandbook.html
  6. https://joshondesign.com/p/books/canvasdeepdive/chapter01.html

正文完
 
Blood.Cold
版权声明:本站原创文章,由 Blood.Cold 2019-06-05发表,共计4504字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。