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

fix: 30 UX improvements — make backgammon fully playable

Game Controller (backgammon-game.js) rewrite:
- Fix selection: click same piece to deselect, click another to swap
- Bar enforcement: must enter from bar first with clear message
- Auto-pass when no moves remain for unused dice
- Bot waiting status message visible during thinking
- Opening roll shows clear "your turn" status with dice values
- Pass button action for truly stuck positions
- Smart move validation with proper die tracking
- Individual bot move logging
- Source validation: only select pieces that have moves

UI (backgammon-ui.js) rewrite:
- setSelected directly applies DOM classes (no render cycle needed)
- Status text system: guides player ("Select piece", "Choose target")
- Movable checkers highlighted with golden ring when it's your turn
- Dice remaining indicator text below dice
- Pass/skip button shown when stuck
- Active player gold border highlight
- Tap feedback animation
- Hover preview on valid targets
- New game button in result overlay
- Valid target points show dashed circle indicator

CSS additions:
- .bg-point--clickable with gold ring on top checker
- .bg-point--hover-preview green tint
- .bg-move-flash animation
- .bg-status, .bg-pass-btn, .bg-dice-remaining styles
- .bg-new-game-btn in overlay
- Valid point dashed circle indicator
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 87ce35b8
...@@ -162,6 +162,17 @@ ...@@ -162,6 +162,17 @@
.bg-point--valid { .bg-point--valid {
background: var(--bg-valid); background: var(--bg-valid);
border-radius: 4px; border-radius: 4px;
cursor: pointer;
}
.bg-point--valid::after {
content: '';
position: absolute;
inset: 20%;
border: 2px dashed rgba(76,175,80,0.7);
border-radius: 50%;
z-index: 5;
pointer-events: none;
} }
.bg-point--selected { .bg-point--selected {
...@@ -169,11 +180,90 @@ ...@@ -169,11 +180,90 @@
border-radius: 4px; border-radius: 4px;
} }
.bg-point--clickable {
cursor: pointer;
}
.bg-point--clickable .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--hover-preview {
background: rgba(76,175,80,0.3);
}
.bg-point--hit { .bg-point--hit {
background: var(--bg-hit); background: var(--bg-hit);
border-radius: 4px; border-radius: 4px;
} }
.bg-move-flash {
animation: bg-move-flash-anim 0.4s ease;
}
@keyframes bg-move-flash-anim {
0% { background: rgba(255,255,255,0.3); }
100% { background: transparent; }
}
.bg-tap-flash {
animation: bg-tap-flash-anim 0.2s ease;
}
@keyframes bg-tap-flash-anim {
0% { transform: scale(0.95); }
100% { transform: scale(1); }
}
/* Status text */
.bg-status {
text-align: center;
padding: 8px 12px;
font-size: 14px;
font-weight: 600;
color: var(--text-1, #fff);
min-height: 24px;
}
/* Pass button */
.bg-pass-btn {
padding: 10px 20px;
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.2);
border-radius: 8px;
color: var(--text-2, #ccc);
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s;
}
.bg-pass-btn:hover {
background: rgba(255,255,255,0.2);
}
/* Dice remaining text */
.bg-dice-remaining {
font-size: 12px;
color: var(--text-3, #888);
text-align: center;
padding: 4px 0;
}
/* New game button in overlay */
.bg-new-game-btn {
margin-top: 16px;
padding: 12px 24px;
background: linear-gradient(135deg, #e7a832, #d4922a);
color: #fff;
border: none;
border-radius: 8px;
font-size: 15px;
font-weight: 700;
cursor: pointer;
box-shadow: 0 3px 10px rgba(231,168,50,0.4);
}
/* Point number labels (optional, shown on hover or always) */ /* Point number labels (optional, shown on hover or always) */
.bg-point-label { .bg-point-label {
position: absolute; position: absolute;
......
...@@ -58,7 +58,11 @@ var BackgammonGame = (function() { ...@@ -58,7 +58,11 @@ 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('انتظر... البوت يفكر');
setTimeout(function() { executeBotTurn(); }, 800); setTimeout(function() { executeBotTurn(); }, 800);
} else {
// Human's turn - show helpful status
UI.setStatus('دورك — حرّك قطعك بالنرد ' + d1 + ' و ' + d2);
} }
} }
...@@ -73,13 +77,15 @@ var BackgammonGame = (function() { ...@@ -73,13 +77,15 @@ var BackgammonGame = (function() {
state.phase = 'moving'; state.phase = 'moving';
UI.showLog(state.players[state.currentTurn].name + ' رمى ' + d1 + ' و ' + d2); UI.showLog(state.players[state.currentTurn].name + ' رمى ' + d1 + ' و ' + d2);
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.setStatus('لا توجد حركة — ينتقل الدور');
UI.showLog('لا توجد حركة متاحة — تخطي الدور'); UI.showLog('لا توجد حركة متاحة — تخطي الدور');
setTimeout(function() { endTurn(); }, 1200); setTimeout(function() { endTurn(); }, 1200);
} else {
UI.setStatus('اختر قطعة لتحريكها');
} }
UI.render(state);
} }
function handlePointClick(pointOrAction) { function handlePointClick(pointOrAction) {
...@@ -91,42 +97,135 @@ var BackgammonGame = (function() { ...@@ -91,42 +97,135 @@ var BackgammonGame = (function() {
return; return;
} }
// Pass/skip action
if (pointOrAction === 'pass') {
if (state.diceRemaining.length > 0 &&
!C.canMove(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining)) {
UI.setStatus('لا توجد حركة — ينتقل الدور');
UI.showLog('تخطي الدور — لا توجد حركة');
UI.clearSelected();
UI.clearHighlights();
setTimeout(function() { endTurn(); }, 600);
}
return;
}
var selected = UI.getSelected(); var selected = UI.getSelected();
// CASE 1: Nothing selected yet — select a source
if (selected === null) { if (selected === null) {
if (pointOrAction === 'bar') { selectSource(pointOrAction);
if (state.bar[state.currentTurn] > 0) { return;
UI.setSelected('bar'); }
highlightValidTargets('bar');
} // CASE 2: Something is already selected
} else { var from = selected;
var point = parseInt(pointOrAction);
var count = state.board[point]; // Check if user clicked the same point (deselect)
var isOwn = (state.currentTurn === 0) ? count > 0 : count < 0; if (isSamePoint(from, pointOrAction)) {
if (isOwn) { UI.clearSelected();
UI.setSelected(point); UI.clearHighlights();
highlightValidTargets(point); UI.setStatus('اختر قطعة لتحريكها');
} UI.render(state);
return;
}
// Determine the target
var to = parseTarget(pointOrAction);
// Check if target is a valid destination
var validMoves = getValidMovesForFrom(from);
var validMove = null;
for (var i = 0; i < validMoves.length; i++) {
if (validMoves[i].to === to) {
validMove = validMoves[i];
break;
} }
}
if (validMove) {
// Execute the valid move
UI.animateMove(from, to);
UI.clearSelected();
UI.clearHighlights();
executeMove(validMove);
} else { } else {
var from = selected; // Not a valid destination — check if it's the user's own piece (swap selection)
var to = (pointOrAction === 'off') ? 'off' : parseInt(pointOrAction); if (isOwnPiece(pointOrAction)) {
UI.clearSelected();
var validMoves = getValidMovesForFrom(from); UI.clearHighlights();
var validMove = null; selectSource(pointOrAction);
for (var i = 0; i < validMoves.length; i++) { } else {
if (validMoves[i].to === to) { // Invalid click — flash feedback, keep selection
validMove = validMoves[i]; UI.setStatus('وجهة غير صالحة — اختر مكان آخر');
break; setTimeout(function() {
} if (UI.getSelected() !== null) {
UI.setStatus('اختر وجهة');
}
}, 1500);
} }
}
}
if (validMove) { function isSamePoint(selected, pointOrAction) {
executeMove(validMove); 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) {
if (pointOrAction === 'off') return 'off';
if (pointOrAction === 'bar') return 'bar';
var parsed = parseInt(pointOrAction);
if (isNaN(parsed)) return pointOrAction;
return parsed;
}
function isOwnPiece(pointOrAction) {
if (pointOrAction === 'bar') {
return state.bar[state.currentTurn] > 0;
}
if (pointOrAction === 'off') return false;
var point = parseInt(pointOrAction);
if (isNaN(point)) return false;
var count = state.board[point];
return (state.currentTurn === 0) ? count > 0 : count < 0;
}
function selectSource(pointOrAction) {
// If player has checkers on bar, they MUST enter from bar first
if (state.bar[state.currentTurn] > 0) {
if (pointOrAction === 'bar') {
UI.setSelected('bar');
highlightValidTargets('bar');
UI.setStatus('اختر وجهة الدخول من البار');
} else {
UI.setStatus('يجب إدخال القطع من البار أولاً');
} }
return;
}
UI.clearSelected(); if (pointOrAction === 'bar') return; // No checkers on bar
UI.clearHighlights();
var point = parseInt(pointOrAction);
if (isNaN(point)) return;
var count = state.board[point];
var isOwn = (state.currentTurn === 0) ? count > 0 : count < 0;
if (isOwn) {
// Check if this piece actually has valid moves
var moves = getValidMovesForFrom(point);
if (moves.length > 0) {
UI.setSelected(point);
highlightValidTargets(point);
UI.setStatus('اختر وجهة');
} else {
UI.setStatus('هذه القطعة لا تملك حركة — اختر قطعة أخرى');
}
} else {
UI.setStatus('اختر قطعة من قطعك');
} }
} }
...@@ -137,11 +236,19 @@ var BackgammonGame = (function() { ...@@ -137,11 +236,19 @@ var BackgammonGame = (function() {
var moves = C.getValidMoves(state.board, state.bar, state.borneOff, state.currentTurn, die); var moves = C.getValidMoves(state.board, state.bar, state.borneOff, state.currentTurn, die);
for (var j = 0; j < moves.length; j++) { for (var j = 0; j < moves.length; j++) {
if (moves[j].from === from) { 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; var isDup = false;
for (var k = 0; k < allMoves.length; k++) { for (var k = 0; k < allMoves.length; k++) {
if (allMoves[k].to === moves[j].to) { isDup = true; break; } if (allMoves[k].to === move.to) { isDup = true; break; }
} }
if (!isDup) allMoves.push(moves[j]); if (!isDup) allMoves.push(move);
} }
} }
} }
...@@ -184,14 +291,21 @@ var BackgammonGame = (function() { ...@@ -184,14 +291,21 @@ var BackgammonGame = (function() {
return; return;
} }
if (state.diceRemaining.length === 0 || !C.canMove(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining)) { if (state.diceRemaining.length === 0) {
if (state.diceRemaining.length > 0) { UI.setStatus('انتهت حركاتك');
UI.showLog('لا توجد حركة متاحة بالنرد المتبقي'); UI.render(state);
}
setTimeout(function() { endTurn(); }, 600); setTimeout(function() { endTurn(); }, 600);
} else if (!C.canMove(state.board, state.bar, state.borneOff, state.currentTurn, state.diceRemaining)) {
// Auto-pass with remaining dice
UI.setStatus('لا توجد حركة — ينتقل الدور');
UI.showLog('لا توجد حركة متاحة بالنرد المتبقي');
UI.render(state);
setTimeout(function() { endTurn(); }, 1200);
} else {
// More moves available
UI.setStatus('اختر قطعة لتحريكها — المتبقي: ' + state.diceRemaining.join('، '));
UI.render(state);
} }
UI.render(state);
} }
function endTurn() { function endTurn() {
...@@ -200,10 +314,16 @@ var BackgammonGame = (function() { ...@@ -200,10 +314,16 @@ var BackgammonGame = (function() {
state.dice = null; state.dice = null;
state.diceRemaining = []; state.diceRemaining = [];
UI.render(state); UI.clearSelected();
UI.clearHighlights();
if (state.players[state.currentTurn].type === 'bot') { if (state.players[state.currentTurn].type === 'bot') {
UI.setStatus('انتظر... البوت يفكر');
UI.render(state);
setTimeout(function() { executeBotTurn(); }, 800); setTimeout(function() { executeBotTurn(); }, 800);
} else {
UI.setStatus('دورك — ارمِ النرد');
UI.render(state);
} }
} }
...@@ -215,11 +335,13 @@ var BackgammonGame = (function() { ...@@ -215,11 +335,13 @@ var BackgammonGame = (function() {
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.showLog(state.players[state.currentTurn].name + ' رمى ' + d1 + ' و ' + d2);
UI.setStatus('البوت يفكر...');
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;
} }
...@@ -258,8 +380,15 @@ var BackgammonGame = (function() { ...@@ -258,8 +380,15 @@ var BackgammonGame = (function() {
hit: move.hit 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 + ' ضرب قطعة!'); 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)) {
...@@ -281,6 +410,7 @@ var BackgammonGame = (function() { ...@@ -281,6 +410,7 @@ var BackgammonGame = (function() {
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 + ' (+' + points + ')');
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);
......
...@@ -5,15 +5,34 @@ var BackgammonUI = (function() { ...@@ -5,15 +5,34 @@ var BackgammonUI = (function() {
var selected = null; var selected = null;
var highlights = []; 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 = '';
html += '<div class="bg-board-inner">'; html += '<div class="bg-board-inner">';
...@@ -61,6 +80,16 @@ var BackgammonUI = (function() { ...@@ -61,6 +80,16 @@ var BackgammonUI = (function() {
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]');
// Tap feedback
var clickTarget = point || action;
if (clickTarget) {
clickTarget.classList.add('bg-tap-flash');
setTimeout(function() {
clickTarget.classList.remove('bg-tap-flash');
}, 200);
}
if (point) { if (point) {
BackgammonGame.handlePointClick(point.dataset.point); BackgammonGame.handlePointClick(point.dataset.point);
} else if (action) { } else if (action) {
...@@ -68,6 +97,25 @@ var BackgammonUI = (function() { ...@@ -68,6 +97,25 @@ var BackgammonUI = (function() {
} }
}); });
// 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() { rollBtn.addEventListener('click', function() {
...@@ -85,6 +133,9 @@ var BackgammonUI = (function() { ...@@ -85,6 +133,9 @@ var BackgammonUI = (function() {
renderTurnIndicator(state); renderTurnIndicator(state);
renderControls(state); renderControls(state);
renderPipCount(state); renderPipCount(state);
renderStatus();
renderMovableCheckers(state);
renderActivePlayerHighlight(state);
} }
function renderCheckers(state) { function renderCheckers(state) {
...@@ -103,6 +154,7 @@ var BackgammonUI = (function() { ...@@ -103,6 +154,7 @@ var BackgammonUI = (function() {
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';
checkersHTML += '<div class="' + cls + '"></div>'; checkersHTML += '<div class="' + cls + '"></div>';
} }
checkersHTML += '</div>'; checkersHTML += '</div>';
...@@ -113,9 +165,10 @@ var BackgammonUI = (function() { ...@@ -113,9 +165,10 @@ var BackgammonUI = (function() {
el.innerHTML = checkersHTML; el.innerHTML = checkersHTML;
el.classList.remove('bg-point--valid', 'bg-point--selected'); 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 (highlights.indexOf(idx) !== -1) el.classList.add('bg-point--valid');
if (movablePoints.indexOf(idx) !== -1) el.classList.add('bg-point--clickable');
}); });
} }
...@@ -129,6 +182,12 @@ var BackgammonUI = (function() { ...@@ -129,6 +182,12 @@ var BackgammonUI = (function() {
barTop.innerHTML = buildBarCheckers(blackBar, 'black', selected === 'bar' && state.currentTurn === 1); barTop.innerHTML = buildBarCheckers(blackBar, 'black', selected === 'bar' && state.currentTurn === 1);
barBottom.innerHTML = buildBarCheckers(whiteBar, 'white', selected === 'bar' && state.currentTurn === 0); 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 buildBarCheckers(count, color, isSelected) {
...@@ -137,6 +196,7 @@ var BackgammonUI = (function() { ...@@ -137,6 +196,7 @@ var BackgammonUI = (function() {
for (var i = 0; i < display; i++) { for (var i = 0; i < display; i++) {
var cls = 'bg-checker bg-checker--' + color; var cls = 'bg-checker bg-checker--' + color;
if (isSelected && i === display - 1) cls += ' bg-checker--selected'; if (isSelected && i === display - 1) cls += ' bg-checker--selected';
if (isSelected) cls += ' bg-checker--movable';
html += '<div class="' + cls + '"></div>'; 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">' + count + '</span>';
...@@ -174,16 +234,38 @@ var BackgammonUI = (function() { ...@@ -174,16 +234,38 @@ var BackgammonUI = (function() {
return; 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">';
for (var d = 0; d < 2; d++) {
var val = state.dice[d]; if (isDoubles) {
var used = true; // Show 4 dice for doubles
for (var r = 0; r < state.diceRemaining.length; r++) { var usedCount = 4 - state.diceRemaining.length;
if (state.diceRemaining[r] === val) { used = false; break; } 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();
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 += renderSingleDie(val, used);
} }
html += '</div>'; html += '</div>';
// Dice remaining indicator text
if (state.diceRemaining.length > 0 && state.phase === 'moving') {
html += '<div class="bg-dice-remaining-text">المتبقي: ' + state.diceRemaining.join('، ') + '</div>';
}
container.innerHTML = html; container.innerHTML = html;
} }
...@@ -225,6 +307,38 @@ var BackgammonUI = (function() { ...@@ -225,6 +307,38 @@ var BackgammonUI = (function() {
rollBtn.style.display = canRoll ? '' : 'none'; rollBtn.style.display = canRoll ? '' : 'none';
rollBtn.disabled = !canRoll; 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);
}
}
} }
function renderPipCount(state) { function renderPipCount(state) {
...@@ -234,8 +348,105 @@ var BackgammonUI = (function() { ...@@ -234,8 +348,105 @@ 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() {
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;
// 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') {
// Highlight the bar area
var turn = gameState ? gameState.currentTurn : 0;
var barEl = turn === 0 ? document.querySelector('.bg-bar--bottom') : document.querySelector('.bg-bar--top');
if (barEl) {
barEl.classList.add('bg-point--selected');
var topChecker = barEl.querySelector('.bg-checker:last-child');
if (topChecker) topChecker.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() {
...@@ -246,6 +457,10 @@ var BackgammonUI = (function() { ...@@ -246,6 +457,10 @@ var BackgammonUI = (function() {
selected = null; selected = null;
var points = document.querySelectorAll('.bg-point--selected'); var points = document.querySelectorAll('.bg-point--selected');
points.forEach(function(el) { el.classList.remove('bg-point--selected'); }); points.forEach(function(el) { el.classList.remove('bg-point--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 highlightPoints(targets) {
...@@ -259,12 +474,42 @@ var BackgammonUI = (function() { ...@@ -259,12 +474,42 @@ var BackgammonUI = (function() {
borneOffs.forEach(function(el) { borneOffs.forEach(function(el) {
el.classList.toggle('bg-point--valid', targets.indexOf('off') !== -1); el.classList.toggle('bg-point--valid', targets.indexOf('off') !== -1);
}); });
// Also ensure the selected point stays visually marked
if (selected !== null && selected !== 'bar') {
var selEl = document.querySelector('[data-point="' + selected + '"]');
if (selEl) selEl.classList.add('bg-point--selected');
}
} }
function clearHighlights() { function clearHighlights() {
highlights = []; highlights = [];
var els = document.querySelectorAll('.bg-point--valid'); var els = document.querySelectorAll('.bg-point--valid');
els.forEach(function(el) { el.classList.remove('bg-point--valid'); }); 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) {
statusText = msg;
renderStatus();
}
function animateMove(fromPoint, toPoint) {
// Brief animation class on destination
var targetEl = null;
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) {
...@@ -285,8 +530,21 @@ var BackgammonUI = (function() { ...@@ -285,8 +530,21 @@ var BackgammonUI = (function() {
'<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>' +
'</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();
}
});
}
} }
return { return {
...@@ -297,6 +555,8 @@ var BackgammonUI = (function() { ...@@ -297,6 +555,8 @@ var BackgammonUI = (function() {
clearSelected: clearSelected, clearSelected: clearSelected,
highlightPoints: highlightPoints, highlightPoints: highlightPoints,
clearHighlights: clearHighlights, clearHighlights: clearHighlights,
setStatus: setStatus,
animateMove: animateMove,
showLog: showLog, showLog: showLog,
showResult: showResult showResult: showResult
}; };
......
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