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

feat: add full Dominoes game — tournament rules, bot AI, multiplayer

Complete dominoes integration with formal tournament rules:
- Double-six set (28 tiles), spinner rule (first double = 4-way branch)
- 2-player Draw Game (draw from boneyard when blocked, first to 100)
- 4-player Teams Block Game (pass when blocked, first to 150)
- All modes: local pass-and-play, VS bot (easy/medium/hard), online matchmaking, private rooms
- Full server-side validation (api/domino.php — play/draw/pass/round/match lifecycle)
- Bot AI with 3 difficulty levels (random, greedy-pip, strategic with opponent tracking)
- Supabase Realtime multiplayer via WebSocket
- CSS pip rendering, board layout with 4 directions, responsive tiles
- Database: domino_matches, domino_queue, RLS policies, game_plugins entry
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 793ff97e
This diff is collapsed.
......@@ -55,6 +55,14 @@ if ($route === '' || $route === 'home') {
require 'pages/ludo-live.php';
} elseif ($route === 'ludo-matchmaking') {
require 'pages/ludo-matchmaking.php';
} elseif ($route === 'domino') {
require 'pages/domino.php';
} elseif ($route === 'domino-game') {
require 'pages/domino-game.php';
} elseif ($route === 'domino-live') {
require 'pages/domino-live.php';
} elseif ($route === 'domino-matchmaking') {
require 'pages/domino-matchmaking.php';
} elseif ($route === 'admin/theme') {
require 'pages/admin-theme.php';
} elseif (str_starts_with($route, 'api/')) {
......
<?php $pageTitle = 'EL3AB - دومينو'; $hideNav = true; ?>
<?php require __DIR__ . '/../includes/header.php'; ?>
<link rel="stylesheet" href="/public/css/domino.css">
<div class="domino-game">
<!-- Top area: opponent + scores -->
<div class="domino-top-area">
<div id="domino-opponent-top" class="domino-opponent-top"></div>
<div id="domino-scores"></div>
</div>
<!-- Side opponents (4P mode) -->
<div id="domino-opponent-left" class="domino-opponent-left"></div>
<div id="domino-opponent-right" class="domino-opponent-right"></div>
<!-- Board -->
<div class="domino-board-area">
<div id="domino-board"></div>
<div id="domino-boneyard"></div>
</div>
<!-- Status -->
<div id="domino-status" class="domino-status"></div>
<!-- Actions -->
<div id="domino-actions"></div>
<!-- End buttons (choose which side to play on) -->
<div id="domino-end-buttons"></div>
<!-- Player hand -->
<div class="domino-hand-area">
<div id="domino-hand"></div>
</div>
</div>
<!-- Round overlay -->
<div id="domino-round-overlay"></div>
<script src="/public/js/domino-constants.js"></script>
<script src="/public/js/domino-ui.js"></script>
<script src="/public/js/domino-bot.js"></script>
<script src="/public/js/domino-game.js"></script>
<script>
(function() {
var params = new URLSearchParams(window.location.search);
var mode = params.get('mode') || '2p';
var type = params.get('type') || 'bot';
var playerCount = parseInt(params.get('players')) || 2;
var difficulty = params.get('difficulty') || 'medium';
DominoGame.init({
mode: mode,
playerCount: playerCount,
difficulty: difficulty,
isLocal: type === 'local'
});
})();
</script>
<?php require __DIR__ . '/../includes/footer.php'; ?>
<?php $pageTitle = 'EL3AB - دومينو اونلاين'; $hideNav = true; ?>
<?php require __DIR__ . '/../includes/header.php'; ?>
<link rel="stylesheet" href="/public/css/domino.css">
<!-- Waiting Room -->
<div id="domino-waiting" class="space-y-6" style="padding:24px;text-align:center;">
<h2 style="font-size:22px;font-weight:700;">غرفة دومينو</h2>
<div class="card">
<div class="card-body space-y-4">
<div id="room-code-display">
<p class="text-muted text-sm">كود الغرفة</p>
<p id="room-code-value" style="font-size:32px;font-weight:700;letter-spacing:6px;color:var(--gold);"></p>
<button class="btn btn-ghost btn-sm" onclick="copyCode()" style="margin-top:8px;">نسخ الكود</button>
</div>
<div id="waiting-players" class="space-y-3"></div>
<button class="btn btn-gold btn-block btn-lg" id="start-game-btn" style="display:none;" onclick="DominoLive.startGame()">
ابدأ اللعب
</button>
<button class="btn btn-ghost btn-sm" onclick="window.location.href='/domino'">مغادرة</button>
</div>
</div>
</div>
<!-- Game Area -->
<div id="domino-game-area" class="domino-game" style="display:none;">
<div class="domino-top-area">
<div id="domino-opponent-top" class="domino-opponent-top"></div>
<div id="domino-scores"></div>
</div>
<div id="domino-opponent-left" class="domino-opponent-left"></div>
<div id="domino-opponent-right" class="domino-opponent-right"></div>
<div class="domino-board-area">
<div id="domino-board"></div>
<div id="domino-boneyard"></div>
</div>
<div id="domino-status" class="domino-status"></div>
<div id="domino-actions"></div>
<div id="domino-end-buttons"></div>
<div class="domino-hand-area">
<div id="domino-hand"></div>
</div>
</div>
<!-- Round overlay -->
<div id="domino-round-overlay"></div>
<style>
.domino-waiting-player {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 14px;
background: var(--bg-2);
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.05);
}
.domino-waiting-player--empty {
border-style: dashed;
opacity: 0.6;
}
</style>
<script src="/public/js/domino-constants.js"></script>
<script src="/public/js/domino-ui.js"></script>
<script src="/public/js/domino-live.js"></script>
<script>
(function() {
var params = new URLSearchParams(window.location.search);
var action = params.get('action');
var matchId = params.get('match_id');
var code = params.get('code');
var token = localStorage.getItem('sb_access_token') || '';
var userId = '';
try {
var payload = JSON.parse(atob(token.split('.')[1]));
userId = payload.sub || '';
} catch(e) {}
if (!token || !userId) {
window.location.href = '/login';
return;
}
if (action === 'create') {
App.fetch('/api/domino.php', {
method: 'POST',
body: JSON.stringify({ action: 'create', mode: params.get('mode') || '2p' })
}).then(function(res) {
if (res && res.ok) {
var newMatchId = res.match.id;
history.replaceState(null, '', '/domino-live?match_id=' + newMatchId);
DominoLive.init({ matchId: newMatchId, userId: userId, token: token });
} else {
App.toast('فشل إنشاء الغرفة', 'error');
}
});
} else if (action === 'join' && code) {
App.fetch('/api/domino.php', {
method: 'POST',
body: JSON.stringify({ action: 'join', room_code: code })
}).then(function(res) {
if (res && res.ok) {
var joinMatchId = res.match.id;
history.replaceState(null, '', '/domino-live?match_id=' + joinMatchId);
DominoLive.init({ matchId: joinMatchId, userId: userId, token: token });
} else {
App.toast(res.error || 'فشل الانضمام', 'error');
setTimeout(function() { window.location.href = '/domino'; }, 1500);
}
});
} else if (matchId) {
DominoLive.init({ matchId: matchId, userId: userId, token: token });
} else {
window.location.href = '/domino';
}
})();
function copyCode() {
var code = document.getElementById('room-code-value').textContent;
if (navigator.clipboard) {
navigator.clipboard.writeText(code);
App.toast('تم نسخ الكود', 'success');
}
}
</script>
<?php require __DIR__ . '/../includes/footer.php'; ?>
<?php $pageTitle = 'EL3AB - البحث عن خصم'; ?>
<?php require __DIR__ . '/../includes/header.php'; ?>
<div class="lobby-page" style="display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh;text-align:center;">
<div class="matchmaking-spinner">
<svg class="icon-lg" style="color:var(--gold);width:48px;height:48px;animation:spin 2s linear infinite;">
<use href="/public/icons/sprite.svg#icon-search"></use>
</svg>
</div>
<h2 style="margin-top:20px;color:var(--text-1);">جاري البحث عن خصم...</h2>
<p class="text-muted" id="matchmaking-status">يتم البحث عن لاعبين مناسبين</p>
<p class="text-muted text-sm" id="matchmaking-timer" style="margin-top:8px;"></p>
<button class="btn btn-ghost" style="margin-top:24px;" onclick="cancelMatchmaking()">إلغاء</button>
</div>
<style>
@keyframes spin { to { transform: rotate(360deg); } }
</style>
<script>
(function() {
var params = new URLSearchParams(window.location.search);
var mode = params.get('mode') || '2p';
var pollInterval = null;
var startTime = Date.now();
function updateTimer() {
var elapsed = Math.floor((Date.now() - startTime) / 1000);
var mins = Math.floor(elapsed / 60);
var secs = elapsed % 60;
document.getElementById('matchmaking-timer').textContent = (mins > 0 ? mins + ':' : '') + (secs < 10 ? '0' : '') + secs;
}
function pollMatchmaking() {
App.fetch('/api/domino.php', {
method: 'POST',
body: JSON.stringify({ action: 'matchmake', sub_action: 'join', mode: mode })
}).then(function(res) {
if (res.status === 'matched' && res.match_id) {
clearInterval(pollInterval);
document.getElementById('matchmaking-status').textContent = 'تم العثور على خصم!';
setTimeout(function() {
window.location.href = '/domino-live?match_id=' + res.match_id;
}, 1000);
}
}).catch(function() {});
}
pollInterval = setInterval(function() {
updateTimer();
pollMatchmaking();
}, 3000);
updateTimer();
pollMatchmaking();
window.cancelMatchmaking = function() {
clearInterval(pollInterval);
App.fetch('/api/domino.php', {
method: 'POST',
body: JSON.stringify({ action: 'matchmake', sub_action: 'leave', mode: mode })
}).then(function() {
window.location.href = '/domino';
});
};
})();
</script>
<?php require __DIR__ . '/../includes/footer.php'; ?>
<?php $pageTitle = 'EL3AB - دومينو'; ?>
<?php require __DIR__ . '/../includes/header.php'; ?>
<div class="lobby-page">
<a href="/games" class="breadcrumb">
<svg class="icon" style="width:14px;height:14px;"><use href="/public/icons/sprite.svg#icon-arrow-right"></use></svg>
العاب
</a>
<div class="text-center" style="margin-top:16px;">
<h2 class="lobby-title">دومينو</h2>
<p class="text-muted text-sm">اختر نوع اللعب</p>
</div>
<div class="lobby-cards">
<!-- Local (Pass & Play) -->
<div class="card lobby-card">
<div class="card-body space-y-4">
<div class="lobby-card-header">
<div class="avatar avatar-game" style="background:linear-gradient(135deg, var(--cyan), var(--gold));">
<svg class="icon-lg" style="color:var(--text-1)"><use href="/public/icons/sprite.svg#icon-users"></use></svg>
</div>
<div>
<p class="lobby-card-title-sm">لعب محلي</p>
<p class="text-muted text-sm">لاعبين على نفس الجهاز</p>
</div>
</div>
<div>
<label class="input-label">عدد اللاعبين</label>
<div class="tab-group" id="local-count-tabs">
<button class="tab active" data-count="2">2 لاعبين</button>
<button class="tab" data-count="4">4 لاعبين</button>
</div>
</div>
<button class="btn btn-gold btn-block btn-lg" onclick="startLocal()">
<svg class="icon"><use href="/public/icons/sprite.svg#icon-play"></use></svg>
ابدأ اللعب
</button>
</div>
</div>
<!-- VS Bot -->
<div class="card lobby-card">
<div class="card-body space-y-4">
<div class="lobby-card-header">
<div class="avatar avatar-game" style="background:var(--bg-3);">
<svg class="icon-lg" style="color:var(--cyan)"><use href="/public/icons/sprite.svg#icon-bot"></use></svg>
</div>
<div>
<p class="lobby-card-title-sm">ضد البوت</p>
<p class="text-muted text-sm">العب ضد بوتات ذكية</p>
</div>
</div>
<div>
<label class="input-label">الوضع</label>
<div class="tab-group" id="bot-mode-tabs">
<button class="tab active" data-mode="2p">1 ضد 1</button>
<button class="tab" data-mode="4p_teams">فرق (2 ضد 2)</button>
</div>
</div>
<div>
<label class="input-label">الصعوبة</label>
<div class="tab-group" id="bot-diff-tabs">
<button class="tab" data-diff="easy">سهل</button>
<button class="tab active" data-diff="medium">متوسط</button>
<button class="tab" data-diff="hard">صعب</button>
</div>
</div>
<button class="btn btn-gold btn-block btn-lg" onclick="startBot()">
<svg class="icon"><use href="/public/icons/sprite.svg#icon-play"></use></svg>
ابدأ اللعب
</button>
</div>
</div>
<!-- Multiplayer -->
<div class="card lobby-card lobby-card--featured">
<div class="card-body space-y-4">
<div class="lobby-card-header">
<div class="avatar avatar-game" style="background:linear-gradient(135deg, var(--gold), var(--cyan));">
<svg class="icon-lg" style="color:var(--text-1)"><use href="/public/icons/sprite.svg#icon-sword"></use></svg>
</div>
<div>
<p class="lobby-card-title">ضد لاعب حقيقي</p>
<p class="text-muted text-sm">العب اونلاين ضد لاعبين حقيقيين</p>
</div>
</div>
<div>
<label class="input-label">الوضع</label>
<div class="tab-group" id="mp-mode-tabs">
<button class="tab active" data-mode="2p">1 ضد 1</button>
<button class="tab" data-mode="4p_teams">فرق (2 ضد 2)</button>
</div>
</div>
<button class="btn btn-gold btn-block btn-lg" onclick="startMatchmaking()">
<svg class="icon"><use href="/public/icons/sprite.svg#icon-search"></use></svg>
ابحث عن خصم
</button>
</div>
</div>
<!-- Private Room -->
<div class="card lobby-card">
<div class="card-body space-y-4">
<div class="lobby-card-header">
<div class="avatar avatar-game" style="background:var(--bg-3);">
<svg class="icon-lg" style="color:var(--gold)"><use href="/public/icons/sprite.svg#icon-lock"></use></svg>
</div>
<div>
<p class="lobby-card-title-sm">غرفة خاصة</p>
<p class="text-muted text-sm">انشئ غرفة وادعو اصحابك</p>
</div>
</div>
<div class="lobby-btn-pair">
<button class="btn btn-gold" onclick="createRoom()">انشئ غرفة</button>
<button class="btn btn-ghost" onclick="showJoinRoom()">انضم بكود</button>
</div>
<div id="join-room-form" style="display:none;">
<div class="lobby-code-row">
<input type="text" class="input" id="room-code-input" placeholder="ادخل كود الغرفة" maxlength="6" style="text-transform:uppercase;letter-spacing:4px;text-align:center;">
<button class="btn btn-gold" onclick="joinRoom()">دخول</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.tab-group').forEach(function(group) {
group.querySelectorAll('.tab').forEach(function(tab) {
tab.addEventListener('click', function() {
group.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
tab.classList.add('active');
});
});
});
// Load config from games API
App.cachedFetch('/api/games.php', 60000).then(function(data) {
if (!data || !data.games) return;
var domino = data.games.find(function(g) { return g.game_key === 'domino'; });
if (!domino || !domino.config || !domino.config.difficulties) return;
var diffTabs = document.getElementById('bot-diff-tabs');
if (!diffTabs) return;
diffTabs.innerHTML = domino.config.difficulties.map(function(d, i) {
return '<button class="tab' + (i === 1 ? ' active' : '') + '" data-diff="' + d.id + '">' + d.label + '</button>';
}).join('');
diffTabs.querySelectorAll('.tab').forEach(function(tab) {
tab.addEventListener('click', function() {
diffTabs.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
tab.classList.add('active');
});
});
}).catch(function() {});
});
function startLocal() {
var count = document.querySelector('#local-count-tabs .tab.active').dataset.count;
var mode = count === '4' ? '4p_teams' : '2p';
window.location.href = '/domino-game?mode=' + mode + '&type=local&players=' + count;
}
function startBot() {
var mode = document.querySelector('#bot-mode-tabs .tab.active').dataset.mode;
var diff = document.querySelector('#bot-diff-tabs .tab.active').dataset.diff;
var count = mode === '4p_teams' ? 4 : 2;
window.location.href = '/domino-game?mode=' + mode + '&type=bot&players=' + count + '&difficulty=' + diff;
}
function startMatchmaking() {
if (!App.isLoggedIn()) { window.location.href = '/login'; return; }
var mode = document.querySelector('#mp-mode-tabs .tab.active').dataset.mode;
window.location.href = '/domino-matchmaking?mode=' + mode;
}
function createRoom() {
if (!App.isLoggedIn()) { window.location.href = '/login'; return; }
window.location.href = '/domino-live?action=create';
}
function showJoinRoom() {
var form = document.getElementById('join-room-form');
form.style.display = form.style.display === 'none' ? 'block' : 'none';
if (form.style.display === 'block') {
document.getElementById('room-code-input').focus();
}
}
function joinRoom() {
if (!App.isLoggedIn()) { window.location.href = '/login'; return; }
var code = document.getElementById('room-code-input').value.trim().toUpperCase();
if (!code || code.length < 4) {
App.toast('ادخل كود صحيح', 'error');
return;
}
window.location.href = '/domino-live?action=join&code=' + code;
}
</script>
<?php require __DIR__ . '/../includes/footer.php'; ?>
This diff is collapsed.
var DominoBot = (function() {
'use strict';
function choosePlay(hand, ends, board, difficulty, moves) {
var validPlays = DominoConstants.getValidPlays(hand, ends);
if (validPlays.length === 0) return null;
if (difficulty === 'easy') {
return validPlays[Math.floor(Math.random() * validPlays.length)];
}
if (difficulty === 'medium') {
// Prefer highest pip count tiles to get rid of heavy ones
validPlays.sort(function(a, b) {
return (b.tile.left + b.tile.right) - (a.tile.left + a.tile.right);
});
return validPlays[0];
}
// Hard: strategic scoring
var bestScore = -999;
var bestPlay = validPlays[0];
for (var i = 0; i < validPlays.length; i++) {
var play = validPlays[i];
var score = scorePlay(play, hand, ends, board, moves);
if (score > bestScore) {
bestScore = score;
bestPlay = play;
}
}
return bestPlay;
}
function scorePlay(play, hand, ends, board, moves) {
var score = 0;
var tile = play.tile;
// 1. Prefer high-pip tiles (shed weight early)
score += (tile.left + tile.right) * 2;
// 2. Play doubles early (they're harder to place later)
if (DominoConstants.isDouble(tile)) score += 8;
// 3. Keep variety — count unique values remaining after this play
var remaining = hand.filter(function(t) { return t.id !== tile.id; });
var values = {};
for (var r = 0; r < remaining.length; r++) {
values[remaining[r].left] = true;
values[remaining[r].right] = true;
}
score += Object.keys(values).length * 3;
// 4. Try to play values that opponents might be short on
var opponentDraws = countOpponentDraws(moves);
var exposedEnd = DominoConstants.getExposedEnd(tile, ends[play.side]);
if (DominoConstants.isDouble(tile)) exposedEnd = tile.left;
// If we expose a value opponents drew on (couldn't match), that's good (blocking)
if (opponentDraws[exposedEnd]) score += opponentDraws[exposedEnd] * 4;
// 5. Avoid leaving yourself with a single value type
var valueCounts = {};
for (var v = 0; v < remaining.length; v++) {
valueCounts[remaining[v].left] = (valueCounts[remaining[v].left] || 0) + 1;
valueCounts[remaining[v].right] = (valueCounts[remaining[v].right] || 0) + 1;
}
var maxConcentration = 0;
for (var k in valueCounts) {
if (valueCounts[k] > maxConcentration) maxConcentration = valueCounts[k];
}
score -= maxConcentration * 2;
return score;
}
function countOpponentDraws(moves) {
var draws = {};
if (!moves) return draws;
for (var i = 0; i < moves.length; i++) {
var move = moves[i];
if (move.action === 'draw' || move.action === 'pass') {
// After a draw/pass, the board ends at that moment tell us what the opponent couldn't match
// Simplified: track which values forced passes
if (i > 0 && moves[i-1].action === 'play' && moves[i-1].tile) {
var prevTile = moves[i-1].tile;
var exposed = prevTile.right;
draws[exposed] = (draws[exposed] || 0) + 1;
}
}
}
return draws;
}
function chooseFirstTile(hand, difficulty) {
// Always prefer [6|6], then highest double, then highest pip
var doubles = hand.filter(function(t) { return DominoConstants.isDouble(t); });
if (doubles.length > 0) {
doubles.sort(function(a, b) { return b.left - a.left; });
return doubles[0];
}
var sorted = hand.slice().sort(function(a, b) {
return (b.left + b.right) - (a.left + a.right);
});
return sorted[0];
}
function shouldDraw(hand, ends) {
return !DominoConstants.canPlayerPlay(hand, ends);
}
return {
choosePlay: choosePlay,
chooseFirstTile: chooseFirstTile,
shouldDraw: shouldDraw
};
})();
var DominoConstants = (function() {
'use strict';
// Pip positions for each value (0-6) on a tile half
// Grid: 3x3, positions numbered 0-8 (top-left to bottom-right)
// 0 1 2
// 3 4 5
// 6 7 8
var PIP_POSITIONS = {
0: [],
1: [4],
2: [2, 6],
3: [2, 4, 6],
4: [0, 2, 6, 8],
5: [0, 2, 4, 6, 8],
6: [0, 2, 3, 5, 6, 8]
};
function generateAllTiles() {
var tiles = [];
for (var i = 0; i <= 6; i++) {
for (var j = i; j <= 6; j++) {
tiles.push({ id: 'tile_' + i + '_' + j, left: i, right: j });
}
}
return tiles;
}
function shuffleTiles(tiles) {
var arr = tiles.slice();
for (var i = arr.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
return arr;
}
function dealTiles(mode, playerCount) {
var tiles = shuffleTiles(generateAllTiles());
var hands = {};
for (var p = 0; p < playerCount; p++) {
hands[p] = tiles.splice(0, 7);
}
var boneyard = (mode === '2p') ? tiles : [];
return { hands: hands, boneyard: boneyard };
}
function findFirstPlayer(hands, roundNumber, previousWinner) {
if (roundNumber > 1 && previousWinner !== null) return previousWinner;
// Round 1: who has [6|6]?
for (var idx in hands) {
for (var t = 0; t < hands[idx].length; t++) {
if (hands[idx][t].left === 6 && hands[idx][t].right === 6) return parseInt(idx);
}
}
// Highest double
for (var d = 5; d >= 0; d--) {
for (var idx2 in hands) {
for (var t2 = 0; t2 < hands[idx2].length; t2++) {
if (hands[idx2][t2].left === d && hands[idx2][t2].right === d) return parseInt(idx2);
}
}
}
// Highest pip total
var best = -1, bestIdx = 0;
for (var idx3 in hands) {
for (var t3 = 0; t3 < hands[idx3].length; t3++) {
var total = hands[idx3][t3].left + hands[idx3][t3].right;
if (total > best) { best = total; bestIdx = parseInt(idx3); }
}
}
return bestIdx;
}
function isDouble(tile) {
return tile.left === tile.right;
}
function pipCount(hand) {
var total = 0;
for (var i = 0; i < hand.length; i++) {
total += hand[i].left + hand[i].right;
}
return total;
}
function tileMatchesEnd(tile, endValue) {
return tile.left === endValue || tile.right === endValue;
}
function getExposedEnd(tile, matchedValue) {
if (tile.left === matchedValue) return tile.right;
return tile.left;
}
function getPlayableEnds(board, spinner) {
if (!board || board.length === 0) return { any: true };
var ends = {};
var spinnerHasEast = false, spinnerHasWest = false;
for (var i = 0; i < board.length; i++) {
if (board[i].side === 'east') spinnerHasEast = true;
if (board[i].side === 'west') spinnerHasWest = true;
}
var eastChain = board.filter(function(e) { return e.side === 'east'; });
var westChain = board.filter(function(e) { return e.side === 'west'; });
var northChain = board.filter(function(e) { return e.side === 'north'; });
var southChain = board.filter(function(e) { return e.side === 'south'; });
if (eastChain.length > 0) {
ends.east = eastChain[eastChain.length - 1].exposed_end;
} else if (spinner) {
ends.east = spinner.left;
}
if (westChain.length > 0) {
ends.west = westChain[westChain.length - 1].exposed_end;
} else if (spinner) {
ends.west = spinner.left;
}
if (spinnerHasEast && spinnerHasWest && spinner) {
if (northChain.length > 0) {
ends.north = northChain[northChain.length - 1].exposed_end;
} else {
ends.north = spinner.left;
}
if (southChain.length > 0) {
ends.south = southChain[southChain.length - 1].exposed_end;
} else {
ends.south = spinner.left;
}
}
return ends;
}
function canPlayerPlay(hand, ends) {
if (ends.any) return true;
for (var i = 0; i < hand.length; i++) {
for (var side in ends) {
if (hand[i].left === ends[side] || hand[i].right === ends[side]) return true;
}
}
return false;
}
function getValidPlays(hand, ends) {
if (ends.any) {
return hand.map(function(t) { return { tile: t, side: 'any' }; });
}
var plays = [];
for (var i = 0; i < hand.length; i++) {
for (var side in ends) {
if (tileMatchesEnd(hand[i], ends[side])) {
plays.push({ tile: hand[i], side: side });
}
}
}
return plays;
}
return {
PIP_POSITIONS: PIP_POSITIONS,
generateAllTiles: generateAllTiles,
shuffleTiles: shuffleTiles,
dealTiles: dealTiles,
findFirstPlayer: findFirstPlayer,
isDouble: isDouble,
pipCount: pipCount,
tileMatchesEnd: tileMatchesEnd,
getExposedEnd: getExposedEnd,
getPlayableEnds: getPlayableEnds,
canPlayerPlay: canPlayerPlay,
getValidPlays: getValidPlays
};
})();
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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