154 lines
5.9 KiB
HTML
154 lines
5.9 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>Go Fish - Game</title>
|
||
|
|
<style>
|
||
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
|
body { font-family: system-ui, -apple-system, sans-serif; background: #1a1a2e; color: #e0e0e0; min-height: 100vh; }
|
||
|
|
.container { max-width: 800px; margin: 0 auto; padding: 2rem; }
|
||
|
|
h1 { text-align: center; margin-bottom: 1rem; color: #f0f0f0; }
|
||
|
|
.game-status { text-align: center; margin-bottom: 2rem; font-size: 1.2rem; }
|
||
|
|
.deck { text-align: center; margin: 2rem 0; }
|
||
|
|
.card { width: 70px; height: 100px; background: #1a1a2e; border: 2px solid #444; border-radius: 4px; margin: 2px; display: inline-block; position: relative; min-height: 100px; }
|
||
|
|
.card.flip { transition: transform 0.3s; transform: rotateY(180deg); }
|
||
|
|
.card.face { background: #e94560; color: white; }
|
||
|
|
.hand { min-height: 120px; }
|
||
|
|
.book-area { min-height: 80px; margin-top: 1rem; }
|
||
|
|
.book { width: 60px; height: 20px; background: #ff6b7a; color: white; border-radius: 3px; margin: 2px; display: inline-block; }
|
||
|
|
.controls { text-align: center; margin-top: 2rem; }
|
||
|
|
.controls button { margin: 0 0.5rem; }
|
||
|
|
.instruction { text-align: center; margin: 1rem 0; color: #888; }
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="container">
|
||
|
|
<h1>Go Fish</h1>
|
||
|
|
|
||
|
|
<div class="game-status" id="gameStatus" data-currentplayerid="{{ .Player.ID }}" data-opponentid="{{ .Game.ID }}" data-isbot="{{ .IsBot }}">
|
||
|
|
{{ .Player.Name }}'s turn - Ask for a rank
|
||
|
|
{{ if .IsBot }}(playing against a bot){{ end }}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="deck" id="deck">
|
||
|
|
<!-- Deck cards -->
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="hand" id="hand">
|
||
|
|
<!-- Player's hand cards -->
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="book-area" id="bookArea">
|
||
|
|
<!-- Booked sets -->
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="controls">
|
||
|
|
<button onclick="askRank('A')">Ask for A</button>
|
||
|
|
<button onclick="askRank('2')">Ask for 2</button>
|
||
|
|
<button onclick="askRank('3')">Ask for 3</button>
|
||
|
|
<button onclick="askRank('4')">Ask for 4</button>
|
||
|
|
<button onclick="askRank('5')">Ask for 5</button>
|
||
|
|
<button onclick="askRank('6')">Ask for 6</button>
|
||
|
|
<button onclick="askRank('7')">Ask for 7</button>
|
||
|
|
<button onclick="askRank('8')">Ask for 8</button>
|
||
|
|
<button onclick="askRank('9')">Ask for 9</button>
|
||
|
|
<button onclick="askRank('10')">Ask for 10</button>
|
||
|
|
<button onclick="askRank('J')">Ask for J</button>
|
||
|
|
<button onclick="askRank('Q')">Ask for Q</button>
|
||
|
|
<button onclick="askRank('K')">Ask for K</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="instruction" id="instruction">
|
||
|
|
Click a card in your hand to see its rank, then ask opponent for rank
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
let currentPlayerId = null;
|
||
|
|
let opponentId = null;
|
||
|
|
let myHand = [];
|
||
|
|
let myBooks = [];
|
||
|
|
let opponentBooks = [];
|
||
|
|
let isBot = false;
|
||
|
|
|
||
|
|
// Load game state from rendered template
|
||
|
|
document.addEventListener('DOMContentLoaded', function() {
|
||
|
|
const statusDiv = document.getElementById('gameStatus');
|
||
|
|
if (statusDiv) {
|
||
|
|
currentPlayerId = statusDiv.dataset.currentplayerid || null;
|
||
|
|
opponentId = statusDiv.dataset.opponentid || null;
|
||
|
|
isBot = statusDiv.dataset.isbot === 'true' || false;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
function askRank(rank) {
|
||
|
|
if (!currentPlayerId || !opponentId) return;
|
||
|
|
|
||
|
|
fetch('/ask', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
body: JSON.stringify({ playerId: currentPlayerId, opponentId: opponentId, rank: rank })
|
||
|
|
})
|
||
|
|
.then(r => r.json())
|
||
|
|
.then(data => {
|
||
|
|
updateGameState(data);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function updateGameState(data) {
|
||
|
|
myHand = data.myHand || [];
|
||
|
|
myBooks = data.myBooks || [];
|
||
|
|
opponentBooks = data.opponentBooks || [];
|
||
|
|
|
||
|
|
renderHand();
|
||
|
|
renderBooks();
|
||
|
|
renderStatus(data.status);
|
||
|
|
|
||
|
|
// Check if game over
|
||
|
|
if (data.gameOver) {
|
||
|
|
alert(data.message || 'Game over!');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderHand() {
|
||
|
|
const handDiv = document.getElementById('hand');
|
||
|
|
handDiv.innerHTML = '';
|
||
|
|
myHand.forEach((card, i) => {
|
||
|
|
const cardEl = document.createElement('div');
|
||
|
|
cardEl.className = 'card' + (i === 0 ? ' flip' : '');
|
||
|
|
cardEl.textContent = card;
|
||
|
|
cardEl.dataset.rank = card;
|
||
|
|
cardEl.onclick = function() { askRank(card); };
|
||
|
|
handDiv.appendChild(cardEl);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderBooks() {
|
||
|
|
const bookArea = document.getElementById('bookArea');
|
||
|
|
bookArea.innerHTML = '';
|
||
|
|
|
||
|
|
// My books
|
||
|
|
myBooks.forEach(rank => {
|
||
|
|
const book = document.createElement('div');
|
||
|
|
book.className = 'book';
|
||
|
|
book.textContent = rank + ' set';
|
||
|
|
bookArea.appendChild(book);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Opponent books
|
||
|
|
opponentBooks.forEach(rank => {
|
||
|
|
const book = document.createElement('div');
|
||
|
|
book.className = 'book';
|
||
|
|
book.textContent = rank + ' set (opp)';
|
||
|
|
bookArea.appendChild(book);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderStatus(status) {
|
||
|
|
const statusDiv = document.getElementById('gameStatus');
|
||
|
|
statusDiv.textContent = status || 'Your turn';
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|