Commit 1c68160e authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat: Chess.com-style smart tap-to-move controls for backgammon

Smart behaviors:
- Auto-roll dice at start of each turn
- If only one move exists for a piece, execute immediately on tap
- If only one complete sequence possible, auto-execute entire turn
- Auto-end turn when no moves remain
- Movable pieces highlighted with golden glow (tap targets)
- Tap piece → valid destinations highlighted → tap destination → done
- Undo button for unlimited undo before turn ends

UX flow:
1. Dice auto-roll
2. Movable sources highlighted
3. Tap source → if single destination, auto-move
4. If multiple destinations → show targets → tap one
5. Repeat until dice exhausted
6. Turn auto-ends

Minimal taps, zero rules knowledge needed, fast animations.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 48a4a508
...@@ -30,10 +30,11 @@ ...@@ -30,10 +30,11 @@
<!-- Side panel --> <!-- Side panel -->
<div class="bg-side-panel"> <div class="bg-side-panel">
<div id="bg-turn" class="bg-turn-indicator"></div>
<div id="bg-dice-container" class="bg-dice-area"></div> <div id="bg-dice-container" class="bg-dice-area"></div>
<div id="bg-status" class="bg-status"></div>
<div class="bg-controls"> <div class="bg-controls">
<button id="bg-roll-btn" class="bg-roll-btn">ارمِ النرد</button> <button id="bg-roll-btn" class="bg-roll-btn">ارمِ النرد</button>
<button id="bg-undo-btn" class="bg-pass-btn" style="display:none;">تراجع</button>
</div> </div>
<div id="bg-log" class="bg-log"></div> <div id="bg-log" class="bg-log"></div>
</div> </div>
......
...@@ -176,12 +176,14 @@ ...@@ -176,12 +176,14 @@
border-radius: 4px; border-radius: 4px;
} }
.bg-point--clickable { .bg-point--source {
cursor: pointer; cursor: pointer;
} }
.bg-point--clickable .bg-checker:last-child { .bg-point--source .bg-checker:last-child,
box-shadow: 0 0 0 2px rgba(255,200,50,0.4), 0 2px 4px rgba(0,0,0,0.3); .bg-point--source .bg-checkers-stack .bg-checker:last-child {
box-shadow: 0 0 0 3px rgba(255,200,50,0.5), 0 0 10px rgba(255,200,50,0.3);
transform: scale(1.05);
} }
.bg-point--hover-preview { .bg-point--hover-preview {
......
...@@ -6,6 +6,7 @@ var BackgammonGame = (function() { ...@@ -6,6 +6,7 @@ var BackgammonGame = (function() {
var Bot = BackgammonBot; var Bot = BackgammonBot;
var state = null; var state = null;
var undoStack = [];
function init(opts) { function init(opts) {
state = { state = {
...@@ -58,14 +59,15 @@ var BackgammonGame = (function() { ...@@ -58,14 +59,15 @@ var BackgammonGame = (function() {
UI.render(state); UI.render(state);
if (state.players[state.currentTurn].type === 'bot') { if (state.players[state.currentTurn].type === 'bot') {
UI.setStatus('انتظر... البوت يفكر'); UI.setStatus('انتظر...');
setTimeout(function() { executeBotTurn(); }, 800); setTimeout(function() { executeBotTurn(); }, 600);
} else { } else {
// Human's turn - show helpful status beginHumanTurn();
UI.setStatus('دورك — حرّك قطعك بالنرد ' + d1 + ' و ' + d2);
} }
} }
// ─── Auto-roll at start of turn ────────────────────────────────────────────
function rollDice() { function rollDice() {
if (state.phase !== 'awaiting_roll') return; if (state.phase !== 'awaiting_roll') return;
if (state.players[state.currentTurn].type !== 'human') return; if (state.players[state.currentTurn].type !== 'human') return;
...@@ -75,273 +77,330 @@ var BackgammonGame = (function() { ...@@ -75,273 +77,330 @@ var BackgammonGame = (function() {
state.dice = [d1, d2]; state.dice = [d1, d2];
state.diceRemaining = C.getDiceRemaining(d1, d2); state.diceRemaining = C.getDiceRemaining(d1, d2);
state.phase = 'moving'; state.phase = 'moving';
undoStack = [];
UI.showLog(state.players[state.currentTurn].name + ' رمى ' + d1 + ' و ' + d2); UI.showLog(state.players[state.currentTurn].name + ' رمى ' + d1 + ' و ' + d2);
UI.render(state); UI.render(state);
beginHumanTurn();
}
// ─── Smart turn start ──────────────────────────────────────────────────────
function beginHumanTurn() {
if (!C.canMove(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining)) { if (!C.canMove(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining)) {
UI.setStatus('لا توجد حركة — ينتقل الدور'); UI.setStatus('لا توجد حركة متاحة');
UI.showLog('لا توجد حركة متاحة — تخطي الدور'); UI.showLog('لا توجد حركة — تخطي');
setTimeout(function() { endTurn(); }, 1200); setTimeout(function() { endTurn(); }, 1000);
} else { return;
UI.setStatus('اختر قطعة لتحريكها');
} }
// Check if only one complete sequence exists — auto-execute with confirmation
var allSeqs = C.getAllMovesForDice(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining);
if (allSeqs.length === 1 && allSeqs[0].length === state.diceRemaining.length) {
UI.setStatus('حركة واحدة ممكنة — جاري التنفيذ...');
autoExecuteSequence(allSeqs[0]);
return;
} }
function handlePointClick(pointOrAction) { UI.setStatus('اختر قطعة');
if (state.phase !== 'moving') return; showMovableSources();
if (state.players[state.currentTurn].type !== 'human') return; }
if (pointOrAction === 'roll') { // ─── Show which pieces can move ───────────────────────────────────────────
rollDice();
return; function showMovableSources() {
var sources = getMovableSources();
UI.highlightSources(sources);
} }
// Pass/skip action function getMovableSources() {
if (pointOrAction === 'pass') { var sources = [];
if (state.diceRemaining.length > 0 && var player = state.currentTurn;
!C.canMove(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining)) {
UI.setStatus('لا توجد حركة — ينتقل الدور'); // Bar first
UI.showLog('تخطي الدور — لا توجد حركة'); if (state.bar[player] > 0) {
UI.clearSelected(); return ['bar'];
UI.clearHighlights(); }
setTimeout(function() { endTurn(); }, 600);
for (var i = 0; i < 24; i++) {
var count = state.board[i];
var isOwn = (player === 0) ? count > 0 : count < 0;
if (isOwn) {
var moves = getValidMovesForFrom(i);
if (moves.length > 0) sources.push(i);
} }
return;
} }
return sources;
}
// ─── Main click handler ────────────────────────────────────────────────────
function handlePointClick(pointOrAction) {
if (pointOrAction === 'roll') { rollDice(); return; }
if (pointOrAction === 'undo') { undoMove(); return; }
if (state.phase !== 'moving') return;
if (state.players[state.currentTurn].type !== 'human') return;
var selected = UI.getSelected(); var selected = UI.getSelected();
// CASE 1: Nothing selected yet — select a source // Nothing selected — try to select source
if (selected === null) { if (selected === null) {
selectSource(pointOrAction); selectSource(pointOrAction);
return; return;
} }
// CASE 2: Something is already selected // Same point clicked — deselect
var from = selected; if (isSamePoint(selected, pointOrAction)) {
deselect();
// Check if user clicked the same point (deselect)
if (isSamePoint(from, pointOrAction)) {
UI.clearSelected();
UI.clearHighlights();
UI.setStatus('اختر قطعة لتحريكها');
UI.render(state);
return; return;
} }
// Determine the target // Try as destination
var to = parseTarget(pointOrAction); var to = parseTarget(pointOrAction);
var validMoves = getValidMovesForFrom(selected);
// Check if target is a valid destination
var validMoves = getValidMovesForFrom(from);
var validMove = null; var validMove = null;
for (var i = 0; i < validMoves.length; i++) { for (var i = 0; i < validMoves.length; i++) {
if (validMoves[i].to === to) { if (validMoves[i].to === to) { validMove = validMoves[i]; break; }
validMove = validMoves[i];
break;
}
} }
if (validMove) { if (validMove) {
// Execute the valid move
UI.animateMove(from, to);
UI.clearSelected();
UI.clearHighlights();
executeMove(validMove); executeMove(validMove);
} else { } else if (isOwnPiece(pointOrAction)) {
// Not a valid destination — check if it's the user's own piece (swap selection) // Clicked another own piece — swap selection
if (isOwnPiece(pointOrAction)) { deselect();
UI.clearSelected();
UI.clearHighlights();
selectSource(pointOrAction); selectSource(pointOrAction);
} else {
// Invalid click — flash feedback, keep selection
UI.setStatus('وجهة غير صالحة — اختر مكان آخر');
setTimeout(function() {
if (UI.getSelected() !== null) {
UI.setStatus('اختر وجهة');
}
}, 1500);
}
} }
} }
function isSamePoint(selected, pointOrAction) { // ─── Source selection ──────────────────────────────────────────────────────
if (selected === 'bar' && pointOrAction === 'bar') return true;
if (pointOrAction === 'bar' || pointOrAction === 'off' || pointOrAction === 'pass') return false;
var parsed = parseInt(pointOrAction);
return selected === parsed;
}
function parseTarget(pointOrAction) { function selectSource(pointOrAction) {
if (pointOrAction === 'off') return 'off'; var player = state.currentTurn;
if (pointOrAction === 'bar') return 'bar';
var parsed = parseInt(pointOrAction);
if (isNaN(parsed)) return pointOrAction;
return parsed;
}
function isOwnPiece(pointOrAction) { // Must enter from bar first
if (pointOrAction === 'bar') { if (state.bar[player] > 0) {
return state.bar[state.currentTurn] > 0; if (pointOrAction !== 'bar') {
} UI.setStatus('أدخل قطعتك من البار أولاً');
if (pointOrAction === 'off') return false; return;
var point = parseInt(pointOrAction);
if (isNaN(point)) return false;
var count = state.board[point];
return (state.currentTurn === 0) ? count > 0 : count < 0;
} }
var moves = getValidMovesForFrom('bar');
if (moves.length === 0) return;
function selectSource(pointOrAction) { // Auto-move if only one destination
// If player has checkers on bar, they MUST enter from bar first if (moves.length === 1) {
if (state.bar[state.currentTurn] > 0) {
if (pointOrAction === 'bar') {
UI.setSelected('bar'); UI.setSelected('bar');
highlightValidTargets('bar'); setTimeout(function() { executeMove(moves[0]); }, 150);
UI.setStatus('اختر وجهة الدخول من البار');
} else {
UI.setStatus('يجب إدخال القطع من البار أولاً');
}
return; return;
} }
if (pointOrAction === 'bar') return; // No checkers on bar UI.setSelected('bar');
UI.highlightTargets(moves.map(function(m) { return m.to; }));
UI.setStatus('اختر وجهة');
return;
}
var point = parseInt(pointOrAction); var point = parseInt(pointOrAction);
if (isNaN(point)) return; if (isNaN(point)) return;
var count = state.board[point]; var count = state.board[point];
var isOwn = (state.currentTurn === 0) ? count > 0 : count < 0; var isOwn = (player === 0) ? count > 0 : count < 0;
if (!isOwn) return;
if (isOwn) {
// Check if this piece actually has valid moves
var moves = getValidMovesForFrom(point); var moves = getValidMovesForFrom(point);
if (moves.length > 0) { if (moves.length === 0) return;
// Auto-move if only one destination
if (moves.length === 1) {
UI.setSelected(point); UI.setSelected(point);
highlightValidTargets(point); UI.render(state);
UI.setStatus('اختر وجهة'); setTimeout(function() { executeMove(moves[0]); }, 150);
} else { return;
UI.setStatus('هذه القطعة لا تملك حركة — اختر قطعة أخرى');
}
} else {
UI.setStatus('اختر قطعة من قطعك');
}
} }
function getValidMovesForFrom(from) { UI.setSelected(point);
var allMoves = []; UI.highlightTargets(moves.map(function(m) { return m.to; }));
for (var i = 0; i < state.diceRemaining.length; i++) { UI.setStatus('اختر وجهة');
var die = state.diceRemaining[i];
var moves = C.getValidMoves(state.board, state.bar, state.borneOff, state.currentTurn, die);
for (var j = 0; j < moves.length; j++) {
if (moves[j].from === from) {
// Include dieUsed for proper deduplication
var move = {
from: moves[j].from,
to: moves[j].to,
hit: moves[j].hit,
dieUsed: die
};
// Deduplicate by destination (keep first occurrence)
var isDup = false;
for (var k = 0; k < allMoves.length; k++) {
if (allMoves[k].to === move.to) { isDup = true; break; }
}
if (!isDup) allMoves.push(move);
}
}
}
return allMoves;
} }
function highlightValidTargets(from) { // ─── Execute move ──────────────────────────────────────────────────────────
var moves = getValidMovesForFrom(from);
var targets = [];
for (var i = 0; i < moves.length; i++) {
targets.push(moves[i].to);
}
UI.highlightPoints(targets);
}
function executeMove(move) { function executeMove(move) {
// Save undo state
undoStack.push({
board: state.board.slice(),
bar: state.bar.slice(),
borneOff: state.borneOff.slice(),
diceRemaining: state.diceRemaining.slice()
});
var result = C.applyMove(state.board, state.bar, state.borneOff, state.currentTurn, move); var result = C.applyMove(state.board, state.bar, state.borneOff, state.currentTurn, move);
state.board = result.board; state.board = result.board;
state.bar = result.bar; state.bar = result.bar;
state.borneOff = result.borneOff; state.borneOff = result.borneOff;
var dieUsed = move.dieUsed; // Remove used die
var idx = state.diceRemaining.indexOf(dieUsed); var idx = state.diceRemaining.indexOf(move.dieUsed);
if (idx !== -1) state.diceRemaining.splice(idx, 1); if (idx !== -1) state.diceRemaining.splice(idx, 1);
state.moves.push({ state.moves.push({ player: state.currentTurn, from: move.from, to: move.to, die: move.dieUsed, hit: move.hit });
player: state.currentTurn,
from: move.from,
to: move.to,
die: dieUsed,
hit: move.hit
});
if (move.hit) { if (move.hit) UI.showLog('ضرب!');
UI.showLog(state.players[state.currentTurn].name + ' ضرب قطعة!');
} UI.clearSelected();
UI.clearHighlights();
UI.render(state);
// Check win
if (C.checkWin(state.borneOff, state.currentTurn)) { if (C.checkWin(state.borneOff, state.currentTurn)) {
handleWin(); handleWin();
return; return;
} }
// Check if more moves available
if (state.diceRemaining.length === 0) { if (state.diceRemaining.length === 0) {
UI.setStatus('انتهت حركاتك'); setTimeout(function() { endTurn(); }, 400);
UI.render(state); return;
setTimeout(function() { endTurn(); }, 600); }
} else if (!C.canMove(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining)) {
// Auto-pass with remaining dice if (!C.canMove(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining)) {
UI.setStatus('لا توجد حركة — ينتقل الدور'); UI.setStatus('لا توجد حركة أخرى');
UI.showLog('لا توجد حركة متاحة بالنرد المتبقي'); setTimeout(function() { endTurn(); }, 800);
return;
}
// Continue — show next movable sources
beginHumanTurn();
}
// ─── Auto-execute single sequence ─────────────────────────────────────────
function autoExecuteSequence(moves) {
var i = 0;
function next() {
if (i >= moves.length) {
setTimeout(function() { endTurn(); }, 300);
return;
}
var move = moves[i];
var result = C.applyMove(state.board, state.bar, state.borneOff, state.currentTurn, move);
state.board = result.board;
state.bar = result.bar;
state.borneOff = result.borneOff;
var dIdx = state.diceRemaining.indexOf(move.dieUsed);
if (dIdx !== -1) state.diceRemaining.splice(dIdx, 1);
state.moves.push({ player: state.currentTurn, from: move.from, to: move.to, die: move.dieUsed, hit: move.hit });
UI.render(state); UI.render(state);
setTimeout(function() { endTurn(); }, 1200); i++;
} else { if (C.checkWin(state.borneOff, state.currentTurn)) { handleWin(); return; }
// More moves available setTimeout(next, 200);
UI.setStatus('اختر قطعة لتحريكها — المتبقي: ' + state.diceRemaining.join('، ')); }
next();
}
// ─── Undo ──────────────────────────────────────────────────────────────────
function undoMove() {
if (undoStack.length === 0) return;
var prev = undoStack.pop();
state.board = prev.board;
state.bar = prev.bar;
state.borneOff = prev.borneOff;
state.diceRemaining = prev.diceRemaining;
state.moves.pop();
UI.clearSelected();
UI.clearHighlights();
UI.render(state); UI.render(state);
beginHumanTurn();
} }
// ─── Helpers ───────────────────────────────────────────────────────────────
function deselect() {
UI.clearSelected();
UI.clearHighlights();
UI.setStatus('اختر قطعة');
showMovableSources();
} }
function isSamePoint(a, b) {
if (a === 'bar' && b === 'bar') return true;
return parseInt(a) === parseInt(b);
}
function parseTarget(val) {
if (val === 'off') return 'off';
if (val === 'bar') return 'bar';
var n = parseInt(val);
return isNaN(n) ? val : n;
}
function isOwnPiece(val) {
if (val === 'bar') return state.bar[state.currentTurn] > 0;
if (val === 'off') return false;
var p = parseInt(val);
if (isNaN(p)) return false;
var count = state.board[p];
return (state.currentTurn === 0) ? count > 0 : count < 0;
}
function getValidMovesForFrom(from) {
var allMoves = [];
for (var i = 0; i < state.diceRemaining.length; i++) {
var die = state.diceRemaining[i];
var moves = C.getValidMoves(state.board, state.bar, state.borneOff, state.currentTurn, die);
for (var j = 0; j < moves.length; j++) {
if (moves[j].from === from) {
var move = { from: moves[j].from, to: moves[j].to, hit: moves[j].hit, dieUsed: die };
var isDup = false;
for (var k = 0; k < allMoves.length; k++) {
if (allMoves[k].to === move.to && allMoves[k].dieUsed === move.dieUsed) { isDup = true; break; }
}
if (!isDup) allMoves.push(move);
}
}
}
return allMoves;
}
// ─── End turn ──────────────────────────────────────────────────────────────
function endTurn() { function endTurn() {
undoStack = [];
state.currentTurn = 1 - state.currentTurn; state.currentTurn = 1 - state.currentTurn;
state.phase = 'awaiting_roll';
state.dice = null; state.dice = null;
state.diceRemaining = []; state.diceRemaining = [];
state.phase = 'awaiting_roll';
UI.clearSelected(); UI.clearSelected();
UI.clearHighlights(); UI.clearHighlights();
UI.render(state);
if (state.players[state.currentTurn].type === 'bot') { if (state.players[state.currentTurn].type === 'bot') {
UI.setStatus('انتظر... البوت يفكر'); UI.setStatus('انتظر...');
UI.render(state); setTimeout(function() { executeBotTurn(); }, 600);
setTimeout(function() { executeBotTurn(); }, 800);
} else { } else {
UI.setStatus('دورك — ارمِ النرد'); // Auto-roll for human
UI.render(state); UI.setStatus('');
setTimeout(function() { rollDice(); }, 400);
} }
} }
// ─── Bot turn ──────────────────────────────────────────────────────────────
function executeBotTurn() { function executeBotTurn() {
if (state.phase === 'awaiting_roll') { // Roll for bot
var d1 = Math.ceil(Math.random() * 6); var d1 = Math.ceil(Math.random() * 6);
var d2 = Math.ceil(Math.random() * 6); var d2 = Math.ceil(Math.random() * 6);
state.dice = [d1, d2]; state.dice = [d1, d2];
state.diceRemaining = C.getDiceRemaining(d1, d2); state.diceRemaining = C.getDiceRemaining(d1, d2);
state.phase = 'moving'; state.phase = 'moving';
UI.showLog(state.players[state.currentTurn].name + ' رمى ' + d1 + ' و ' + d2);
UI.setStatus('البوت يفكر...'); UI.showLog(state.players[state.currentTurn].name + ': ' + d1 + '، ' + d2);
UI.render(state); UI.render(state);
}
if (!C.canMove(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining)) { if (!C.canMove(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining)) {
UI.showLog(state.players[state.currentTurn].name + ' — لا توجد حركة'); UI.showLog(state.players[state.currentTurn].name + ' — لا حركة');
UI.setStatus('البوت لا يستطيع الحركة');
setTimeout(function() { endTurn(); }, 800); setTimeout(function() { endTurn(); }, 800);
return; return;
} }
...@@ -350,7 +409,7 @@ var BackgammonGame = (function() { ...@@ -350,7 +409,7 @@ var BackgammonGame = (function() {
var moves = Bot.chooseMove(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining, difficulty); var moves = Bot.chooseMove(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining, difficulty);
if (!moves || moves.length === 0) { if (!moves || moves.length === 0) {
setTimeout(function() { endTurn(); }, 800); setTimeout(function() { endTurn(); }, 600);
return; return;
} }
...@@ -359,7 +418,7 @@ var BackgammonGame = (function() { ...@@ -359,7 +418,7 @@ var BackgammonGame = (function() {
function executeBotMoves(moves, idx) { function executeBotMoves(moves, idx) {
if (idx >= moves.length) { if (idx >= moves.length) {
setTimeout(function() { endTurn(); }, 600); setTimeout(function() { endTurn(); }, 400);
return; return;
} }
...@@ -372,23 +431,8 @@ var BackgammonGame = (function() { ...@@ -372,23 +431,8 @@ var BackgammonGame = (function() {
var dieIdx = state.diceRemaining.indexOf(move.dieUsed); var dieIdx = state.diceRemaining.indexOf(move.dieUsed);
if (dieIdx !== -1) state.diceRemaining.splice(dieIdx, 1); if (dieIdx !== -1) state.diceRemaining.splice(dieIdx, 1);
state.moves.push({ state.moves.push({ player: state.currentTurn, from: move.from, to: move.to, die: move.dieUsed, hit: move.hit });
player: state.currentTurn,
from: move.from,
to: move.to,
die: move.dieUsed,
hit: move.hit
});
// Log each bot move individually
var fromLabel = (move.from === 'bar') ? 'البار' : (move.from + 1);
var toLabel = (move.to === 'off') ? 'خارج' : (move.to + 1);
UI.showLog(state.players[state.currentTurn].name + ': ' + fromLabel + ' → ' + toLabel + ' (' + move.dieUsed + ')');
if (move.hit) UI.showLog(state.players[state.currentTurn].name + ' ضرب قطعة!');
UI.setStatus('البوت يتحرك... (' + (idx + 1) + '/' + moves.length + ')');
UI.animateMove(move.from, move.to);
UI.render(state); UI.render(state);
if (C.checkWin(state.borneOff, state.currentTurn)) { if (C.checkWin(state.borneOff, state.currentTurn)) {
...@@ -396,9 +440,11 @@ var BackgammonGame = (function() { ...@@ -396,9 +440,11 @@ var BackgammonGame = (function() {
return; return;
} }
setTimeout(function() { executeBotMoves(moves, idx + 1); }, 500); setTimeout(function() { executeBotMoves(moves, idx + 1); }, 300);
} }
// ─── Win ───────────────────────────────────────────────────────────────────
function handleWin() { function handleWin() {
var winner = state.currentTurn; var winner = state.currentTurn;
var winType = C.getWinType(state.board, state.bar, state.borneOff, winner); var winType = C.getWinType(state.board, state.bar, state.borneOff, winner);
...@@ -409,8 +455,7 @@ var BackgammonGame = (function() { ...@@ -409,8 +455,7 @@ var BackgammonGame = (function() {
state.phase = 'game_over'; state.phase = 'game_over';
var winLabel = (winType === 'backgammon') ? 'باكغمّون!' : (winType === 'gammon') ? 'غمّون!' : 'فوز!'; var winLabel = (winType === 'backgammon') ? 'باكغمّون!' : (winType === 'gammon') ? 'غمّون!' : 'فوز!';
UI.showLog(state.players[winner].name + ' — ' + winLabel + ' (+' + points + ')'); UI.showLog(state.players[winner].name + ' — ' + winLabel);
UI.setStatus(state.players[winner].name + ' فاز!');
UI.showResult(state.players[winner].name, winType, points); UI.showResult(state.players[winner].name, winType, points);
UI.render(state); UI.render(state);
...@@ -419,9 +464,7 @@ var BackgammonGame = (function() { ...@@ -419,9 +464,7 @@ var BackgammonGame = (function() {
} }
} }
function getState() { function getState() { return state; }
return state;
}
return { return {
init: init, init: init,
......
...@@ -3,139 +3,70 @@ var BackgammonUI = (function() { ...@@ -3,139 +3,70 @@ var BackgammonUI = (function() {
var C = BackgammonConstants; var C = BackgammonConstants;
var selected = null; var selected = null;
var highlights = [];
var gameState = null; var gameState = null;
var statusText = '';
var movablePoints = [];
function init(state) { function init(state) {
gameState = state; gameState = state;
var board = document.getElementById('bg-board'); var board = document.getElementById('bg-board');
if (!board) return; if (!board) return;
board.innerHTML = buildBoardHTML(); board.innerHTML = buildBoardHTML();
// Ensure status element exists
ensureStatusElement();
bindEvents(); bindEvents();
} }
function ensureStatusElement() {
var existing = document.getElementById('bg-status');
if (!existing) {
var diceContainer = document.getElementById('bg-dice-container');
if (diceContainer && diceContainer.parentNode) {
var statusEl = document.createElement('div');
statusEl.id = 'bg-status';
statusEl.className = 'bg-status';
diceContainer.parentNode.insertBefore(statusEl, diceContainer.nextSibling);
}
}
}
function buildBoardHTML() { function buildBoardHTML() {
var html = ''; var html = '<div class="bg-board-inner">';
html += '<div class="bg-board-inner">';
html += '<div class="bg-half bg-half--top">'; html += '<div class="bg-half bg-half--top">';
html += '<div class="bg-quadrant bg-quadrant--top-left">'; html += '<div class="bg-quadrant bg-quadrant--top-left">';
for (var i = 12; i <= 17; i++) { for (var i = 12; i <= 17; i++) html += '<div class="bg-point bg-point--top" data-point="' + i + '"></div>';
html += '<div class="bg-point bg-point--top" data-point="' + i + '"></div>';
}
html += '</div>'; html += '</div>';
html += '<div class="bg-bar-area bg-bar--top" data-action="bar"></div>'; html += '<div class="bg-bar-area bg-bar--top" data-action="bar"></div>';
html += '<div class="bg-quadrant bg-quadrant--top-right">'; html += '<div class="bg-quadrant bg-quadrant--top-right">';
for (var i = 18; i <= 23; i++) { for (var i = 18; i <= 23; i++) html += '<div class="bg-point bg-point--top" data-point="' + i + '"></div>';
html += '<div class="bg-point bg-point--top" data-point="' + i + '"></div>'; html += '</div></div>';
}
html += '</div>';
html += '</div>';
html += '<div class="bg-half bg-half--bottom">'; html += '<div class="bg-half bg-half--bottom">';
html += '<div class="bg-quadrant bg-quadrant--bottom-left">'; html += '<div class="bg-quadrant bg-quadrant--bottom-left">';
for (var i = 11; i >= 6; i--) { for (var i = 11; i >= 6; i--) html += '<div class="bg-point bg-point--bottom" data-point="' + i + '"></div>';
html += '<div class="bg-point bg-point--bottom" data-point="' + i + '"></div>';
}
html += '</div>'; html += '</div>';
html += '<div class="bg-bar-area bg-bar--bottom" data-action="bar"></div>'; html += '<div class="bg-bar-area bg-bar--bottom" data-action="bar"></div>';
html += '<div class="bg-quadrant bg-quadrant--bottom-right">'; html += '<div class="bg-quadrant bg-quadrant--bottom-right">';
for (var i = 5; i >= 0; i--) { for (var i = 5; i >= 0; i--) html += '<div class="bg-point bg-point--bottom" data-point="' + i + '"></div>';
html += '<div class="bg-point bg-point--bottom" data-point="' + i + '"></div>'; html += '</div></div></div>';
}
html += '</div>';
html += '</div>';
html += '</div>';
html += '<div class="bg-borne-off-area">'; html += '<div class="bg-borne-off-area">';
html += '<div class="bg-borne-off bg-borne-off--black" data-action="off"></div>'; html += '<div class="bg-borne-off bg-borne-off--black" data-action="off"></div>';
html += '<div class="bg-borne-off bg-borne-off--white" data-action="off"></div>'; html += '<div class="bg-borne-off bg-borne-off--white" data-action="off"></div>';
html += '</div>'; html += '</div>';
return html; return html;
} }
function bindEvents() { function bindEvents() {
var board = document.getElementById('bg-board'); var board = document.getElementById('bg-board');
if (!board) return; if (!board) return;
board.addEventListener('click', function(e) { board.addEventListener('click', function(e) {
var point = e.target.closest('[data-point]'); var point = e.target.closest('[data-point]');
var action = e.target.closest('[data-action]'); var action = e.target.closest('[data-action]');
if (point) BackgammonGame.handlePointClick(point.dataset.point);
// Tap feedback else if (action) BackgammonGame.handlePointClick(action.dataset.action);
var clickTarget = point || action;
if (clickTarget) {
clickTarget.classList.add('bg-tap-flash');
setTimeout(function() {
clickTarget.classList.remove('bg-tap-flash');
}, 200);
}
if (point) {
BackgammonGame.handlePointClick(point.dataset.point);
} else if (action) {
BackgammonGame.handlePointClick(action.dataset.action);
}
});
// Hover preview for valid targets
board.addEventListener('mouseover', function(e) {
if (selected === null) return;
var point = e.target.closest('[data-point]');
var action = e.target.closest('[data-action]');
var el = point || action;
if (el && el.classList.contains('bg-point--valid')) {
el.classList.add('bg-point--hover-preview');
}
});
board.addEventListener('mouseout', function(e) {
var point = e.target.closest('[data-point]');
var action = e.target.closest('[data-action]');
var el = point || action;
if (el) {
el.classList.remove('bg-point--hover-preview');
}
}); });
var rollBtn = document.getElementById('bg-roll-btn'); var rollBtn = document.getElementById('bg-roll-btn');
if (rollBtn) { if (rollBtn) rollBtn.addEventListener('click', function() { BackgammonGame.rollDice(); });
rollBtn.addEventListener('click', function() {
BackgammonGame.rollDice(); var undoBtn = document.getElementById('bg-undo-btn');
}); if (undoBtn) undoBtn.addEventListener('click', function() { BackgammonGame.handlePointClick('undo'); });
}
} }
// ─── Render ────────────────────────────────────────────────────────────────
function render(state) { function render(state) {
gameState = state; gameState = state;
renderCheckers(state); renderCheckers(state);
renderBar(state); renderBar(state);
renderBorneOff(state); renderBorneOff(state);
renderDice(state); renderDice(state);
renderTurnIndicator(state);
renderControls(state); renderControls(state);
renderPipCount(state); renderPipCount(state);
renderStatus();
renderMovableCheckers(state);
renderActivePlayerHighlight(state);
} }
function renderCheckers(state) { function renderCheckers(state) {
...@@ -146,29 +77,24 @@ var BackgammonUI = (function() { ...@@ -146,29 +77,24 @@ var BackgammonUI = (function() {
var absCount = Math.abs(count); var absCount = Math.abs(count);
var player = (count > 0) ? 0 : (count < 0) ? 1 : -1; var player = (count > 0) ? 0 : (count < 0) ? 1 : -1;
var checkersHTML = ''; var html = '';
if (absCount > 0) { if (absCount > 0) {
var display = Math.min(absCount, 5); var display = Math.min(absCount, 5);
var compact = absCount > 3 ? ' bg-checkers-stack--compact' : ''; var compact = absCount > 3 ? ' bg-checkers-stack--compact' : '';
checkersHTML = '<div class="bg-checkers-stack' + compact + '">'; html = '<div class="bg-checkers-stack' + compact + '">';
for (var c = 0; c < display; c++) { for (var c = 0; c < display; c++) {
var cls = 'bg-checker bg-checker--' + (player === 0 ? 'white' : 'black'); var cls = 'bg-checker bg-checker--' + (player === 0 ? 'white' : 'black');
if (selected === idx && c === display - 1) cls += ' bg-checker--selected'; if (selected === idx && c === display - 1) cls += ' bg-checker--selected';
if (movablePoints.indexOf(idx) !== -1 && c === display - 1) cls += ' bg-checker--movable'; html += '<div class="' + cls + '"></div>';
checkersHTML += '<div class="' + cls + '"></div>';
}
checkersHTML += '</div>';
if (absCount > 5) {
checkersHTML += '<span class="bg-checker-count-badge">' + absCount + '</span>';
} }
html += '</div>';
if (absCount > 5) html += '<span class="bg-checker-count-badge">' + absCount + '</span>';
} }
el.innerHTML = html;
el.innerHTML = checkersHTML; // Clear all state classes
el.classList.remove('bg-point--valid', 'bg-point--selected', 'bg-point--source');
el.classList.remove('bg-point--valid', 'bg-point--selected', 'bg-point--clickable');
if (selected === idx) el.classList.add('bg-point--selected'); if (selected === idx) el.classList.add('bg-point--selected');
if (highlights.indexOf(idx) !== -1) el.classList.add('bg-point--valid');
if (movablePoints.indexOf(idx) !== -1) el.classList.add('bg-point--clickable');
}); });
} }
...@@ -177,29 +103,18 @@ var BackgammonUI = (function() { ...@@ -177,29 +103,18 @@ var BackgammonUI = (function() {
var barBottom = document.querySelector('.bg-bar--bottom'); var barBottom = document.querySelector('.bg-bar--bottom');
if (!barTop || !barBottom) return; if (!barTop || !barBottom) return;
var blackBar = state.bar[1]; barTop.innerHTML = buildBarHTML(state.bar[1], 'black');
var whiteBar = state.bar[0]; barBottom.innerHTML = buildBarHTML(state.bar[0], 'white');
barTop.innerHTML = buildBarCheckers(blackBar, 'black', selected === 'bar' && state.currentTurn === 1); barBottom.classList.toggle('bg-point--source', selected === 'bar');
barBottom.innerHTML = buildBarCheckers(whiteBar, 'white', selected === 'bar' && state.currentTurn === 0);
// Mark bar as clickable if player has checkers on bar
barTop.classList.toggle('bg-point--clickable', state.currentTurn === 1 && blackBar > 0 && state.phase === 'moving');
barBottom.classList.toggle('bg-point--clickable', state.currentTurn === 0 && whiteBar > 0 && state.phase === 'moving');
barTop.classList.toggle('bg-point--selected', selected === 'bar' && state.currentTurn === 1);
barBottom.classList.toggle('bg-point--selected', selected === 'bar' && state.currentTurn === 0);
} }
function buildBarCheckers(count, color, isSelected) { function buildBarHTML(count, color) {
var html = ''; var html = '';
var display = Math.min(count, 4); for (var i = 0; i < Math.min(count, 4); i++) {
for (var i = 0; i < display; i++) { html += '<div class="bg-checker bg-checker--' + color + '"></div>';
var cls = 'bg-checker bg-checker--' + color;
if (isSelected && i === display - 1) cls += ' bg-checker--selected';
if (isSelected) cls += ' bg-checker--movable';
html += '<div class="' + cls + '"></div>';
} }
if (count > 4) html += '<span class="bg-checker-count">' + count + '</span>'; if (count > 4) html += '<span class="bg-checker-count-badge">' + count + '</span>';
return html; return html;
} }
...@@ -207,137 +122,57 @@ var BackgammonUI = (function() { ...@@ -207,137 +122,57 @@ var BackgammonUI = (function() {
var whiteOff = document.querySelector('.bg-borne-off--white'); var whiteOff = document.querySelector('.bg-borne-off--white');
var blackOff = document.querySelector('.bg-borne-off--black'); var blackOff = document.querySelector('.bg-borne-off--black');
if (!whiteOff || !blackOff) return; if (!whiteOff || !blackOff) return;
whiteOff.innerHTML = buildBorneHTML(state.borneOff[0], 'white');
whiteOff.innerHTML = buildBorneOff(state.borneOff[0], 'white'); blackOff.innerHTML = buildBorneHTML(state.borneOff[1], 'black');
blackOff.innerHTML = buildBorneOff(state.borneOff[1], 'black');
whiteOff.classList.toggle('bg-point--valid', highlights.indexOf('off') !== -1 && state.currentTurn === 0);
blackOff.classList.toggle('bg-point--valid', highlights.indexOf('off') !== -1 && state.currentTurn === 1);
} }
function buildBorneOff(count, color) { function buildBorneHTML(count, color) {
var html = '<div class="bg-borne-off-stack">'; var html = '';
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) html += '<div class="bg-borne-chip bg-borne-chip--' + color + '"></div>';
html += '<div class="bg-borne-chip bg-borne-chip--' + color + '"></div>'; if (count > 0) html += '<span class="bg-borne-count">' + count + '</span>';
}
html += '</div>';
if (count > 0) html += '<span class="bg-borne-count">' + count + '/15</span>';
return html; return html;
} }
function renderDice(state) { function renderDice(state) {
var container = document.getElementById('bg-dice-container'); var container = document.getElementById('bg-dice-container');
if (!container) return; if (!container) return;
if (!state.dice) { container.innerHTML = ''; return; }
if (!state.dice) {
container.innerHTML = '';
return;
}
// For doubles, show 4 dice; for normal, show 2
var diceToRender = state.dice;
var isDoubles = (state.dice[0] === state.dice[1]);
var html = '<div class="bg-dice-pair">'; var html = '<div class="bg-dice-pair">';
var total = state.dice[0] === state.dice[1] ? 4 : 2;
if (isDoubles) {
// Show 4 dice for doubles
var usedCount = 4 - state.diceRemaining.length;
for (var d = 0; d < 4; d++) {
var used = (d < usedCount);
html += renderSingleDie(state.dice[0], used);
}
} else {
// Show 2 dice - mark each as used if not in diceRemaining
var remaining = state.diceRemaining.slice(); var remaining = state.diceRemaining.slice();
for (var d = 0; d < 2; d++) {
var val = state.dice[d];
var rIdx = remaining.indexOf(val);
var used = (rIdx === -1);
if (rIdx !== -1) remaining.splice(rIdx, 1);
html += renderSingleDie(val, used);
}
}
html += '</div>';
// Dice remaining indicator text for (var d = 0; d < total; d++) {
if (state.diceRemaining.length > 0 && state.phase === 'moving') { var val = state.dice[d < 2 ? d : 0];
html += '<div class="bg-dice-remaining-text">المتبقي: ' + state.diceRemaining.join('، ') + '</div>'; var usedIdx = remaining.indexOf(val);
var isUsed = (usedIdx === -1);
if (!isUsed) remaining.splice(usedIdx, 1);
html += renderDie(val, isUsed);
} }
html += '</div>';
container.innerHTML = html; container.innerHTML = html;
} }
function renderSingleDie(value, used) { function renderDie(value, used) {
var cls = 'bg-dice' + (used ? ' bg-dice--used' : ''); var cls = 'bg-dice' + (used ? ' bg-dice--used' : '');
var dots = C.DICE_FACES[value] || []; var dots = C.DICE_FACES[value] || [];
var html = '<div class="' + cls + '">'; var html = '<div class="' + cls + '">';
for (var i = 0; i < 9; i++) { for (var i = 0; i < 9; i++) {
var visible = dots.indexOf(i) !== -1; html += '<span class="bg-dice-dot' + (dots.indexOf(i) !== -1 ? ' bg-dice-dot--filled' : '') + '"></span>';
html += '<span class="bg-dice-dot' + (visible ? ' bg-dice-dot--filled' : '') + '"></span>';
}
html += '</div>';
return html;
}
function renderTurnIndicator(state) {
var el = document.getElementById('bg-turn');
if (!el) return;
var player = state.players[state.currentTurn];
var color = state.currentTurn === 0 ? 'white' : 'black';
var phaseText = state.phase === 'awaiting_roll' ? 'ارمِ النرد' : state.phase === 'moving' ? 'حرّك قطعك' : '';
var isActive = state.players[state.currentTurn].type === 'human';
if (isActive) {
el.classList.add('bg-turn-indicator--active');
} else {
el.classList.remove('bg-turn-indicator--active');
} }
el.innerHTML = '<div class="bg-player-dot bg-player-dot--' + color + '"></div>' + return html + '</div>';
'<span class="bg-player-name">' + player.name + '</span>' +
(phaseText ? '<span class="bg-player-pip">' + phaseText + '</span>' : '');
} }
function renderControls(state) { function renderControls(state) {
var rollBtn = document.getElementById('bg-roll-btn'); var rollBtn = document.getElementById('bg-roll-btn');
if (!rollBtn) return; var undoBtn = document.getElementById('bg-undo-btn');
var isHumanTurn = state.players[state.currentTurn].type === 'human';
var canRoll = state.phase === 'awaiting_roll' && isHumanTurn;
if (rollBtn) {
var canRoll = state.phase === 'awaiting_roll' && state.players[state.currentTurn].type === 'human';
rollBtn.style.display = canRoll ? '' : 'none'; rollBtn.style.display = canRoll ? '' : 'none';
rollBtn.disabled = !canRoll;
// Pass/Skip button
renderPassButton(state);
}
function renderPassButton(state) {
var existing = document.getElementById('bg-pass-btn');
var isHumanTurn = state.players[state.currentTurn].type === 'human';
var canMoveAny = state.phase === 'moving' && state.diceRemaining.length > 0 &&
C.canMove(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining);
var shouldShowPass = state.phase === 'moving' && isHumanTurn &&
state.diceRemaining.length > 0 && !canMoveAny;
if (shouldShowPass) {
if (!existing) {
var btn = document.createElement('button');
btn.id = 'bg-pass-btn';
btn.className = 'bg-pass-btn';
btn.textContent = 'تخطي الدور';
btn.addEventListener('click', function() {
BackgammonGame.handlePointClick('pass');
});
var rollBtn = document.getElementById('bg-roll-btn');
if (rollBtn && rollBtn.parentNode) {
rollBtn.parentNode.insertBefore(btn, rollBtn.nextSibling);
}
}
} else {
if (existing) {
existing.parentNode.removeChild(existing);
} }
if (undoBtn) {
undoBtn.style.display = state.phase === 'moving' && state.players[state.currentTurn].type === 'human' ? '' : 'none';
} }
} }
...@@ -348,168 +183,68 @@ var BackgammonUI = (function() { ...@@ -348,168 +183,68 @@ var BackgammonUI = (function() {
if (el1) el1.textContent = C.pipCount(state.board, state.bar, 1); if (el1) el1.textContent = C.pipCount(state.board, state.bar, 1);
} }
function renderStatus() { // ─── Selection & Highlighting ──────────────────────────────────────────────
var el = document.getElementById('bg-status');
if (!el) return;
el.textContent = statusText;
el.style.display = statusText ? '' : 'none';
}
function renderMovableCheckers(state) {
movablePoints = [];
if (state.phase !== 'moving') return;
if (state.players[state.currentTurn].type !== 'human') return;
if (selected !== null) return; // Don't show movable when already selected
var sign = (state.currentTurn === 0) ? 1 : -1;
// If player has checkers on bar, only bar is movable
if (state.bar[state.currentTurn] > 0) {
// Bar is handled separately, no point highlights needed
return;
}
// Find all points that have valid moves
for (var i = 0; i < 24; i++) {
if (state.board[i] * sign <= 0) continue;
var hasMove = false;
for (var d = 0; d < state.diceRemaining.length; d++) {
var moves = C.getValidMoves(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining[d]);
for (var m = 0; m < moves.length; m++) {
if (moves[m].from === i) { hasMove = true; break; }
}
if (hasMove) break;
}
if (hasMove) movablePoints.push(i);
}
// Apply classes
var points = document.querySelectorAll('.bg-point');
points.forEach(function(el) {
var idx = parseInt(el.dataset.point);
if (movablePoints.indexOf(idx) !== -1) {
el.classList.add('bg-point--clickable');
var topChecker = el.querySelector('.bg-checker:last-child');
if (topChecker) topChecker.classList.add('bg-checker--movable');
} else {
el.classList.remove('bg-point--clickable');
}
});
}
function renderActivePlayerHighlight(state) {
var info0 = document.getElementById('bg-player-info-0');
var info1 = document.getElementById('bg-player-info-1');
if (info0) {
info0.classList.toggle('bg-player-info--active', state.currentTurn === 0);
}
if (info1) {
info1.classList.toggle('bg-player-info--active', state.currentTurn === 1);
}
}
function setSelected(val) { function setSelected(val) {
selected = val; selected = val;
// Apply visual immediately
// Immediately apply visual feedback to DOM without waiting for full render
// Clear previous selections
var prevSelected = document.querySelectorAll('.bg-point--selected, .bg-checker--selected');
prevSelected.forEach(function(el) {
el.classList.remove('bg-point--selected');
el.classList.remove('bg-checker--selected');
});
var prevBarSelected = document.querySelectorAll('.bg-bar-area.bg-point--selected');
prevBarSelected.forEach(function(el) { el.classList.remove('bg-point--selected'); });
// Remove movable indicators when selecting
var movableEls = document.querySelectorAll('.bg-checker--movable, .bg-point--clickable');
movableEls.forEach(function(el) {
el.classList.remove('bg-checker--movable');
el.classList.remove('bg-point--clickable');
});
if (val === null) return;
if (val === 'bar') { if (val === 'bar') {
// Highlight the bar area var bar = document.querySelector('.bg-bar--bottom');
var turn = gameState ? gameState.currentTurn : 0; if (bar) bar.classList.add('bg-point--selected');
var barEl = turn === 0 ? document.querySelector('.bg-bar--bottom') : document.querySelector('.bg-bar--top'); } else if (val !== null) {
if (barEl) { var el = document.querySelector('[data-point="' + val + '"]');
barEl.classList.add('bg-point--selected'); if (el) {
var topChecker = barEl.querySelector('.bg-checker:last-child'); el.classList.add('bg-point--selected');
if (topChecker) topChecker.classList.add('bg-checker--selected'); var checker = el.querySelector('.bg-checker:last-child');
} if (checker) checker.classList.add('bg-checker--selected');
} else {
var pointEl = document.querySelector('[data-point="' + val + '"]');
if (pointEl) {
pointEl.classList.add('bg-point--selected');
var topChecker = pointEl.querySelector('.bg-checker:last-child');
if (topChecker) topChecker.classList.add('bg-checker--selected');
} }
} }
} }
function getSelected() { function getSelected() { return selected; }
return selected;
}
function clearSelected() { function clearSelected() {
selected = null; selected = null;
var points = document.querySelectorAll('.bg-point--selected'); document.querySelectorAll('.bg-point--selected').forEach(function(el) { el.classList.remove('bg-point--selected'); });
points.forEach(function(el) { el.classList.remove('bg-point--selected'); }); document.querySelectorAll('.bg-checker--selected').forEach(function(el) { el.classList.remove('bg-checker--selected'); });
var checkers = document.querySelectorAll('.bg-checker--selected');
checkers.forEach(function(el) { el.classList.remove('bg-checker--selected'); });
var bars = document.querySelectorAll('.bg-bar-area.bg-point--selected');
bars.forEach(function(el) { el.classList.remove('bg-point--selected'); });
} }
function highlightPoints(targets) { function highlightSources(sources) {
highlights = targets; clearHighlights();
var points = document.querySelectorAll('.bg-point'); sources.forEach(function(src) {
points.forEach(function(el) { if (src === 'bar') {
var idx = parseInt(el.dataset.point); var bar = document.querySelector('.bg-bar--bottom');
el.classList.toggle('bg-point--valid', targets.indexOf(idx) !== -1); if (bar) bar.classList.add('bg-point--source');
}); } else {
var borneOffs = document.querySelectorAll('.bg-borne-off'); var el = document.querySelector('[data-point="' + src + '"]');
borneOffs.forEach(function(el) { if (el) el.classList.add('bg-point--source');
el.classList.toggle('bg-point--valid', targets.indexOf('off') !== -1); }
}); });
}
// Also ensure the selected point stays visually marked function highlightTargets(targets) {
if (selected !== null && selected !== 'bar') { clearHighlights();
var selEl = document.querySelector('[data-point="' + selected + '"]'); targets.forEach(function(t) {
if (selEl) selEl.classList.add('bg-point--selected'); if (t === 'off') {
document.querySelectorAll('.bg-borne-off').forEach(function(el) { el.classList.add('bg-point--valid'); });
} else {
var el = document.querySelector('[data-point="' + t + '"]');
if (el) el.classList.add('bg-point--valid');
} }
});
} }
function clearHighlights() { function clearHighlights() {
highlights = []; document.querySelectorAll('.bg-point--valid, .bg-point--source').forEach(function(el) {
var els = document.querySelectorAll('.bg-point--valid'); el.classList.remove('bg-point--valid', 'bg-point--source');
els.forEach(function(el) { el.classList.remove('bg-point--valid'); }); });
var hovers = document.querySelectorAll('.bg-point--hover-preview');
hovers.forEach(function(el) { el.classList.remove('bg-point--hover-preview'); });
} }
function setStatus(msg) { // ─── Status & Log ──────────────────────────────────────────────────────────
statusText = msg;
renderStatus();
}
function animateMove(fromPoint, toPoint) { function setStatus(msg) {
// Brief animation class on destination var el = document.getElementById('bg-status');
var targetEl = null; if (el) el.textContent = msg;
if (toPoint === 'off') {
var turn = gameState ? gameState.currentTurn : 0;
targetEl = document.querySelector(turn === 0 ? '.bg-borne-off--white' : '.bg-borne-off--black');
} else if (typeof toPoint === 'number') {
targetEl = document.querySelector('[data-point="' + toPoint + '"]');
}
if (targetEl) {
targetEl.classList.add('bg-move-flash');
setTimeout(function() {
targetEl.classList.remove('bg-move-flash');
}, 400);
}
} }
function showLog(msg) { function showLog(msg) {
...@@ -519,45 +254,35 @@ var BackgammonUI = (function() { ...@@ -519,45 +254,35 @@ var BackgammonUI = (function() {
entry.className = 'bg-log-entry'; entry.className = 'bg-log-entry';
entry.textContent = msg; entry.textContent = msg;
log.prepend(entry); log.prepend(entry);
if (log.children.length > 20) log.removeChild(log.lastChild); if (log.children.length > 15) log.removeChild(log.lastChild);
} }
function showResult(winnerName, winType, points) { function showResult(winnerName, winType, points) {
var overlay = document.getElementById('bg-result-overlay'); var overlay = document.getElementById('bg-result-overlay');
if (!overlay) return; if (!overlay) return;
var label = (winType === 'backgammon') ? 'باكغمّون!' : (winType === 'gammon') ? 'غمّون!' : 'فوز عادي'; var label = (winType === 'backgammon') ? 'باكغمّون!' : (winType === 'gammon') ? 'غمّون!' : 'فوز!';
overlay.innerHTML = '<div class="bg-result-card">' + overlay.innerHTML = '<div class="bg-result-card">' +
'<h2>' + winnerName + '</h2>' + '<h2>' + winnerName + '</h2>' +
'<p class="bg-result-type">' + label + '</p>' + '<p class="bg-result-type">' + label + '</p>' +
'<p class="bg-result-points">+' + points + ' نقاط</p>' + '<p class="bg-result-points">+' + points + '</p>' +
'<button class="bg-new-game-btn" id="bg-new-game-btn">لعبة جديدة</button>' + '<button class="bg-new-game-btn" onclick="location.reload()">لعبة جديدة</button></div>';
'</div>';
overlay.style.display = 'flex'; overlay.style.display = 'flex';
// Bind new game button
var newGameBtn = document.getElementById('bg-new-game-btn');
if (newGameBtn) {
newGameBtn.addEventListener('click', function() {
overlay.style.display = 'none';
if (typeof BackgammonGame.init === 'function' && gameState) {
// Restart with same options
location.reload();
}
});
}
} }
function animateMove() {}
return { return {
init: init, init: init,
render: render, render: render,
setSelected: setSelected, setSelected: setSelected,
getSelected: getSelected, getSelected: getSelected,
clearSelected: clearSelected, clearSelected: clearSelected,
highlightPoints: highlightPoints, highlightSources: highlightSources,
highlightTargets: highlightTargets,
clearHighlights: clearHighlights, clearHighlights: clearHighlights,
setStatus: setStatus, setStatus: setStatus,
animateMove: animateMove,
showLog: showLog, showLog: showLog,
showResult: showResult showResult: showResult,
animateMove: animateMove
}; };
})(); })();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment