144 lines
4.5 KiB
JavaScript
144 lines
4.5 KiB
JavaScript
/*
|
|
* Canvas 渲染:木色棋盘、立体棋子、悬停预览、最后一手标记、获胜连线。
|
|
* 不持有游戏状态,每次 render(game, hover) 根据传入状态整体重绘。
|
|
*/
|
|
(function () {
|
|
|
|
const SIZE = 15;
|
|
const STAR_POINTS = [[3, 3], [11, 3], [3, 11], [11, 11], [7, 7]];
|
|
|
|
function createRenderer(canvas) {
|
|
const ctx = canvas.getContext('2d');
|
|
let cssSize = 600;
|
|
let margin = 0;
|
|
let cell = 0;
|
|
|
|
function resize() {
|
|
const rect = canvas.getBoundingClientRect();
|
|
cssSize = Math.max(200, Math.floor(rect.width));
|
|
const dpr = window.devicePixelRatio || 1;
|
|
canvas.width = Math.round(cssSize * dpr);
|
|
canvas.height = Math.round(cssSize * dpr);
|
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
margin = cssSize / (SIZE + 1);
|
|
cell = (cssSize - margin * 2) / (SIZE - 1);
|
|
}
|
|
|
|
function toPixel(x, y) {
|
|
return { px: margin + x * cell, py: margin + y * cell };
|
|
}
|
|
|
|
function drawBoard() {
|
|
const bg = ctx.createLinearGradient(0, 0, cssSize, cssSize);
|
|
bg.addColorStop(0, '#e3b473');
|
|
bg.addColorStop(1, '#d19a52');
|
|
ctx.fillStyle = bg;
|
|
ctx.fillRect(0, 0, cssSize, cssSize);
|
|
|
|
ctx.strokeStyle = '#7a5a2e';
|
|
ctx.lineWidth = 1;
|
|
for (let i = 0; i < SIZE; i++) {
|
|
const p = margin + i * cell;
|
|
ctx.beginPath();
|
|
ctx.moveTo(margin, p);
|
|
ctx.lineTo(cssSize - margin, p);
|
|
ctx.moveTo(p, margin);
|
|
ctx.lineTo(p, cssSize - margin);
|
|
ctx.stroke();
|
|
}
|
|
// 外框加粗一圈
|
|
ctx.lineWidth = 2;
|
|
ctx.strokeRect(margin, margin, cell * (SIZE - 1), cell * (SIZE - 1));
|
|
|
|
ctx.fillStyle = '#5a3f1c';
|
|
for (const [sx, sy] of STAR_POINTS) {
|
|
const { px, py } = toPixel(sx, sy);
|
|
ctx.beginPath();
|
|
ctx.arc(px, py, Math.max(2.5, cell * 0.09), 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
}
|
|
|
|
function drawStone(x, y, color, alpha) {
|
|
const { px, py } = toPixel(x, y);
|
|
const r = cell * 0.42;
|
|
ctx.save();
|
|
ctx.globalAlpha = alpha;
|
|
if (alpha === 1) {
|
|
ctx.shadowColor = 'rgba(0, 0, 0, 0.35)';
|
|
ctx.shadowBlur = r * 0.35;
|
|
ctx.shadowOffsetY = r * 0.12;
|
|
}
|
|
const g = ctx.createRadialGradient(px - r * 0.35, py - r * 0.35, r * 0.1, px, py, r);
|
|
if (color === 1) {
|
|
g.addColorStop(0, '#6a6a6a');
|
|
g.addColorStop(1, '#0d0d0d');
|
|
} else {
|
|
g.addColorStop(0, '#ffffff');
|
|
g.addColorStop(1, '#c9c4ba');
|
|
}
|
|
ctx.fillStyle = g;
|
|
ctx.beginPath();
|
|
ctx.arc(px, py, r, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.restore();
|
|
}
|
|
|
|
function render(game, hover) {
|
|
drawBoard();
|
|
for (let y = 0; y < SIZE; y++) {
|
|
for (let x = 0; x < SIZE; x++) {
|
|
if (game.board[y][x] !== 0) drawStone(x, y, game.board[y][x], 1);
|
|
}
|
|
}
|
|
|
|
// 最后一手红点标记(有获胜连线时不画,避免遮挡)
|
|
const last = game.moveHistory[game.moveHistory.length - 1];
|
|
if (last && !game.winningLine) {
|
|
const { px, py } = toPixel(last.x, last.y);
|
|
ctx.fillStyle = '#e6493a';
|
|
ctx.beginPath();
|
|
ctx.arc(px, py, Math.max(2.5, cell * 0.1), 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
|
|
// 获胜连线
|
|
if (game.winningLine) {
|
|
const a = toPixel(game.winningLine[0].x, game.winningLine[0].y);
|
|
const b = toPixel(
|
|
game.winningLine[game.winningLine.length - 1].x,
|
|
game.winningLine[game.winningLine.length - 1].y
|
|
);
|
|
ctx.save();
|
|
ctx.strokeStyle = 'rgba(230, 60, 45, 0.9)';
|
|
ctx.lineWidth = cell * 0.14;
|
|
ctx.lineCap = 'round';
|
|
ctx.beginPath();
|
|
ctx.moveTo(a.px, a.py);
|
|
ctx.lineTo(b.px, b.py);
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
}
|
|
|
|
// 悬停预览:半透明显示当前行棋方棋子
|
|
if (hover && !game.gameOver && game.board[hover.y][hover.x] === 0) {
|
|
drawStone(hover.x, hover.y, game.currentPlayer, 0.4);
|
|
}
|
|
}
|
|
|
|
// 把鼠标事件换算成最近的交叉点,超出棋盘返回 null
|
|
function cellFromEvent(evt) {
|
|
const rect = canvas.getBoundingClientRect();
|
|
const x = Math.round((evt.clientX - rect.left - margin) / cell);
|
|
const y = Math.round((evt.clientY - rect.top - margin) / cell);
|
|
if (x < 0 || x >= SIZE || y < 0 || y >= SIZE) return null;
|
|
return { x, y };
|
|
}
|
|
|
|
resize();
|
|
return { render, resize, cellFromEvent };
|
|
}
|
|
|
|
window.GomokuRenderer = { createRenderer };
|
|
})();
|