摘要:五子棋人機(jī)大戰(zhàn)創(chuàng)建實(shí)例是否結(jié)束我電腦所有棋子已經(jīng)落下的棋子贏法總數(shù)所有贏法統(tǒng)計(jì)我的贏法統(tǒng)計(jì)電腦贏法統(tǒng)計(jì)初始化初始化生成棋盤棋盤初始化鼠標(biāo)移動(dòng)聚焦功能實(shí)現(xiàn)算法初始化落子功能實(shí)現(xiàn)生成棋盤初始化生成不是的倍數(shù)棋盤列初始化棋盤棋盤初始化畫棋盤畫
JS+canvas五子棋人機(jī)大戰(zhàn)
1. 創(chuàng)建實(shí)例function Gobang () { this.over = false; // 是否結(jié)束 this.player = true; // true:我 false:電腦 this.allChesses = []; // 所有棋子 this.existChesses = [] // 已經(jīng)落下的棋子 this.winsCount = 0; // 贏法總數(shù) this.wins = []; // 所有贏法統(tǒng)計(jì) this.myWins = []; //我的贏法統(tǒng)計(jì) this.computerWins = []; //電腦贏法統(tǒng)計(jì) }2. 初始化
//初始化 Gobang.prototype.init = function(opts) { // 生成canvas棋盤 this.createCanvas(opts); //棋盤初始化 this.boardInit(); // 鼠標(biāo)移動(dòng)聚焦功能實(shí)現(xiàn) this.mouseMove(); //算法初始化 this.algorithmInit(); //落子功能實(shí)現(xiàn) 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的倍數(shù)"); 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. 移動(dòng)聚焦
// 鼠標(biāo)移動(dòng)時(shí)觸發(fā)聚焦效果, 需要前面的聚焦效果消失, 所有需要重繪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(); //移動(dòng)聚焦效果 that.focusChess(x, y); //重繪已經(jīng)下好的棋子 that.redrawedChess() }); } //鼠標(biāo)移動(dòng)聚焦 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(); } //重繪當(dāng)前下好的棋子 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(){ //初始化棋盤的每個(gè)位置和贏法 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(); // 初始化電腦和我每個(gè)贏法當(dāng)前擁有的棋子數(shù) for (var i = 0; i < this.winsCount; i++) { this.myWins[i] = 0; this.computerWins[i] = 0; } }8. 獲取所有贏法
Gobang.prototype.computedWins = function(){ /* 直線贏法 以15列為準(zhǔn) */ 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循環(huán)給每種贏法的位置信息儲(chǔ)存起來 for (var k = 0; k < 5; k ++) { this.wins[x][y+k][this.winsCount] = true; /* 位置信息 第一種贏法的時(shí)候: this.wins = [ [ [1:true], [1:true], [1:true], [1:true], [1:true] ], [ ...... ] ] 雖然這是一個(gè)三維數(shù)組, 我們把它拆分下就好理解了 相當(dāng)于 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] 因?yàn)閷?duì)象可以這樣取值: var obj = { a: 10, b: "demo" } obj["a"] === obj.a 所有也就相當(dāng)于 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 雖然數(shù)組不能這么取值,可以這么理解 所以 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] 所以第一種贏法的坐標(biāo)就是: [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] 相當(dāng)于從左至右 一列列計(jì)算過去 */ 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. 落子實(shí)現(xiàn)
//落子實(shí)現(xiàn) Gobang.prototype.dorpChess = function(){ var that = this; this.canvas.addEventListener("click", function(e) { // 判斷是否結(jié)束 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();//計(jì)算機(jī)落子 } }) } //檢查落子情況 Gobang.prototype.checkChess = function(x, y){ //畫棋 this.drawChess(x, y, this.player); //記錄落下的棋子 this.existChesses.push({ x: x, y: y, player: this.player }); //該位置棋子置為true,證明已經(jīng)存在 this.allChesses[x][y] = true; this.currWinChesses(x, y, this.player); } //判斷當(dāng)前坐標(biāo)贏的方法各自擁有幾粒棋子 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]) { //因?yàn)橼A法統(tǒng)計(jì)是從1開始的 所以對(duì)應(yīng)我的贏法需要減1 currObj[i-1] ++; // 每個(gè)經(jīng)過這個(gè)點(diǎn)的贏法都增加一個(gè)棋子; enemyObj[i-1] = 6; //這里我下好棋了,證明電腦不可能在這種贏法上取得勝利了, 置為6就永遠(yuǎn)不會(huì)到5 if (currObj[i-1] === 5) { //當(dāng)達(dá)到 5 的時(shí)候,證明我勝利了 alert(currText+"贏了") this.over = true; } } } }9. 計(jì)算機(jī)落子實(shí)現(xiàn)
// 計(jì)算機(jī)落子 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); //電腦待會(huì)落子的坐標(biāo) var x = 0, y = 0; // 基于我和電腦的每種贏法擁有的棋子來返回對(duì)應(yīng)的分?jǐn)?shù) 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 } // 獲取沒有落子的棋盤區(qū)域 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); // 循環(huán)未落子區(qū)域,找出分?jǐn)?shù)最大的位置 for (var i = 0; i < exceptArr.length; i++) { var o = exceptArr[i]; // 循環(huán)所有贏的方法 for (var k = 0; k < this.winsCount; k++) { //判斷每個(gè)坐標(biāo)對(duì)應(yīng)的贏法是否存在 if (this.wins[o.x][o.y][k]) { // 計(jì)算每種贏法,擁有多少棋子,獲取對(duì)應(yīng)分?jǐn)?shù) // 電腦起始分?jǐn)?shù)需要高一些,因?yàn)楝F(xiàn)在是電腦落子, 優(yōu)先權(quán)大 myScore[o.x][o.y] += formatScore(this.myWins[k-1], 10); computerScore[o.x][o.y] += formatScore(this.computerWins[k-1], 11); } } //我的分?jǐn)?shù)判斷 if (myScore[o.x][o.y] > maxScore) { //當(dāng)我的分?jǐn)?shù)大于最大分?jǐn)?shù)時(shí), 證明這個(gè)位置的是對(duì)我最有利的 maxScore = myScore[o.x][o.y]; x = o.x; y = o.y; }else if (myScore[o.x][o.y] === maxScore) { //當(dāng)我的分?jǐn)?shù)與最大分?jǐn)?shù)一樣時(shí), 證明我在這兩個(gè)位置下的效果一樣, 所以我們應(yīng)該去判斷在這兩個(gè)位置時(shí),電腦方對(duì)應(yīng)的分?jǐn)?shù) if (computerScore[o.x][o.y] > computerScore[x][y]) { x = o.x; y = o.y; } } // 電腦分?jǐn)?shù)判斷, 因?yàn)槭请娔X落子, 所以優(yōu)先權(quán)大 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()
github地址
線上地址
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/90080.html
摘要:撤銷悔棋功能悔棋過后,再撤銷,相當(dāng)于還原悔棋之前的狀態(tài)。三總結(jié)五子棋游戲的核心關(guān)鍵點(diǎn)是弄清楚有多少種贏法怎么判斷是否已經(jīng)贏了計(jì)算機(jī)下棋算法。 一、功能模塊 先看下現(xiàn)在做完的效果: showImg(https://segmentfault.com/img/remote/1460000009826653?w=481&h=640); 線上體驗(yàn):https://wj704.github.io/...
摘要:預(yù)覽效果源碼核心代碼記錄是否走過所有贏法數(shù)量贏法數(shù)組我方贏法的統(tǒng)計(jì)數(shù)組計(jì)算機(jī)贏法的統(tǒng)計(jì)數(shù)組初始化填充數(shù)組是否走過贏法數(shù)組橫豎斜反斜贏法的統(tǒng)計(jì)數(shù)組繪制水印繪制棋盤落子實(shí)現(xiàn)我方落子統(tǒng)計(jì)贏法你贏了計(jì)算機(jī)落子最大分?jǐn)?shù)所在坐標(biāo)遍歷棋盤遍 預(yù)覽效果 github源碼 showImg(https://segmentfault.com/img/bVbk02l?w=450&h=450); 核心代碼 ...
摘要:預(yù)覽效果源碼核心代碼記錄是否走過所有贏法數(shù)量贏法數(shù)組我方贏法的統(tǒng)計(jì)數(shù)組計(jì)算機(jī)贏法的統(tǒng)計(jì)數(shù)組初始化填充數(shù)組是否走過贏法數(shù)組橫豎斜反斜贏法的統(tǒng)計(jì)數(shù)組繪制水印繪制棋盤落子實(shí)現(xiàn)我方落子統(tǒng)計(jì)贏法你贏了計(jì)算機(jī)落子最大分?jǐn)?shù)所在坐標(biāo)遍歷棋盤遍 預(yù)覽效果 github源碼 showImg(https://segmentfault.com/img/bVbk02l?w=450&h=450); 核心代碼 ...
閱讀 3525·2021-11-17 17:01
閱讀 3934·2021-11-08 13:12
閱讀 2487·2021-10-08 10:04
閱讀 708·2021-09-29 09:35
閱讀 1429·2021-09-26 10:12
閱讀 2057·2021-09-07 09:58
閱讀 1965·2019-08-30 15:55
閱讀 2143·2019-08-30 13:14