JS+canvas五子棋人機(jī)對戰(zhàn)實現(xiàn)步驟詳解
更新時間:2020年06月04日 16:02:19 作者:javascript癡癡
這篇文章主要介紹了JS+canvas五子棋人機(jī)對戰(zhàn)實現(xiàn)步驟詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
1. 創(chuàng)建實例
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)計
this.myWins = []; //我的贏法統(tǒng)計
this.computerWins = []; //電腦贏法統(tǒng)計
}
2. 初始化
//初始化
Gobang.prototype.init = function(opts) {
// 生成canvas棋盤
this.createCanvas(opts);
//棋盤初始化
this.boardInit();
// 鼠標(biāo)移動聚焦功能實現(xiàn)
this.mouseMove();
//算法初始化
this.algorithmInit();
//落子功能實現(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. 移動聚焦
// 鼠標(biāo)移動時觸發(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();
//移動聚焦效果
that.focusChess(x, y);
//重繪已經(jīng)下好的棋子
that.redrawedChess()
});
}
//鼠標(biāo)移動聚焦
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(){
//初始化棋盤的每個位置和贏法
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();
// 初始化電腦和我每個贏法當(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)給每種贏法的位置信息儲存起來
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]
],
[
......
]
]
雖然這是一個三維數(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]
因為對象可以這樣取值:
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)于從左至右 一列列計算過去
*/
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. 落子實現(xiàn)
//落子實現(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ī)落子
}
})
}
//檢查落子情況
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]) { //因為贏法統(tǒng)計是從1開始的 所以對應(yīng)我的贏法需要減1
currObj[i-1] ++; // 每個經(jīng)過這個點的贏法都增加一個棋子;
enemyObj[i-1] = 6; //這里我下好棋了,證明電腦不可能在這種贏法上取得勝利了, 置為6就永遠(yuǎn)不會到5
if (currObj[i-1] === 5) { //當(dāng)達(dá)到 5 的時候,證明我勝利了
alert(currText+'贏了')
this.over = true;
}
}
}
}
10. 計算機(jī)落子實現(xiàn)
// 計算機(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);
//電腦待會落子的坐標(biāo)
var x = 0, y = 0;
// 基于我和電腦的每種贏法擁有的棋子來返回對應(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++) {
//判斷每個坐標(biāo)對應(yīng)的贏法是否存在
if (this.wins[o.x][o.y][k]) {
// 計算每種贏法,擁有多少棋子,獲取對應(yīng)分?jǐn)?shù)
// 電腦起始分?jǐn)?shù)需要高一些,因為現(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ù)時, 證明這個位置的是對我最有利的
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ù)一樣時, 證明我在這兩個位置下的效果一樣, 所以我們應(yīng)該去判斷在這兩個位置時,電腦方對應(yīng)的分?jǐn)?shù)
if (computerScore[o.x][o.y] > computerScore[x][y]) {
x = o.x;
y = o.y;
}
}
// 電腦分?jǐn)?shù)判斷, 因為是電腦落子, 所以優(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()
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Js+Ajax,Get和Post在使用上的區(qū)別小結(jié)
下面小編就為大家?guī)硪黄狫s+Ajax,Get和Post在使用上的區(qū)別小結(jié)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-06-06
淺談JavaScript函數(shù)參數(shù)的可修改性問題
這篇文章主要是對JavaScript函數(shù)參數(shù)的可修改性進(jìn)行了詳細(xì)的介紹,需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12
Bootstrap Paginator+PageHelper實現(xiàn)分頁效果
這篇文章主要為大家詳細(xì)介紹了Bootstrap Paginator+PageHelper實現(xiàn)分頁效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-12-12

