config = {
canvasW: 570, // 画布宽
canvasH: 570, // 画布高
gridW: 30, //棋盘格子宽
gridH: 30, //棋盘格子高
me: true, // 哪方先下棋
meChessColor: '#222', // 我方棋子颜色
friendChessColor: '#fff', // 对方棋子颜色
winnerColor: '#aacf53', //赢家连线棋子颜色
meChessValue: 1, // 我方棋子代码
friendChessValue: 2, //对方棋子代码
tagBoard: [], //棋盘,保存代码数据,用于标识落子位置
// meNum: 0, // 我方下棋数量,通过Object.defineProperty设置
// friendNum: 0, // 对方下棋子数量,通过Object.defineProperty设置
};
const store = {
rows: 0, //保存的棋盘行数
cols: 0, //保存的棋盘列数
context: null, //画布上下文
canvas: null, // 画布对象
currenAxis:[], //落子的坐标记录
drawHistory:[], //落子前的棋盘图像数据记录
dirLoseWin: { // 判断输赢的方向坐标数据
TB: { me: [], friend: [] }, //上+下 "|"
TRBL: { me: [], friend: [] }, //上右 +下左 "/"
LR: { me: [], friend: [] }, // 左+右 "--"
RBLT: { me: [], friend: [] }, // 右下 + 左上 "\"
},
};
/**
* @description 监听落子数量,更新页面
*/
function listenChessNum() {
let meNum = 0;
let friendNum = 0;
Object.defineProperties(config, {
meNum: {
configurable: true,
get: function () {
return meNum;
},
set: function (value) {
meNum = value;
document.getElementById('meNum').innerText = value;
},
},
friendNum: {
configurable: true,
get: function () {
return friendNum;
},
set: function (value) {
friendNum = value;
document.getElementById('friendNum').innerText = value;
},
},
});
}
// 页面加载后,初始化棋盘
window.onload = function () {
init(config, store);
};
/**
* @description 初始化棋盘,监听落子事件,监听棋子数量变化更新页面
* @param {Object} config 配置信息
* @param {Object} store 用于保存数据
*/
function init(config,store) {
var canvas = document.getElementById('canvas');
canvas.width = config.canvasW; //设置画布宽度
canvas.height = config.canvasH; // 设置画布高度
var context = canvas.getContext('2d');
store.context = context; // 保存画布上下文到store中
store.canvas = canvas; //保存画布到上下文
let [rows, cols] = drawBoard(context, config); //绘制棋盘
store.rows = rows; //保存棋盘行数
store.cols = cols; //保存棋盘列数
listenChessNum(); //绑定监听棋子数量变化
store.canvas.addEventListener('click', clickChessHandler);//点击落棋子绑定事件
}
/**
* @description 落子事件句柄,绘制棋子的同时判断输赢
* @param {Event} e 事件对象
*/
function clickChessHandler(e) {
// 判断【上+左】边界棋子是否越界
var outsideLeftTop =
e.offsetX < config.gridW ? false : e.offsetY < config.gridH ? false : true;
// 判断【下+右】边界棋子是否越界
let outsideRgithBottom =
e.offsetX > config.canvasW - config.gridW / 2
? false
: e.offsetY > config.canvasH - config.gridH / 2
? false
: true;
// 如果都没越界
if (outsideLeftTop && outsideRgithBottom) {
var xy = judgeMouseXY(e.offsetX, e.offsetY, config); // 判断棋子落在各自的哪个顶点
// 保存落子前的画布图像数据
store.drawHistory.push(
store.context.getImageData(0, 0, config.canvasW, config.canvasH),
);
// 绘制棋子,落子
drawChess(store.context, config, xy[0], xy[1], xy[2], xy[3], config.me);
// 保存落子后的坐标数据
store.currenAxis.push([xy[2], xy[3]]);
// 判断输赢
try {
judgeWin(xy[2], xy[3]);
} catch (err) {
console.log('只是判断越界,正常逻辑,基操勿6');
}
} else {
console.log('大哥你落子棋盘歪了');
}
}
/**
* @description 绘制棋盘,底部标语/格子
* @param {CanvasRenderingContext2D} ctx 画布上下文
* @param {Object} config 配置对象
* @returns {Array} 棋盘行数和列数【rows,cols】
*/
function drawBoard(ctx, config) {
//绘制标语
ctx.font = '40px Arial';
ctx.fillStyle = '#ffc';
ctx.strokeStyle = '#222';
ctx.fillText(
'Go Bang Game',
Math.floor(config.canvasW / 2) - 138,
Math.floor(config.canvasH / 2),
);
//计算行数和列数
var rowNums = Math.floor(config.canvasH / config.gridH);
var colNums = Math.floor(config.canvasW / config.gridW);
// 初始化棋盘代码数据:0
for (var row = 1; row < rowNums; row++) {
config.tagBoard[row] = [];
for (var col = 1; col < colNums; col++) {
config.tagBoard[row][col] = 0;
}
}
//绘制横线
for (var row = 1; row < rowNums; row++) {
ctx.beginPath();
ctx.lineTo(config.gridW, row * config.gridH);
ctx.lineTo(config.canvasW - config.gridW, row * config.gridH);
ctx.stroke();
}
//绘制竖线
for (var col = 1; col < colNums; col++) {
ctx.beginPath();
ctx.lineTo(col * config.gridW, config.gridH);
ctx.lineTo(col * config.gridW, config.canvasH - config.gridH);
ctx.stroke();
}
// 返回行数和列数
return [rowNums, colNums];
}
/**
* @description 绘制棋子
* @param {CanvasRenderingContext2D} ctx 画布上下文
* @param {Object} config 配置信息
* @param {Number} x 绘制横坐标 eg: 5*gridW
* @param {Number} y 绘制纵坐标 eg: 5*gridH
* @param {Number} tagX 棋盘数据所在格子横向位置 eg: 5
* @param {Number} tagY 棋盘数据所在格子纵向位置 eg: 5
* @param {Boolean} isMe 我方还是对方
*/
function drawChess(ctx, config, x, y, tagX, tagY, isMe) {
if (
config.tagBoard[tagX][tagY] === config.meChessValue ||
config.tagBoard[tagX][tagY] === config.friendChessValue
) {
return;
}
ctx.beginPath();
if (isMe) {
ctx.fillStyle = config.meChessColor;
config.tagBoard[tagX][tagY] = config.meChessValue;
config.meNum++;
} else {
ctx.fillStyle = config.friendChessColor;
config.tagBoard[tagX][tagY] = config.friendChessValue;
config.friendNum++;
}
ctx.arc(x, y, Math.floor(config.gridW / 2), 0, Math.PI * 2);
ctx.fill();
config.me = !config.me;
}
/**
* @description 绘制赢方棋子
* @param {Array} axis 连线的棋子坐标数据
*/
function drawWinner(axis) {
for (let i = 0; i < axis.length; i++) {
let x = axis[i][0] * config.gridW;
let y = axis[i][1] * config.gridH;
store.context.beginPath();
store.context.fillStyle = config.winnerColor;
store.context.arc(x, y, Math.floor(config.gridW / 4), 0, Math.PI * 2);
store.context.fill();
}
console.log(axis);
}
/**
* @description 判断落子位置边界处理
* @param {Number} x 点击后的x轴位置,需要计算后确定落子位置
* @param {Number} y 点击后的y轴位置,需要计算后确定落子位置
* @param {Object} config 配置对象
* @returns {Array} 棋子实际落子的坐标信息 =>[落子横坐标,落子纵坐标,落子所在横向格子数,落子所在纵向格子数]
*/
function judgeMouseXY(x, y, config) {
var cordinates = [
[0, 0],
[1, 0],
[0, 1],
[1, 1],
];
var offsetD = Number.MAX_VALUE;
var index = -1;
var tmpX = x / config.gridW - Math.floor(x / config.gridW);
var tmpY = y / config.gridH - Math.floor(y / config.gridH);
for (var i = 0; i < cordinates.length; i++) {
var curNum = Math.sqrt(
Math.pow(tmpX - cordinates[i][0], 2) +
Math.pow(tmpY - cordinates[i][1], 2),
);
if (offsetD > curNum) {
offsetD = curNum;
index = i;
}
// console.log(curNum)
}
if (index !== -1) {
tmpX = cordinates[index][0] + Math.floor(x / config.gridW);
tmpY = cordinates[index][1] + Math.floor(y / config.gridH);
} else {
tmpX = Math.floor(x / config.gridW);
tmpY = Math.floor(y / config.gridH);
}
return [tmpX * config.gridW, tmpY * config.gridW, tmpX, tmpY];
}
// 检测落子数
/**
*
* @param {Array} role 当前角色的落子标记数据,连续的坐标数据
* @param {Boolean} isMe 是否我方
* @param {String} dir 方向代码 'TB'|'TRBL'| 'LR'| 'RBLT'
* @returns {Boolean} 是否输赢
*/
function checkWin(role, isMe, dir) {
if (role.length >= 5) {
let isContinue = checkContinuous(role, dir);
if (!isContinue) {
return false
}
setTimeout(function () {
alert(!isMe ? '白旗胜利✌️' : '黑棋胜利✌️');
}, 200);
store.canvas.removeEventListener('click', clickChessHandler);
console.log(role);
drawWinner(role);
return true;
}
return false;
}
/**
* @description 检测棋子连续性
* @param {Array} role 当前角色的落子标记数据,连续的坐标数据
* @param {String} dir 方向代码 'TB'|'TRBL'| 'LR'| 'RBLT'
* @returns {Boolean} 是否连续
*/
function checkContinuous(role, dir) {
let judgeX = function (role) {
let step = 1;
for (let i = 1; i < role.length; i++) {
step += 1;
if (Math.abs(role[i][0] - role[i - 1][0]) !== 1) {
step -= 1;
}
}
console.log('x', step)
return step >= 5;
}
let judgeY = function (role) {
let step = 1;
for (let i = 1; i < role.length; i++) {
step += 1;
console.log(Math.abs(role[i][1] - role[i - 1][1]), '!==1?')
if (Math.abs(role[i][1] - role[i - 1][1]) !== 1) {
step -= 1;
}
}
console.log(step)
return step >= 5;
}
switch (dir) {
case 'TB':
return judgeY(role);
case 'LR':
return judgeX(role);
case 'RBLT':
case 'TRBL':
return judgeX(role) && judgeY(role);
default: return false
}
}
// 判断输赢
/**
*
* @param {Number} x 棋子实际落子横向格子位置
* @param {Number} y 棋子实际落子纵向向格子位置
*/
function judgeWin(x, y) {
let board = config.tagBoard;
let { TB, TRBL, LR, RBLT } = store.dirLoseWin;
let isMe = !config.me;
let chessValue = isMe ? config.meChessValue : config.friendChessValue;
let dirs = ['TB', 'TRBL', 'LR', 'RBLT'];
for (let dir = 0; dir < dirs.length; dir++) {
TB.me = TB.friend = [];
TRBL.me = TRBL.friend = [];
LR.me = LR.friend = [];
RBLT.me = RBLT.friend = [];
let role = [];
switch (dirs[dir]) {
case 'TB':
role = TB[isMe ? 'me' : 'friend'];
for (let i = -4; i <= 4; i++) {
board[x][y + i] === chessValue && role.push([x, y + i]);
if (checkWin(role, isMe, dirs[dir])) return;
}
break;
case 'TRBL':
role = TRBL[isMe ? 'me' : 'friend'];
for (let i = -4; i <= 4; i++) {
board[x + i][y + i] === chessValue && role.push([x + i, y + i]);
if (checkWin(role, isMe, dirs[dir])) return;
}
break;
case 'LR':
role = TRBL[isMe ? 'me' : 'friend'];
for (let i = -4; i <= 4; i++) {
board[x + i][y] === chessValue && role.push([x + i, y]);
if (checkWin(role, isMe, dirs[dir])) return;
}
break;
case 'RBLT':
role = TRBL[isMe ? 'me' : 'friend'];
for (let i = -4; i <= 4; i++) {
board[x + i][y - i] === chessValue && role.push([x + i, y - i]);
if (checkWin(role, isMe, dirs[dir])) return;
}
break;
default:
break;
}
}
}
/**
* @description 悔棋绘制
*/
function drawBack(){
if(store.drawHistory.length === 0){
alert('无棋可悔....你好水哦😯😂')
return
}
!config.me ? --config.meNum: --config.friendNum;
config.me = !config.me;
let tagBoardData = store.currenAxis.pop()
config.tagBoard[tagBoardData[0]][tagBoardData[1]] = 0;
let imgData = store.drawHistory.pop()
store.context.clearRect(0, 0, config.canvasW, config.canvasH)
store.context.putImageData(imgData, 0, 0)
}