272 lines
9.0 KiB
JavaScript
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();
|
|
}
|
|
}); |