Files
ChineseChess/static/room.js
2026-03-04 08:04:48 +08:00

272 lines
9.0 KiB
JavaScript

const roomId = window.location.pathname.split('/').pop();
const urlParams = new URLSearchParams(window.location.search);
const password = urlParams.get('pwd') || '';
const username = localStorage.getItem('username');
if (!username) {
alert('请先登录');
window.location.href = '/';
}
const socket = io();
let myColor = null; // 'red', 'black', null
let board = [];
let turn = 'red';
let selectedRow = -1, selectedCol = -1;
const canvas = document.getElementById('chessboard');
const ctx = canvas.getContext('2d');
const cellSize = 60;
const offset = 30;
// 精确坐标转换
function getBoardPosition(clientX, clientY) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const canvasX = (clientX - rect.left) * scaleX;
const canvasY = (clientY - rect.top) * scaleY;
const x = canvasX - offset;
const y = canvasY - offset;
const col = Math.floor(x / cellSize);
const row = Math.floor(y / cellSize);
if (x < 0 || y < 0 || col >= 9 || row >= 10) {
return { row: -1, col: -1 };
}
return { row, col };
}
// 绘制棋盘
function drawBoard() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 画线
ctx.lineWidth = 2;
ctx.strokeStyle = '#8B4513';
for (let i = 0; i < 10; i++) {
ctx.beginPath();
ctx.moveTo(offset, offset + i * cellSize);
ctx.lineTo(offset + 8 * cellSize, offset + i * cellSize);
ctx.stroke();
}
for (let i = 0; i < 9; i++) {
ctx.beginPath();
ctx.moveTo(offset + i * cellSize, offset);
ctx.lineTo(offset + i * cellSize, offset + 9 * cellSize);
ctx.stroke();
}
// 楚河汉界
ctx.font = 'bold 24px "KaiTi"';
ctx.fillStyle = '#8B4513';
ctx.fillText('楚 河', offset + 2 * cellSize, offset + 4.5 * cellSize - 5);
ctx.fillText('汉 界', offset + 2 * cellSize, offset + 5.5 * cellSize - 5);
// 九宫对角线
ctx.beginPath();
ctx.moveTo(offset + 3 * cellSize, offset);
ctx.lineTo(offset + 5 * cellSize, offset + 2 * cellSize);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(offset + 5 * cellSize, offset);
ctx.lineTo(offset + 3 * cellSize, offset + 2 * cellSize);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(offset + 3 * cellSize, offset + 7 * cellSize);
ctx.lineTo(offset + 5 * cellSize, offset + 9 * cellSize);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(offset + 5 * cellSize, offset + 7 * cellSize);
ctx.lineTo(offset + 3 * cellSize, offset + 9 * cellSize);
ctx.stroke();
// 画棋子
for (let r = 0; r < 10; r++) {
for (let c = 0; c < 9; c++) {
if (!board[r] || !board[r][c]) continue;
const piece = board[r][c];
if (piece && piece !== '.') {
const x = offset + c * cellSize;
const y = offset + r * cellSize;
// 棋子圆形
ctx.beginPath();
ctx.arc(x, y, cellSize/2 - 4, 0, 2 * Math.PI);
ctx.fillStyle = piece === piece.toUpperCase() ? '#FF6B6B' : '#4A90E2';
ctx.fill();
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.stroke();
// 棋子文字
ctx.font = 'bold 28px "KaiTi"';
ctx.fillStyle = '#FFF';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
let char = '';
switch(piece.toLowerCase()) {
case 'r': char = '车'; break;
case 'h': char = '马'; break;
case 'e': char = piece === 'E' ? '象' : '相'; break;
case 'a': char = '士'; break;
case 'k': char = piece === 'K' ? '帅' : '将'; break;
case 'c': char = '炮'; break;
case 'p': char = piece === 'P' ? '兵' : '卒'; break;
}
ctx.fillText(char, x, y);
}
}
}
// 高亮选中的棋子(金色外圈 + 准星)
if (selectedRow >= 0 && selectedCol >= 0) {
const x = offset + selectedCol * cellSize;
const y = offset + selectedRow * cellSize;
// 外发光
ctx.beginPath();
ctx.arc(x, y, cellSize/2 - 2, 0, 2 * Math.PI);
ctx.strokeStyle = '#FFD700';
ctx.lineWidth = 6;
ctx.stroke();
// 准星(十字线)
ctx.beginPath();
ctx.moveTo(x - 15, y);
ctx.lineTo(x + 15, y);
ctx.moveTo(x, y - 15);
ctx.lineTo(x, y + 15);
ctx.strokeStyle = '#FFD700';
ctx.lineWidth = 3;
ctx.stroke();
// 中心点
ctx.beginPath();
ctx.arc(x, y, 4, 0, 2 * Math.PI);
ctx.fillStyle = '#FFD700';
ctx.fill();
}
}
// 鼠标点击
canvas.addEventListener('click', (e) => {
const pos = getBoardPosition(e.clientX, e.clientY);
const row = pos.row;
const col = pos.col;
if (row < 0 || row >= 10 || col < 0 || col >= 9) return;
const clickedPiece = board[row] && board[row][col];
// 旁观者
if (myColor === null) {
if (clickedPiece && clickedPiece !== '.') {
alert('您是旁观者,只能观看');
}
return;
}
// 回合检查
if (myColor !== turn) {
if (clickedPiece && clickedPiece !== '.') {
alert(`现在轮到${turn === 'red' ? '红方' : '黑方'},请等待`);
}
return;
}
if (selectedRow === -1) {
// 第一次点击:选择己方棋子
if (clickedPiece && clickedPiece !== '.') {
const isMyPiece = (myColor === 'red' && clickedPiece === clickedPiece.toUpperCase()) ||
(myColor === 'black' && clickedPiece === clickedPiece.toLowerCase());
if (isMyPiece) {
selectedRow = row;
selectedCol = col;
drawBoard();
console.log('选中:', clickedPiece, row, col);
} else {
alert('不能选择对方棋子');
}
}
} else {
// 第二次点击:尝试移动
console.log('移动请求:', selectedRow, selectedCol, '->', row, col);
socket.emit('move', {
room_id: roomId,
from: { row: selectedRow, col: selectedCol },
to: { row, col }
});
// 不清除选中,等待服务器响应
}
});
// Socket 事件
socket.on('connect', () => {
socket.emit('login', { username });
socket.emit('join_room', { room_id: roomId, password });
});
socket.on('room_joined', (data) => {
board = data.board.split('\n').map(row => row.split(''));
turn = data.turn;
myColor = data.my_color;
document.getElementById('my-color').innerText = myColor ? (myColor === 'red' ? '红方' : '黑方') : '旁观';
document.getElementById('red-player').innerText = data.players.red || '等待';
document.getElementById('black-player').innerText = data.players.black || '等待';
document.getElementById('watcher-count').innerText = data.watchers;
document.getElementById('turn').innerText = turn === 'red' ? '红方' : '黑方';
selectedRow = -1;
drawBoard();
});
socket.on('board_updated', (data) => {
board = data.board.split('\n').map(row => row.split(''));
turn = data.turn;
document.getElementById('turn').innerText = turn === 'red' ? '红方' : '黑方';
selectedRow = -1;
drawBoard();
});
socket.on('move_result', (data) => {
if (data.success) {
console.log('移动成功');
selectedRow = -1; // 清除选中
drawBoard();
} else {
alert(data.message || '非法移动');
// 保持选中,但可以闪烁提示
drawBoard(); // 重绘保持高亮
}
});
socket.on('player_joined', (data) => {
if (data.color === 'red') document.getElementById('red-player').innerText = data.username;
else document.getElementById('black-player').innerText = data.username;
});
socket.on('player_left', (data) => {
if (data.color === 'red') document.getElementById('red-player').innerText = '等待';
else document.getElementById('black-player').innerText = '等待';
});
socket.on('error', (data) => {
alert(data.msg);
selectedRow = -1;
drawBoard();
});
socket.on('force_disconnect', (data) => {
alert(data.msg);
localStorage.removeItem('username');
window.location.href = '/';
});
document.getElementById('copy-link').addEventListener('click', () => {
navigator.clipboard.writeText(window.location.href).then(() => alert('链接已复制'));
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
selectedRow = -1;
drawBoard();
}
});