JS+canvas五子棋人机对战实现步骤详解
程序员文章站
2022-04-09 16:12:14
1. 创建实例function gobang () { this.over = false; // 是否结束 this.player = true; // true:我 false:电脑 thi...
1. 创建实例
function gobang () { this.over = false; // 是否结束 this.player = true; // true:我 false:电脑 this.allchesses = []; // 所有棋子 this.existchesses = [] // 已经落下的棋子 this.winscount = 0; // 赢法总数 this.wins = []; // 所有赢法统计 this.mywins = []; //我的赢法统计 this.computerwins = []; //电脑赢法统计 }
2. 初始化
//初始化 gobang.prototype.init = function(opts) { // 生成canvas棋盘 this.createcanvas(opts); //棋盘初始化 this.boardinit(); // 鼠标移动聚焦功能实现 this.mousemove(); //算法初始化 this.algorithminit(); //落子功能实现 this.dorpchess(); }
3. 生成canvas棋盘
//初始化 //生成canvas gobang.prototype.createcanvas = function(opts) { var opts = opts || {}; if (opts.width && opts.width%30 !== 0) throw new rangeerror(opts.width+'不是30的倍数'); this.col = (opts.width && opts.width/30) || 15; // 棋盘列 var ocanvas = document.createelement('canvas'); ocanvas.width = ocanvas.height = opts.width || 450; this.canvas = ocanvas; document.queryselector(opts.container || 'body').appendchild(this.canvas); this.ctx = ocanvas.getcontext('2d'); }
4. 初始化棋盘
//棋盘初始化 gobang.prototype.boardinit = function(opts){ this.drawboard(); } // 画棋盘 gobang.prototype.drawboard = function(){ this.ctx.strokestyle = "#bfbfbf"; for (var i = 0; i < this.col; i++) { this.ctx.moveto(15+ 30*i, 15); this.ctx.lineto(15+ 30*i, this.col*30-15); this.ctx.stroke(); this.ctx.moveto(15, 15+ 30*i); this.ctx.lineto(this.col*30-15, 15+ 30*i); this.ctx.stroke(); } }
5. 画棋子
// 画棋子 gobang.prototype.drawchess = function(x, y, player){ var x = 15 + x * 30, y = 15 + y * 30; this.ctx.beginpath(); this.ctx.arc(x, y, 13, 0, math.pi*2); var grd = this.ctx.createradialgradient(x + 2, y - 2, 13 , x + 2, y - 2, 0); if (player) { //我 == 黑棋 grd.addcolorstop(0, '#0a0a0a'); grd.addcolorstop(1, '#636766'); }else{ //电脑 == 白棋 grd.addcolorstop(0, '#d1d1d1'); grd.addcolorstop(1, '#f9f9f9'); } this.ctx.fillstyle = grd; this.ctx.fill() }
6. 移动聚焦
// 鼠标移动时触发聚焦效果, 需要前面的聚焦效果消失, 所有需要重绘canvas gobang.prototype.mousemove = function(){ var that = this; this.canvas.addeventlistener('mousemove', function (e) { that.ctx.clearrect(0, 0, that.col*30, that.col*30); var x = math.floor((e.offsetx)/30), y = math.floor((e.offsety)/30); //重绘棋盘 that.drawboard(); //移动聚焦效果 that.focuschess(x, y); //重绘已经下好的棋子 that.redrawedchess() }); } //鼠标移动聚焦 gobang.prototype.focuschess = function(x, y){ this.ctx.beginpath(); this.ctx.fillstyle = '#e74343'; this.ctx.arc(15 + x * 30, 15 + y * 30, 6, 0, math.pi*2); this.ctx.fill(); } //重绘当前下好的棋子 gobang.prototype.redrawedchess = function(x, y){ for (var i = 0; i < this.existchesses.length; i++) { this.drawchess(this.existchesses[i].x, this.existchesses[i].y, this.existchesses[i].player); } }
7. 算法初始化
//算法初始化 gobang.prototype.algorithminit = function(){ //初始化棋盘的每个位置和赢法 for (var x = 0; x < this.col; x++) { this.allchesses[x] = []; this.wins[x] = []; for (var y = 0; y < this.col; y++) { this.allchesses[x][y] = false; this.wins[x][y] = []; } } //获取所有赢法 this.computedwins(); // 初始化电脑和我每个赢法当前拥有的棋子数 for (var i = 0; i < this.winscount; i++) { this.mywins[i] = 0; this.computerwins[i] = 0; } }
8. 获取所有赢法
gobang.prototype.computedwins = function(){ /* 直线赢法 以15列为准 */ for (var x = 0; x < this.col; x++) { //纵向所有赢法 for (var y = 0; y < this.col-4; y ++) { this.winscount ++; /* 如: 1.组成的第一种赢法 [0,0] [0,1] [0,2] [0,3] [0,4] 2.组成的第二种赢法 [0,1] [0,2] [0,3] [0,4] [0,5] 以此类推一列最多也就11种赢法, 所有纵向x有15列 每列最多11种, 所有纵向总共15 * 11种 */ //以下for循环给每种赢法的位置信息储存起来 for (var k = 0; k < 5; k ++) { this.wins[x][y+k][this.winscount] = true; /* 位置信息 第一种赢法的时候: this.wins = [ [ [1:true], [1:true], [1:true], [1:true], [1:true] ], [ ...... ] ] 虽然这是一个三维数组, 我们把它拆分下就好理解了 相当于 this.wins[0][0][1], this.wins[0][4][1], this.wins[0][5][1], this.wins[0][6][1], this.wins[0][7][1] 因为对象可以这样取值: var obj = { a: 10, b: 'demo' } obj['a'] === obj.a 所有也就相当于 this.wins[0][0].1, this.wins[0][8].1, this.wins[0][9].1, this.wins[0][10].1, this.wins[0][11].1 虽然数组不能这么取值,可以这么理解 所以 this.wins[0][0].1 就可以理解为 在 x=0, y=0, 上有第一种赢法 this.wins[0][12].1 就可以理解为 在 x=0, y=1, 上有第一种赢法 ...... 以上this.wins[0][0],this.wins[0][13]...可以看作是 this.wins[x][y] 所以第一种赢法的坐标就是: [0,0] [0,1] [0,2] [0,3] [0,4] */ } } } for (var y = 0; y < this.col; y++) { //横向所有赢法, 同纵向赢法一样,也是15 * 11种 for (var x = 0; x < this.col-4; x ++) { this.winscount ++; for (var k = 0; k < 5; k ++) { this.wins[x+k][y][this.winscount] = true; } } }
交叉赢法
/* 交叉赢法 */ for (var x = 0; x < this.col-4; x++) { // 左 -> 右 开始的所有交叉赢法 总共11 * 11种 for (var y = 0; y < this.col-4; y ++) { this.winscount ++; /* 如: 1. [0,0] [1,1] [2,2] [3,3] [4,4] 2. [0,1] [1,2] [2,3] [3,4] [4,5] 3. [0,2] [1,3] [2,4] [3,5] [4,6] ... [1,0] [2,1] [3,2] [4,3] [5,5] 相当于从左至右 一列列计算过去 */ for (var k = 0; k < 5; k ++) { this.wins[x+k][y+k][this.winscount] = true; } } } for (var x = this.col-1; x >= 4; x --) { //右 -> 左 开始的所有交叉赢法 总共11 * 11种 for (var y = 0; y < this.col-4; y ++) { this.winscount ++; for (var k = 0; k < 5; k ++) { this.wins[x-k][y+k][this.winscount] = true; } } } }
9. 落子实现
//落子实现 gobang.prototype.dorpchess = function(){ var that = this; this.canvas.addeventlistener('click', function(e) { // 判断是否结束 if (that.over) return; var x = math.floor((e.offsetx)/30), y = math.floor((e.offsety)/30); //判断该棋子是否已存在 if (that.allchesses[x][y]) return; // 检查落子情况 that.checkchess(x, y) if (!that.over) { that.player = false; that.computerdropchess();//计算机落子 } }) } //检查落子情况 gobang.prototype.checkchess = function(x, y){ //画棋 this.drawchess(x, y, this.player); //记录落下的棋子 this.existchesses.push({ x: x, y: y, player: this.player }); //该位置棋子置为true,证明已经存在 this.allchesses[x][y] = true; this.currwinchesses(x, y, this.player); } //判断当前坐标赢的方法各自拥有几粒棋子 gobang.prototype.currwinchesses = function(x, y, player){ var currobj = player ? this.mywins : this.computerwins; var enemyobj = player ? this.computerwins : this.mywins; var currtext = player ? '我' : '电脑'; for (var i = 1; i <= this.winscount; i++) { if (this.wins[x][y][i]) { //因为赢法统计是从1开始的 所以对应我的赢法需要减1 currobj[i-1] ++; // 每个经过这个点的赢法都增加一个棋子; enemyobj[i-1] = 6; //这里我下好棋了,证明电脑不可能在这种赢法上取得胜利了, 置为6就永远不会到5 if (currobj[i-1] === 5) { //当达到 5 的时候,证明我胜利了 alert(currtext+'赢了') this.over = true; } } } }
10. 计算机落子实现
// 计算机落子 gobang.prototype.computerdropchess = function(){ var myscore = [], //玩家比分 computerscore = [], // 电脑比分 maxscore = 0; //最大比分 //比分初始化 var scoreinit = function(){ for( var x = 0; x < this.col; x ++) { myscore[x] = []; computerscore[x] = []; for (var y = 0; y < this.col; y ++) { myscore[x][y] = 0; computerscore[x][y] = 0; } } } scoreinit.call(this); //电脑待会落子的坐标 var x = 0, y = 0; // 基于我和电脑的每种赢法拥有的棋子来返回对应的分数 function formatscore(o, n) { if (o < 6 && o > 0) { var n = 10; for (var i = 0; i < o; i++) { n *= 3; } return n } return 0 } // 获取没有落子的棋盘区域 function existchess(arr) { var existarr = []; for (var i = 0; i < arr.length; i++) { for (var j = 0; j < arr[i].length; j++) { if (!arr[i][j]) { existarr.push({x:i, y:j}) } } } return existarr; } var exceptarr = existchess(this.allchesses); // 循环未落子区域,找出分数最大的位置 for (var i = 0; i < exceptarr.length; i++) { var o = exceptarr[i]; // 循环所有赢的方法 for (var k = 0; k < this.winscount; k++) { //判断每个坐标对应的赢法是否存在 if (this.wins[o.x][o.y][k]) { // 计算每种赢法,拥有多少棋子,获取对应分数 // 电脑起始分数需要高一些,因为现在是电脑落子, 优先权大 myscore[o.x][o.y] += formatscore(this.mywins[k-1], 10); computerscore[o.x][o.y] += formatscore(this.computerwins[k-1], 11); } } //我的分数判断 if (myscore[o.x][o.y] > maxscore) { //当我的分数大于最大分数时, 证明这个位置的是对我最有利的 maxscore = myscore[o.x][o.y]; x = o.x; y = o.y; }else if (myscore[o.x][o.y] === maxscore) { //当我的分数与最大分数一样时, 证明我在这两个位置下的效果一样, 所以我们应该去判断在这两个位置时,电脑方对应的分数 if (computerscore[o.x][o.y] > computerscore[x][y]) { x = o.x; y = o.y; } } // 电脑分数判断, 因为是电脑落子, 所以优先权大 if (computerscore[o.x][o.y] > maxscore) { maxscore = computerscore[o.x][o.y]; x = o.x; y = o.y; }else if (computerscore[o.x][o.y] === maxscore) { if (myscore[o.x][o.y] > myscore[x][y]) { x = o.x; y = o.y; } } } this.checkchess(x, y) if (!this.over) { this.player = true; } } var gobang = new gobang(); gobang.init()
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。