- 服务端权威状态机:落子校验、四方向五连判胜、悔棋请求、认输、离线判负、交换黑白再来一局 - 断线重连:resumeToken 凭证 + localStorage,刷新/锁屏自动回到原座位 - 移动端 Canvas 棋盘:DPR 适配、触摸容差吸附、最后一手标记、胜利连子高亮 - Docker 多阶段构建,非 root + 只读根文件系统 - k8s 清单:单副本 Recreate(内存态房态)、Ingress WebSocket 超时与粘性会话注释
56 lines
1.6 KiB
TypeScript
56 lines
1.6 KiB
TypeScript
/**
|
|
* 纯规则模块:坐标换算、落子合法性、胜负判定。
|
|
* 不依赖任何 I/O,便于单独推演和测试。
|
|
*/
|
|
import { BOARD_SIZE, CELL_COUNT, WIN_COUNT, type Cell, type Player } from '../shared/protocol.js'
|
|
|
|
export function idx(x: number, y: number): number {
|
|
return y * BOARD_SIZE + x
|
|
}
|
|
|
|
export function inBounds(x: number, y: number): boolean {
|
|
return x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE
|
|
}
|
|
|
|
export function createBoard(): Cell[] {
|
|
return new Array<Cell>(CELL_COUNT).fill(0)
|
|
}
|
|
|
|
/**
|
|
* 以最后落子点为中心,向横、竖、两条斜线四个方向统计同色连子。
|
|
* 返回该方向上的完整连子下标数组(长度 >= 5 即为获胜),否则返回 null。
|
|
*/
|
|
export function findWinLine(board: Cell[], x: number, y: number, player: Player): number[] | null {
|
|
const dirs: Array<[number, number]> = [
|
|
[1, 0],
|
|
[0, 1],
|
|
[1, 1],
|
|
[1, -1],
|
|
]
|
|
|
|
for (const [dx, dy] of dirs) {
|
|
const line: number[] = [idx(x, y)]
|
|
|
|
// 正方向延伸
|
|
for (let cx = x + dx, cy = y + dy; inBounds(cx, cy) && board[idx(cx, cy)] === player; cx += dx, cy += dy) {
|
|
line.push(idx(cx, cy))
|
|
}
|
|
// 反方向延伸,插到数组头部以保持连线有序
|
|
for (let cx = x - dx, cy = y - dy; inBounds(cx, cy) && board[idx(cx, cy)] === player; cx -= dx, cy -= dy) {
|
|
line.unshift(idx(cx, cy))
|
|
}
|
|
|
|
if (line.length >= WIN_COUNT) return line
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
export function isEmptyCell(board: Cell[], x: number, y: number): boolean {
|
|
return inBounds(x, y) && board[idx(x, y)] === 0
|
|
}
|
|
|
|
export function opponent(p: Player): Player {
|
|
return p === 1 ? 2 : 1
|
|
}
|