js
复制代码
/* 棋盘
*/
import { SCREEN_WIDTH, SCREEN_HEIGHT } from '../../render';
/**
* 获取点击屏幕的坐标
* @param {*} event
* @returns
*/
function getTouchPosition(event) {
const touches = event.changedTouches;
if (!touches || touches.length === 0) return null;
const touch = touches[0];
const touchX = touch.clientX;
const touchY = touch.clientY;
return { x: touchX, y: touchY };
}
const CAN_BACK_STEP_COUNT = 1; // 可悔棋的步数
export default class Chessboard {
constructor(musicCtrl) {
this.musicCtrl = musicCtrl;
this.isPlayedMusic = false;
this.initBoard();
this.bindTouchEvent();
}
initBoard() {
// 棋盘参数
this.boardWidth = SCREEN_WIDTH * 0.9; // 棋盘宽度为屏幕宽度的90%
this.cellSize = this.boardWidth / 9; // 每格尺寸
this.pieceRadius = this.cellSize * 0.4; // 棋子半径
this.boardLeft = (SCREEN_WIDTH - this.boardWidth) / 2; // 棋盘左边界
this.boardTop = (SCREEN_HEIGHT - this.boardWidth * 10 / 9) / 2; // 棋盘上边界,纵横比10:9
this.boardHeight = this.boardWidth * 10 / 9; // 棋盘高度
this.history = []; // 记录走棋历史
this.selectedPiece = null; // 当前选中的棋子
this.currentPlayer = 'red'; // 当前玩家:red 或 black
this.gameOver = false; // 游戏是否结束
// 初始化棋盘(10行9列,0表示无棋子,1表示红方,2表示黑方)
this.board = Array(10).fill().map(() => Array(9).fill(0));
// 棋子初始位置(以棋盘坐标表示,[row, col])
this.pieces = [
// 红方棋子
{ type: '車', player: 'red', pos: [9, 0], alive: true },
{ type: '馬', player: 'red', pos: [9, 1], alive: true },
{ type: '象', player: 'red', pos: [9, 2], alive: true },
{ type: '士', player: 'red', pos: [9, 3], alive: true },
{ type: '将', player: 'red', pos: [9, 4], alive: true },
{ type: '士', player: 'red', pos: [9, 5], alive: true },
{ type: '象', player: 'red', pos: [9, 6], alive: true },
{ type: '馬', player: 'red', pos: [9, 7], alive: true },
{ type: '車', player: 'red', pos: [9, 8], alive: true },
{ type: '炮', player: 'red', pos: [7, 1], alive: true },
{ type: '炮', player: 'red', pos: [7, 7], alive: true },
{ type: '兵', player: 'red', pos: [6, 0], alive: true },
{ type: '兵', player: 'red', pos: [6, 2], alive: true },
{ type: '兵', player: 'red', pos: [6, 4], alive: true },
{ type: '兵', player: 'red', pos: [6, 6], alive: true },
{ type: '兵', player: 'red', pos: [6, 8], alive: true },
// 黑方棋子
{ type: '車', player: 'black', pos: [0, 0], alive: true },
{ type: '馬', player: 'black', pos: [0, 1], alive: true },
{ type: '象', player: 'black', pos: [0, 2], alive: true },
{ type: '士', player: 'black', pos: [0, 3], alive: true },
{ type: '将', player: 'black', pos: [0, 4], alive: true },
{ type: '士', player: 'black', pos: [0, 5], alive: true },
{ type: '象', player: 'black', pos: [0, 6], alive: true },
{ type: '馬', player: 'black', pos: [0, 7], alive: true },
{ type: '車', player: 'black', pos: [0, 8], alive: true },
{ type: '炮', player: 'black', pos: [2, 1], alive: true },
{ type: '炮', player: 'black', pos: [2, 7], alive: true },
{ type: '卒', player: 'black', pos: [3, 0], alive: true },
{ type: '卒', player: 'black', pos: [3, 2], alive: true },
{ type: '卒', player: 'black', pos: [3, 4], alive: true },
{ type: '卒', player: 'black', pos: [3, 6], alive: true },
{ type: '卒', player: 'black', pos: [3, 8], alive: true },
];
// 更新棋盘状态
this.updateBoard();
}
bindTouchEvent() {
wx.onTouchEnd(this.handleClick.bind(this));
}
updateBoard() {
// 清空棋盘
this.board = Array(10).fill().map(() => Array(9).fill(0));
// 更新棋子位置
for (const piece of this.pieces) {
if (piece.alive) {
const [row, col] = piece.pos;
this.board[row][col] = piece.player === 'red' ? 1 : 2;
}
}
}
// 将屏幕坐标转换为棋盘坐标
screenToBoard(x, y) {
const col = Math.round((x - this.boardLeft) / this.cellSize);
const row = Math.round((y - this.boardTop) / this.cellSize);
return { row, col };
}
// 将棋盘坐标转换为屏幕坐标
boardToScreen(row, col) {
const x = this.boardLeft + col * this.cellSize;
const y = this.boardTop + row * this.cellSize;
return { x, y };
}
// 检查移动是否合法
isValidMove(piece, newRow, newCol) {
if (this.gameOver) return false;
if (newRow < 0 || newRow > 9 || newCol < 0 || newCol > 8) return false;
const [oldRow, oldCol] = piece.pos;
const targetPiece = this.pieces.find(p => p.alive && p.pos[0] === newRow && p.pos[1] === newCol);
// 不能吃自己方的棋子
if (targetPiece && targetPiece.player === piece.player) return false;
// 棋子移动规则
switch (piece.type) {
case '車':
return this.isValidRookMove(oldRow, oldCol, newRow, newCol);
case '馬':
return this.isValidKnightMove(oldRow, oldCol, newRow, newCol);
case '象':
return this.isValidElephantMove(oldRow, oldCol, newRow, newCol, piece.player);
case '士':
return this.isValidAdvisorMove(oldRow, oldCol, newRow, newCol, piece.player);
case '将':
return this.isValidKingMove(oldRow, oldCol, newRow, newCol, piece.player);
case '炮':
return this.isValidCannonMove(oldRow, oldCol, newRow, newCol, targetPiece);
case '兵':
case '卒':
return this.isValidPawnMove(oldRow, oldCol, newRow, newCol, piece.player);
default:
return false;
}
}
isValidRookMove(oldRow, oldCol, newRow, newCol) {
if (oldRow !== newRow && oldCol !== newCol) return false;
if (oldRow === newRow) {
const step = oldCol < newCol ? 1 : -1;
for (let col = oldCol + step; col !== newCol; col += step) {
if (this.board[oldRow][col] !== 0) return false;
}
} else {
const step = oldRow < newRow ? 1 : -1;
for (let row = oldRow + step; row !== newRow; row += step) {
if (this.board[row][oldCol] !== 0) return false;
}
}
return true;
}
isValidKnightMove(oldRow, oldCol, newRow, newCol) {
const rowDiff = Math.abs(newRow - oldRow);
const colDiff = Math.abs(newCol - oldCol);
if (!((rowDiff === 2 && colDiff === 1) || (rowDiff === 1 && colDiff === 2))) return false;
if (rowDiff === 2) {
const midRow = (oldRow + newRow) / 2;
if (this.board[midRow][oldCol] !== 0) return false;
} else {
const midCol = (oldCol + newCol) / 2;
if (this.board[oldRow][midCol] !== 0) return false;
}
return true;
}
isValidElephantMove(oldRow, oldCol, newRow, newCol, player) {
if (player === 'red' && newRow < 5 || player === 'black' && newRow > 4) return false;
if (Math.abs(newRow - oldRow) !== 2 || Math.abs(newCol - oldCol) !== 2) return false;
const midRow = (oldRow + newRow) / 2;
const midCol = (oldCol + newCol) / 2;
return this.board[midRow][midCol] === 0;
}
isValidAdvisorMove(oldRow, oldCol, newRow, newCol, player) {
if (player === 'red' && (newRow < 7 || newCol < 3 || newCol > 5)) return false;
if (player === 'black' && (newRow > 2 || newCol < 3 || newCol > 5)) return false;
return Math.abs(newRow - oldRow) === 1 && Math.abs(newCol - oldCol) === 1;
}
isValidKingMove(oldRow, oldCol, newRow, newCol, player) {
if (player === 'red' && (newRow < 7 || newCol < 3 || newCol > 5)) return false;
if (player === 'black' && (newRow > 2 || newCol < 3 || newCol > 5)) return false;
if (Math.abs(newRow - oldRow) + Math.abs(newCol - oldCol) !== 1) return false;
// 将帅对面
if (oldCol === newCol && this.board[newRow][newCol] === (player === 'red' ? 2 : 1)) {
const step = player === 'red' ? -1 : 1;
for (let row = oldRow + step; row !== newRow; row += step) {
if (this.board[row][newCol] !== 0) return false;
}
return true;
}
return true;
}
isValidCannonMove(oldRow, oldCol, newRow, newCol, targetPiece) {
if (oldRow !== newRow && oldCol !== newCol) return false;
let pieceCount = 0;
if (oldRow === newRow) {
const step = oldCol < newCol ? 1 : -1;
for (let col = oldCol + step; col !== newCol; col += step) {
if (this.board[oldRow][col] !== 0) pieceCount++;
}
} else {
const step = oldRow < newRow ? 1 : -1;
for (let row = oldRow + step; row !== newRow; row += step) {
if (this.board[row][oldCol] !== 0) pieceCount++;
}
}
return (targetPiece && pieceCount === 1) || (!targetPiece && pieceCount === 0);
}
isValidPawnMove(oldRow, oldCol, newRow, newCol, player) {
const rowDiff = newRow - oldRow;
const colDiff = Math.abs(newCol - oldCol);
if (colDiff > 1 || Math.abs(rowDiff) > 1) return false;
if (player === 'red') {
if (oldRow <= 4 && colDiff === 1) return false; // 过河前只能向前
if (rowDiff > 0) return false; // 只能向前
return colDiff === 0 || (colDiff === 1 && oldRow <= 4);
} else {
if (oldRow >= 5 && colDiff === 1) return false; // 过河前只能向前
if (rowDiff < 0) return false; // 只能向前
return colDiff === 0 || (colDiff === 1 && oldRow >= 5);
}
}
// 检查是否胜利
checkWin() {
const redKing = this.pieces.find(p => p.type === '将' && p.player === 'red' && p.alive);
const blackKing = this.pieces.find(p => p.type === '将' && p.player === 'black' && p.alive);
if (!redKing) {
console.log('黑方胜利');
this.gameOver = true;
return 'black';
}
if (!blackKing) {
console.log('红方胜利');
this.gameOver = true;
return 'red';
}
return null;
}
handleClick(event) {
if (this.gameOver) return;
const position = getTouchPosition(event);
if (!position) return;
const { x, y } = position;
const { row, col } = this.screenToBoard(x, y);
if (row < 0 || row > 9 || col < 0 || col > 8) return;
const clickedPiece = this.pieces.find(p => p.alive && p.pos[0] === row && p.pos[1] === col);
if (this.selectedPiece) {
if (this.isValidMove(this.selectedPiece, row, col)) {
const targetPiece = this.pieces.find(p => p.alive && p.pos[0] === row && p.pos[1] === col);
if (targetPiece) {
targetPiece.alive = false;
}
this.history.push({
piece: { ...this.selectedPiece },
from: [...this.selectedPiece.pos],
to: [row, col],
eaten: targetPiece ? { ...targetPiece } : null
});
this.selectedPiece.pos = [row, col];
this.updateBoard();
const winner = this.checkWin();
if (winner) {
this.drawWinResult(this.ctx, winner === 'red' ? '红方胜' : '黑方胜');
} else {
this.currentPlayer = this.currentPlayer === 'red' ? 'black' : 'red';
}
this.selectedPiece = null;
} else {
this.selectedPiece = null;
}
} else if (clickedPiece && clickedPiece.player === this.currentPlayer) {
this.selectedPiece = clickedPiece;
}
}
backstep() {
if (this.gameOver || this.history.length === 0 || this.history.length > CAN_BACK_STEP_COUNT) return;
const lastMove = this.history.pop();
lastMove.piece.pos = lastMove.from;
if (lastMove.eaten) {
lastMove.eaten.alive = true;
}
this.updateBoard();
this.currentPlayer = this.currentPlayer === 'red' ? 'black' : 'red';
this.selectedPiece = null;
}
drawWinResult(ctx, text) {
const fontSize = SCREEN_WIDTH * 0.3;
const rectWidth = SCREEN_WIDTH;
const rectHeight = fontSize * 1.05;
const rectX = 0;
const rectY = (SCREEN_HEIGHT - rectHeight) / 2;
ctx.fillStyle = '#8B4513'; // 咖啡色背景
ctx.strokeStyle = '#FFFF00'; // 黄色边框
ctx.lineWidth = 2;
ctx.fillRect(rectX, rectY, rectWidth, rectHeight);
ctx.strokeRect(rectX, rectY, rectWidth, rectHeight);
ctx.fillStyle = '#FFFFFF'; // 白色文字
ctx.font = `${fontSize}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, SCREEN_WIDTH / 2, rectY + rectHeight / 2);
}
render(ctx) {
if (!this.ctx) {
this.ctx = ctx;
}
// 绘制棋盘背景
ctx.fillStyle = '#F4A460'; // 木质颜色
ctx.fillRect(this.boardLeft, this.boardTop, this.boardWidth, this.boardHeight);
// 绘制格子线
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
for (let i = 0; i <= 9; i++) {
const y = this.boardTop + i * this.cellSize;
ctx.beginPath();
ctx.moveTo(this.boardLeft, y);
ctx.lineTo(this.boardLeft + this.boardWidth, y);
ctx.stroke();
}
for (let i = 0; i <= 8; i++) {
const x = this.boardLeft + i * this.cellSize;
ctx.beginPath();
ctx.moveTo(x, this.boardTop);
ctx.lineTo(x, this.boardTop + this.boardHeight);
ctx.stroke();
}
// 绘制楚河汉界
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(this.boardLeft, this.boardTop + 4.5 * this.cellSize, this.boardWidth, this.cellSize);
ctx.fillStyle = '#000000';
ctx.font = `${this.cellSize * 0.5}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('楚河 汉界', this.boardLeft + this.boardWidth / 2, this.boardTop + 5 * this.cellSize);
// 绘制九宫格
const drawPalace = (rowStart, colStart) => {
ctx.beginPath();
ctx.moveTo(this.boardLeft + colStart * this.cellSize, this.boardTop + rowStart * this.cellSize);
ctx.lineTo(this.boardLeft + (colStart + 2) * this.cellSize, this.boardTop + (rowStart + 2) * this.cellSize);
ctx.moveTo(this.boardLeft + (colStart + 2) * this.cellSize, this.boardTop + rowStart * this.cellSize);
ctx.lineTo(this.boardLeft + colStart * this.cellSize, this.boardTop + (rowStart + 2) * this.cellSize);
ctx.stroke();
};
drawPalace(0, 3); // 黑方九宫格
drawPalace(7, 3); // 红方九宫格
// 绘制棋子
for (const piece of this.pieces) {
if (piece.alive) {
const { x, y } = this.boardToScreen(piece.pos[0], piece.pos[1]);
ctx.fillStyle = '#DEB887'; // 木质浅色
ctx.beginPath();
ctx.arc(x, y, this.pieceRadius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#000000';
ctx.stroke();
ctx.fillStyle = piece.player === 'red' ? '#FF0000' : '#000000';
ctx.font = `${this.pieceRadius * 0.8}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(piece.type, x, y);
if (piece === this.selectedPiece) {
ctx.strokeStyle = '#00FF00';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(x, y, this.pieceRadius, 0, Math.PI * 2);
ctx.stroke();
}
}
}
// 如果游戏结束,绘制胜利结果
if (this.gameOver) {
const winner = this.checkWin();
if (winner) {
this.drawWinResult(ctx, winner === 'red' ? '红方胜' : '黑方胜');
}
}
this.update();
}
update() {
// 更新逻辑(如有需要)
}
}