initial commit

This commit is contained in:
Daniel Noulet 2026-10-07 15:16:00 +02:00
commit ac9aa86d33
14 changed files with 1021 additions and 0 deletions

154
web/templates/play.html Normal file
View file

@ -0,0 +1,154 @@
<!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>