Update chess/public/js/app.js
This commit is contained in:
+342
-89
@@ -90,8 +90,11 @@ const elements = {
|
|||||||
partyTimeControl: document.querySelector("#partyClockSelect"),
|
partyTimeControl: document.querySelector("#partyClockSelect"),
|
||||||
|
|
||||||
startAiButton: document.querySelector("#startAiButton"),
|
startAiButton: document.querySelector("#startAiButton"),
|
||||||
|
newGameToolbarButton: document.querySelector("#newGameToolbarButton"),
|
||||||
|
|
||||||
createPartyButton: document.querySelector("#createPartyButton"),
|
createPartyButton: document.querySelector("#createPartyButton"),
|
||||||
|
newPartyGameButton: document.querySelector("#newPartyGameButton"),
|
||||||
|
|
||||||
partyCodeInput: document.querySelector("#partyCodeInput"),
|
partyCodeInput: document.querySelector("#partyCodeInput"),
|
||||||
joinPartyButton: document.querySelector("#joinPartyButton"),
|
joinPartyButton: document.querySelector("#joinPartyButton"),
|
||||||
leavePartyButton: document.querySelector("#leavePartyButton"),
|
leavePartyButton: document.querySelector("#leavePartyButton"),
|
||||||
@@ -113,6 +116,11 @@ const elements = {
|
|||||||
whiteClock: document.querySelector("#whiteClock"),
|
whiteClock: document.querySelector("#whiteClock"),
|
||||||
blackClock: document.querySelector("#blackClock"),
|
blackClock: document.querySelector("#blackClock"),
|
||||||
|
|
||||||
|
capturedByWhite: document.querySelector("#capturedByWhite"),
|
||||||
|
capturedByBlack: document.querySelector("#capturedByBlack"),
|
||||||
|
|
||||||
|
moveHistory: document.querySelector("#moveHistory"),
|
||||||
|
|
||||||
promotionDialog: document.querySelector("#promotionDialog"),
|
promotionDialog: document.querySelector("#promotionDialog"),
|
||||||
promotionOptions: document.querySelector("#promotionOptions"),
|
promotionOptions: document.querySelector("#promotionOptions"),
|
||||||
|
|
||||||
@@ -303,10 +311,9 @@ function showToast(message, type = "info") {
|
|||||||
|
|
||||||
elements.toastHost.appendChild(toast);
|
elements.toastHost.appendChild(toast);
|
||||||
|
|
||||||
window.setTimeout(
|
window.setTimeout(() => {
|
||||||
() => toast.remove(),
|
toast.remove();
|
||||||
3200
|
}, 3200);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -384,15 +391,13 @@ function renderBoard() {
|
|||||||
const selectedMoves =
|
const selectedMoves =
|
||||||
getLegalMovesForSelected();
|
getLegalMovesForSelected();
|
||||||
|
|
||||||
const moveTargets = new Map(
|
const moveTargets = new Map();
|
||||||
selectedMoves.map(move => [
|
|
||||||
move.to,
|
|
||||||
move,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
const orientation =
|
for (const move of selectedMoves) {
|
||||||
appState.orientation;
|
moveTargets.set(move.to, move);
|
||||||
|
}
|
||||||
|
|
||||||
|
const orientation = appState.orientation;
|
||||||
|
|
||||||
const xOrder =
|
const xOrder =
|
||||||
orientation === "w"
|
orientation === "w"
|
||||||
@@ -426,16 +431,24 @@ function renderBoard() {
|
|||||||
document.createElement("button");
|
document.createElement("button");
|
||||||
|
|
||||||
square.type = "button";
|
square.type = "button";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ВАЖНО:
|
||||||
|
* Теперь цвет клетки задаётся непосредственно
|
||||||
|
* через .light / .dark.
|
||||||
|
*/
|
||||||
|
const isDark =
|
||||||
|
(x + y) % 2 === 0;
|
||||||
|
|
||||||
square.className =
|
square.className =
|
||||||
`square ${
|
`square ${
|
||||||
(x + y) % 2 === 0
|
isDark ? "dark" : "light"
|
||||||
? "dark"
|
|
||||||
: "light"
|
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
square.dataset.square =
|
square.dataset.square =
|
||||||
squareName;
|
squareName;
|
||||||
|
|
||||||
|
/* Selected */
|
||||||
if (
|
if (
|
||||||
appState.selectedSquare ===
|
appState.selectedSquare ===
|
||||||
squareName
|
squareName
|
||||||
@@ -445,6 +458,7 @@ function renderBoard() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Legal move */
|
||||||
const move =
|
const move =
|
||||||
moveTargets.get(squareName);
|
moveTargets.get(squareName);
|
||||||
|
|
||||||
@@ -456,6 +470,7 @@ function renderBoard() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Last move */
|
||||||
if (
|
if (
|
||||||
lastMove &&
|
lastMove &&
|
||||||
(
|
(
|
||||||
@@ -468,6 +483,7 @@ function renderBoard() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check */
|
||||||
if (
|
if (
|
||||||
piece &&
|
piece &&
|
||||||
piece.type === "K" &&
|
piece.type === "K" &&
|
||||||
@@ -478,6 +494,7 @@ function renderBoard() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Piece */
|
||||||
if (piece) {
|
if (piece) {
|
||||||
const pieceNode =
|
const pieceNode =
|
||||||
document.createElement("span");
|
document.createElement("span");
|
||||||
@@ -494,15 +511,26 @@ function renderBoard() {
|
|||||||
piece.color
|
piece.color
|
||||||
][piece.type];
|
][piece.type];
|
||||||
|
|
||||||
square.appendChild(pieceNode);
|
pieceNode.setAttribute(
|
||||||
|
"aria-hidden",
|
||||||
|
"true"
|
||||||
|
);
|
||||||
|
|
||||||
|
square.appendChild(
|
||||||
|
pieceNode
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
square.addEventListener(
|
square.addEventListener(
|
||||||
"click",
|
"click",
|
||||||
() => handleSquareClick(squareName)
|
() => handleSquareClick(
|
||||||
|
squareName
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
elements.board.appendChild(square);
|
elements.board.appendChild(
|
||||||
|
square
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -520,22 +548,22 @@ function formatStatus() {
|
|||||||
const status = currentStatus();
|
const status = currentStatus();
|
||||||
|
|
||||||
if (status.phase === "checkmate") {
|
if (status.phase === "checkmate") {
|
||||||
return "Checkmate";
|
return "Мат";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.phase === "draw") {
|
if (status.phase === "draw") {
|
||||||
return "Draw";
|
return "Ничья";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.check) {
|
if (status.check) {
|
||||||
return appState.engine.state.turn === "w"
|
return appState.engine.state.turn === "w"
|
||||||
? "White is in check"
|
? "Белые под шахом"
|
||||||
: "Black is in check";
|
: "Чёрные под шахом";
|
||||||
}
|
}
|
||||||
|
|
||||||
return appState.engine.state.turn === "w"
|
return appState.engine.state.turn === "w"
|
||||||
? "White to move"
|
? "Ход белых"
|
||||||
: "Black to move";
|
: "Ход чёрных";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -546,7 +574,7 @@ function getGameOverMessage() {
|
|||||||
if (!snapshot) {
|
if (!snapshot) {
|
||||||
return (
|
return (
|
||||||
appState.gameOverMessage ||
|
appState.gameOverMessage ||
|
||||||
"Game over."
|
"Игра окончена."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,21 +587,21 @@ function getGameOverMessage() {
|
|||||||
switch (reason) {
|
switch (reason) {
|
||||||
case "timeout":
|
case "timeout":
|
||||||
return winner === "w"
|
return winner === "w"
|
||||||
? "White wins on time."
|
? "Белые победили по времени."
|
||||||
: "Black wins on time.";
|
: "Чёрные победили по времени.";
|
||||||
|
|
||||||
case "checkmate":
|
case "checkmate":
|
||||||
return winner === "w"
|
return winner === "w"
|
||||||
? "White wins by checkmate."
|
? "Белые победили матом."
|
||||||
: "Black wins by checkmate.";
|
: "Чёрные победили матом.";
|
||||||
|
|
||||||
case "draw":
|
case "draw":
|
||||||
return "Draw.";
|
return "Ничья.";
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
appState.gameOverMessage ||
|
appState.gameOverMessage ||
|
||||||
"Game over."
|
"Игра окончена."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -586,17 +614,17 @@ function getGameOverMessage() {
|
|||||||
function renderRoster() {
|
function renderRoster() {
|
||||||
if (appState.mode === "ai") {
|
if (appState.mode === "ai") {
|
||||||
const human =
|
const human =
|
||||||
appState.playerName || "You";
|
appState.playerName || "Вы";
|
||||||
|
|
||||||
const whiteName =
|
const whiteName =
|
||||||
appState.playerColor === "w"
|
appState.playerColor === "w"
|
||||||
? human
|
? human
|
||||||
: `Computer (${appState.aiLevel})`;
|
: `Компьютер (${appState.aiLevel})`;
|
||||||
|
|
||||||
const blackName =
|
const blackName =
|
||||||
appState.playerColor === "b"
|
appState.playerColor === "b"
|
||||||
? human
|
? human
|
||||||
: `Computer (${appState.aiLevel})`;
|
: `Компьютер (${appState.aiLevel})`;
|
||||||
|
|
||||||
if (elements.whitePlayerLabel) {
|
if (elements.whitePlayerLabel) {
|
||||||
elements.whitePlayerLabel.textContent =
|
elements.whitePlayerLabel.textContent =
|
||||||
@@ -620,7 +648,7 @@ function renderRoster() {
|
|||||||
|
|
||||||
if (elements.spectatorList) {
|
if (elements.spectatorList) {
|
||||||
elements.spectatorList.innerHTML =
|
elements.spectatorList.innerHTML =
|
||||||
"<li>None in AI mode</li>";
|
"<li>В режиме AI зрителей нет</li>";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elements.spectatorCount) {
|
if (elements.spectatorCount) {
|
||||||
@@ -642,34 +670,38 @@ function renderRoster() {
|
|||||||
? `${white.name}${
|
? `${white.name}${
|
||||||
white.connected
|
white.connected
|
||||||
? ""
|
? ""
|
||||||
: " · offline"
|
: " · офлайн"
|
||||||
}`
|
}`
|
||||||
: "Open seat";
|
: "Свободно";
|
||||||
|
|
||||||
const blackName =
|
const blackName =
|
||||||
black
|
black
|
||||||
? `${black.name}${
|
? `${black.name}${
|
||||||
black.connected
|
black.connected
|
||||||
? ""
|
? ""
|
||||||
: " · offline"
|
: " · офлайн"
|
||||||
}`
|
}`
|
||||||
: "Open seat";
|
: "Свободно";
|
||||||
|
|
||||||
elements.whitePlayerLabel &&
|
if (elements.whitePlayerLabel) {
|
||||||
(elements.whitePlayerLabel.textContent =
|
elements.whitePlayerLabel.textContent =
|
||||||
whiteName);
|
whiteName;
|
||||||
|
}
|
||||||
|
|
||||||
elements.blackPlayerLabel &&
|
if (elements.blackPlayerLabel) {
|
||||||
(elements.blackPlayerLabel.textContent =
|
elements.blackPlayerLabel.textContent =
|
||||||
blackName);
|
blackName;
|
||||||
|
}
|
||||||
|
|
||||||
elements.rosterWhite &&
|
if (elements.rosterWhite) {
|
||||||
(elements.rosterWhite.textContent =
|
elements.rosterWhite.textContent =
|
||||||
whiteName);
|
whiteName;
|
||||||
|
}
|
||||||
|
|
||||||
elements.rosterBlack &&
|
if (elements.rosterBlack) {
|
||||||
(elements.rosterBlack.textContent =
|
elements.rosterBlack.textContent =
|
||||||
blackName);
|
blackName;
|
||||||
|
}
|
||||||
|
|
||||||
const spectators =
|
const spectators =
|
||||||
appState.partySnapshot?.spectators || [];
|
appState.partySnapshot?.spectators || [];
|
||||||
@@ -685,7 +717,7 @@ function renderRoster() {
|
|||||||
|
|
||||||
if (!spectators.length) {
|
if (!spectators.length) {
|
||||||
elements.spectatorList.innerHTML =
|
elements.spectatorList.innerHTML =
|
||||||
"<li>None yet</li>";
|
"<li>Пока никого нет</li>";
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -699,13 +731,149 @@ function renderRoster() {
|
|||||||
)}${
|
)}${
|
||||||
spectator.connected
|
spectator.connected
|
||||||
? ""
|
? ""
|
||||||
: " · offline"
|
: " · офлайн"
|
||||||
}</li>`
|
}</li>`
|
||||||
)
|
)
|
||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* =========================================================
|
||||||
|
CAPTURED PIECES
|
||||||
|
========================================================= */
|
||||||
|
|
||||||
|
function renderCaptured() {
|
||||||
|
if (
|
||||||
|
!elements.capturedByWhite &&
|
||||||
|
!elements.capturedByBlack
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Поддерживаем несколько возможных форматов
|
||||||
|
* истории движка, если они присутствуют.
|
||||||
|
*/
|
||||||
|
const history =
|
||||||
|
appState.engine.state.moveHistory ||
|
||||||
|
appState.engine.state.history ||
|
||||||
|
[];
|
||||||
|
|
||||||
|
const whiteCaptured = [];
|
||||||
|
const blackCaptured = [];
|
||||||
|
|
||||||
|
for (const move of history) {
|
||||||
|
if (!move) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const captured =
|
||||||
|
move.captured ||
|
||||||
|
move.capturedPiece ||
|
||||||
|
null;
|
||||||
|
|
||||||
|
if (!captured) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const color =
|
||||||
|
captured.color ||
|
||||||
|
(captured.type
|
||||||
|
? (move.color === "w" ? "b" : "w")
|
||||||
|
: null);
|
||||||
|
|
||||||
|
const type =
|
||||||
|
captured.type ||
|
||||||
|
(
|
||||||
|
typeof captured === "string"
|
||||||
|
? captured
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const glyph =
|
||||||
|
PIECE_GLYPHS[color]?.[type];
|
||||||
|
|
||||||
|
if (!glyph) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move.color === "w") {
|
||||||
|
whiteCaptured.push(glyph);
|
||||||
|
} else {
|
||||||
|
blackCaptured.push(glyph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.capturedByWhite) {
|
||||||
|
elements.capturedByWhite.textContent =
|
||||||
|
whiteCaptured.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.capturedByBlack) {
|
||||||
|
elements.capturedByBlack.textContent =
|
||||||
|
blackCaptured.join(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* =========================================================
|
||||||
|
MOVE HISTORY
|
||||||
|
========================================================= */
|
||||||
|
|
||||||
|
function renderMoveHistory() {
|
||||||
|
if (!elements.moveHistory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const history =
|
||||||
|
appState.engine.state.moveHistory ||
|
||||||
|
appState.engine.state.history ||
|
||||||
|
[];
|
||||||
|
|
||||||
|
if (!Array.isArray(history) || !history.length) {
|
||||||
|
elements.moveHistory.innerHTML =
|
||||||
|
"<li class=\"empty-state\">Ходов пока нет</li>";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.moveHistory.innerHTML =
|
||||||
|
history
|
||||||
|
.map((move, index) => {
|
||||||
|
if (typeof move === "string") {
|
||||||
|
return `<li>${escapeHtml(move)}</li>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const from =
|
||||||
|
move?.from || "";
|
||||||
|
|
||||||
|
const to =
|
||||||
|
move?.to || "";
|
||||||
|
|
||||||
|
const promotion =
|
||||||
|
move?.promotion
|
||||||
|
? `=${String(move.promotion).toUpperCase()}`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const text =
|
||||||
|
from && to
|
||||||
|
? `${from} → ${to}${promotion}`
|
||||||
|
: JSON.stringify(move);
|
||||||
|
|
||||||
|
return (
|
||||||
|
`<li>${escapeHtml(
|
||||||
|
`${index + 1}. ${text}`
|
||||||
|
)}</li>`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* =========================================================
|
/* =========================================================
|
||||||
PARTY SUMMARY
|
PARTY SUMMARY
|
||||||
========================================================= */
|
========================================================= */
|
||||||
@@ -717,7 +885,7 @@ function renderPartySummary() {
|
|||||||
|
|
||||||
if (!appState.party) {
|
if (!appState.party) {
|
||||||
elements.partySummary.textContent =
|
elements.partySummary.textContent =
|
||||||
"Create a room to become White. The next player joins as Black. Up to 20 spectators can watch live.";
|
"Создайте комнату, чтобы играть белыми. Следующий игрок присоединится за чёрных. До 20 зрителей.";
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -727,10 +895,10 @@ function renderPartySummary() {
|
|||||||
|
|
||||||
const roleText =
|
const roleText =
|
||||||
role === "white"
|
role === "white"
|
||||||
? "playing as White"
|
? "вы играете белыми"
|
||||||
: role === "black"
|
: role === "black"
|
||||||
? "playing as Black"
|
? "вы играете чёрными"
|
||||||
: "watching as a spectator";
|
: "вы зритель";
|
||||||
|
|
||||||
const timeLabel =
|
const timeLabel =
|
||||||
appState.partySnapshot
|
appState.partySnapshot
|
||||||
@@ -738,7 +906,7 @@ function renderPartySummary() {
|
|||||||
"Без часов";
|
"Без часов";
|
||||||
|
|
||||||
elements.partySummary.textContent =
|
elements.partySummary.textContent =
|
||||||
`Party ${appState.party.code} · ${roleText}. ${timeLabel}.`;
|
`Комната ${appState.party.code} · ${roleText} · ${timeLabel}.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -782,17 +950,21 @@ function renderClocks() {
|
|||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
if (elements.whiteClock) {
|
if (elements.whiteClock) {
|
||||||
elements.whiteClock.textContent = "∞";
|
elements.whiteClock.textContent = "∞";
|
||||||
|
|
||||||
elements.whiteClock.classList.remove(
|
elements.whiteClock.classList.remove(
|
||||||
"active",
|
"active",
|
||||||
"low"
|
"low",
|
||||||
|
"danger"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elements.blackClock) {
|
if (elements.blackClock) {
|
||||||
elements.blackClock.textContent = "∞";
|
elements.blackClock.textContent = "∞";
|
||||||
|
|
||||||
elements.blackClock.classList.remove(
|
elements.blackClock.classList.remove(
|
||||||
"active",
|
"active",
|
||||||
"low"
|
"low",
|
||||||
|
"danger"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,6 +991,11 @@ function renderClocks() {
|
|||||||
"low",
|
"low",
|
||||||
white <= 10
|
white <= 10
|
||||||
);
|
);
|
||||||
|
|
||||||
|
elements.whiteClock.classList.toggle(
|
||||||
|
"danger",
|
||||||
|
white <= 5
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elements.blackClock) {
|
if (elements.blackClock) {
|
||||||
@@ -835,6 +1012,11 @@ function renderClocks() {
|
|||||||
"low",
|
"low",
|
||||||
black <= 10
|
black <= 10
|
||||||
);
|
);
|
||||||
|
|
||||||
|
elements.blackClock.classList.toggle(
|
||||||
|
"danger",
|
||||||
|
black <= 5
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -851,28 +1033,61 @@ function renderConnectionBadge() {
|
|||||||
if (appState.mode === "ai") {
|
if (appState.mode === "ai") {
|
||||||
elements.connectionBadge.textContent =
|
elements.connectionBadge.textContent =
|
||||||
appState.isAiThinking
|
appState.isAiThinking
|
||||||
? "Computer thinking"
|
? "Компьютер думает"
|
||||||
: `AI: ${appState.aiLevel}`;
|
: `AI · ${appState.aiLevel}`;
|
||||||
|
|
||||||
|
elements.connectionBadge.className =
|
||||||
|
"status-chip connection-solo";
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!appState.party) {
|
if (!appState.party) {
|
||||||
elements.connectionBadge.textContent =
|
elements.connectionBadge.textContent =
|
||||||
"Party idle";
|
"Party ожидание";
|
||||||
|
|
||||||
|
elements.connectionBadge.className =
|
||||||
|
"status-chip connection-solo";
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const role =
|
const role =
|
||||||
appState.party.role === "white"
|
appState.party.role === "white"
|
||||||
? "White"
|
? "Белые"
|
||||||
: appState.party.role === "black"
|
: appState.party.role === "black"
|
||||||
? "Black"
|
? "Чёрные"
|
||||||
: "Spectator";
|
: "Зритель";
|
||||||
|
|
||||||
elements.connectionBadge.textContent =
|
elements.connectionBadge.textContent =
|
||||||
`Party ${appState.party.code} · ${role}`;
|
`${appState.party.code} · ${role}`;
|
||||||
|
|
||||||
|
elements.connectionBadge.className =
|
||||||
|
"status-chip connection-party";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* =========================================================
|
||||||
|
STATUS BADGE
|
||||||
|
========================================================= */
|
||||||
|
|
||||||
|
function renderTurnBadge() {
|
||||||
|
if (!elements.turnBadge) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const turn =
|
||||||
|
appState.engine.state.turn;
|
||||||
|
|
||||||
|
elements.turnBadge.classList.toggle(
|
||||||
|
"turn-white",
|
||||||
|
turn === "w"
|
||||||
|
);
|
||||||
|
|
||||||
|
elements.turnBadge.classList.toggle(
|
||||||
|
"turn-black",
|
||||||
|
turn === "b"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -886,8 +1101,12 @@ function render() {
|
|||||||
renderPartySummary();
|
renderPartySummary();
|
||||||
renderClocks();
|
renderClocks();
|
||||||
renderConnectionBadge();
|
renderConnectionBadge();
|
||||||
|
renderTurnBadge();
|
||||||
|
renderCaptured();
|
||||||
|
renderMoveHistory();
|
||||||
|
|
||||||
const status = formatStatus();
|
const status =
|
||||||
|
formatStatus();
|
||||||
|
|
||||||
if (elements.turnBadge) {
|
if (elements.turnBadge) {
|
||||||
elements.turnBadge.textContent =
|
elements.turnBadge.textContent =
|
||||||
@@ -939,7 +1158,8 @@ function openPromotionDialog(moves) {
|
|||||||
|
|
||||||
button.textContent =
|
button.textContent =
|
||||||
PIECE_GLYPHS[
|
PIECE_GLYPHS[
|
||||||
move.color
|
move.color ||
|
||||||
|
appState.engine.state.turn
|
||||||
][promotion];
|
][promotion];
|
||||||
|
|
||||||
button.addEventListener(
|
button.addEventListener(
|
||||||
@@ -1013,7 +1233,7 @@ async function submitMove(move) {
|
|||||||
|
|
||||||
showToast(
|
showToast(
|
||||||
response.error ||
|
response.error ||
|
||||||
"Move rejected.",
|
"Ход отклонён.",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1039,7 +1259,7 @@ async function submitMove(move) {
|
|||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
showToast(
|
showToast(
|
||||||
result.error ||
|
result.error ||
|
||||||
"Illegal move.",
|
"Недопустимый ход.",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1084,7 +1304,10 @@ function handleSquareClick(square) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (matchingMoves.length === 1) {
|
if (matchingMoves.length === 1) {
|
||||||
submitMove(matchingMoves[0]);
|
submitMove(
|
||||||
|
matchingMoves[0]
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1092,6 +1315,7 @@ function handleSquareClick(square) {
|
|||||||
openPromotionDialog(
|
openPromotionDialog(
|
||||||
matchingMoves
|
matchingMoves
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1111,6 +1335,7 @@ function handleSquareClick(square) {
|
|||||||
: square;
|
: square;
|
||||||
|
|
||||||
renderBoard();
|
renderBoard();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1304,8 +1529,8 @@ function updateAiClock() {
|
|||||||
|
|
||||||
appState.gameOverMessage =
|
appState.gameOverMessage =
|
||||||
color === "w"
|
color === "w"
|
||||||
? "Black wins on time."
|
? "Чёрные победили по времени."
|
||||||
: "White wins on time.";
|
: "Белые победили по времени.";
|
||||||
|
|
||||||
stopAiClock();
|
stopAiClock();
|
||||||
|
|
||||||
@@ -1368,6 +1593,7 @@ function resetLocalGame() {
|
|||||||
new ChessEngine();
|
new ChessEngine();
|
||||||
|
|
||||||
appState.partySnapshot = null;
|
appState.partySnapshot = null;
|
||||||
|
appState.selectedSquare = null;
|
||||||
|
|
||||||
appState.orientation =
|
appState.orientation =
|
||||||
appState.playerColor;
|
appState.playerColor;
|
||||||
@@ -1416,7 +1642,7 @@ async function postJson(url, body) {
|
|||||||
ok: false,
|
ok: false,
|
||||||
|
|
||||||
error:
|
error:
|
||||||
`Server returned ${response.status} instead of JSON.`,
|
`Сервер вернул ${response.status} вместо JSON.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1428,7 +1654,7 @@ async function postJson(url, body) {
|
|||||||
error:
|
error:
|
||||||
error instanceof Error
|
error instanceof Error
|
||||||
? error.message
|
? error.message
|
||||||
: "Network error.",
|
: "Ошибка сети.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1475,6 +1701,8 @@ function hydratePartyState(
|
|||||||
partyPayload.game
|
partyPayload.game
|
||||||
);
|
);
|
||||||
|
|
||||||
|
appState.selectedSquare = null;
|
||||||
|
|
||||||
appState.gameOver =
|
appState.gameOver =
|
||||||
Boolean(
|
Boolean(
|
||||||
partyPayload.gameOverReason
|
partyPayload.gameOverReason
|
||||||
@@ -1581,7 +1809,7 @@ function connectPartyStream() {
|
|||||||
elements.connectionBadge
|
elements.connectionBadge
|
||||||
) {
|
) {
|
||||||
elements.connectionBadge.textContent =
|
elements.connectionBadge.textContent =
|
||||||
`Party ${code} · reconnecting`;
|
`Party ${code} · переподключение`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1624,7 +1852,7 @@ async function createParty() {
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
showToast(
|
showToast(
|
||||||
response.error ||
|
response.error ||
|
||||||
"Unable to create party.",
|
"Не удалось создать комнату.",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1644,7 +1872,7 @@ async function createParty() {
|
|||||||
connectPartyStream();
|
connectPartyStream();
|
||||||
|
|
||||||
showToast(
|
showToast(
|
||||||
`Party ${response.party.code} created.`,
|
`Комната ${response.party.code} создана.`,
|
||||||
"success"
|
"success"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1662,7 +1890,7 @@ async function joinParty(code) {
|
|||||||
|
|
||||||
if (!normalizedCode) {
|
if (!normalizedCode) {
|
||||||
showToast(
|
showToast(
|
||||||
"Enter a party code first.",
|
"Введите код комнаты.",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1674,7 +1902,7 @@ async function joinParty(code) {
|
|||||||
normalizedCode
|
normalizedCode
|
||||||
) {
|
) {
|
||||||
showToast(
|
showToast(
|
||||||
`You are already in party ${normalizedCode}.`
|
`Вы уже в комнате ${normalizedCode}.`
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -1706,7 +1934,7 @@ async function joinParty(code) {
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
showToast(
|
showToast(
|
||||||
response.error ||
|
response.error ||
|
||||||
"Unable to join party.",
|
"Не удалось войти в комнату.",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1726,7 +1954,7 @@ async function joinParty(code) {
|
|||||||
connectPartyStream();
|
connectPartyStream();
|
||||||
|
|
||||||
showToast(
|
showToast(
|
||||||
`Joined party ${response.party.code}.`,
|
`Вы вошли в комнату ${response.party.code}.`,
|
||||||
"success"
|
"success"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1774,7 +2002,7 @@ async function leaveParty(
|
|||||||
|
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
showToast(
|
showToast(
|
||||||
"Left the party room."
|
"Вы покинули комнату."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1802,12 +2030,12 @@ async function copyInviteLink() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
showToast(
|
showToast(
|
||||||
"Invite link copied.",
|
"Ссылка скопирована.",
|
||||||
"success"
|
"success"
|
||||||
);
|
);
|
||||||
} catch {
|
} catch {
|
||||||
showToast(
|
showToast(
|
||||||
`Party code: ${appState.party.code}`
|
`Код комнаты: ${appState.party.code}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1960,7 +2188,28 @@ function bindEvents() {
|
|||||||
resetLocalGame();
|
resetLocalGame();
|
||||||
|
|
||||||
showToast(
|
showToast(
|
||||||
"New AI game ready.",
|
"Новая партия с ИИ готова.",
|
||||||
|
"success"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
elements.newGameToolbarButton?.addEventListener(
|
||||||
|
"click",
|
||||||
|
async () => {
|
||||||
|
if (appState.mode === "party") {
|
||||||
|
showToast(
|
||||||
|
"Для новой партии создайте новую комнату."
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetLocalGame();
|
||||||
|
|
||||||
|
showToast(
|
||||||
|
"Новая партия.",
|
||||||
"success"
|
"success"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1973,6 +2222,12 @@ function bindEvents() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
elements.newPartyGameButton?.addEventListener(
|
||||||
|
"click",
|
||||||
|
createParty
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
elements.joinPartyButton?.addEventListener(
|
elements.joinPartyButton?.addEventListener(
|
||||||
"click",
|
"click",
|
||||||
() =>
|
() =>
|
||||||
@@ -2113,5 +2368,3 @@ async function init() {
|
|||||||
========================================================= */
|
========================================================= */
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user