Skip to content

Commit d9fab77

Browse files
Update index.html
1 parent 6e871a8 commit d9fab77

File tree

1 file changed

+122
-102
lines changed

1 file changed

+122
-102
lines changed

index.html

Lines changed: 122 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@
3434
.game-wrapper { transition: opacity 0.5s ease-in-out; position: relative; }
3535
.status { font-size: 1.5em; height: 30px; margin-bottom: 15px;}
3636

37-
.cell { border-radius: 10px; cursor: pointer; display: flex; justify-content: center; align-items: center; font-weight: bold; }
37+
.cell { border-radius: 10px; display: flex; justify-content: center; align-items: center; font-weight: bold; transition: opacity 0.3s ease-in-out; cursor: not-allowed; }
3838
.cell.x { color: var(--color-primary); text-shadow: 0 0 10px var(--color-primary); }
3939
.cell.o { color: var(--color-danger); text-shadow: 0 0 10px var(--color-danger); }
4040

4141
.initial-board { display: grid; grid-template-columns: repeat(3, 100px); gap: 10px; }
42-
.initial-board .cell { width: 100px; height: 100px; font-size: 4em; border: 2px solid var(--color-secondary); box-shadow: 0 0 10px var(--color-secondary); }
42+
.initial-board .cell { width: 100px; height: 100px; font-size: 4em; border: 2px solid var(--color-secondary); box-shadow: 0 0 10px var(--color-secondary); cursor: pointer; }
4343

4444
.ultimate-board { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; }
4545
.local-board {
@@ -48,21 +48,19 @@
4848
border-radius: 10px;
4949
transition: transform 0.3s ease-in-out, box-shadow 0.3s ease-in-out;
5050
}
51-
/* NEW: Active board scaling */
5251
.local-board.active {
5352
transform: scale(1.08);
5453
z-index: 10;
5554
box-shadow: 0 0 25px var(--color-secondary);
5655
}
5756
.local-board .cell { width: 60px; height: 60px; font-size: 2.5em; background: var(--color-surface); }
57+
.local-board.active .cell:not(.x):not(.o) { cursor: pointer; }
5858

59-
/* NEW: Winner overlay icon */
6059
.winner-overlay {
6160
position: absolute; top: 0; left: 0; right: 0; bottom: 0;
6261
font-size: 8em; font-weight: bold;
6362
display: flex; justify-content: center; align-items: center;
64-
pointer-events: none;
65-
opacity: 0;
63+
pointer-events: none; opacity: 0;
6664
transition: opacity 0.3s ease-in-out;
6765
}
6866
.local-board.won .winner-overlay { opacity: 1; }
@@ -78,9 +76,9 @@
7876
</head>
7977
<body>
8078
<div class="container">
81-
<!-- Header with description restored -->
79+
<!-- DESCRIPTION RESTORED -->
8280
<h1>DeveloperCrcodile</h1>
83-
<p>A developer-in-training, exploring the world of self-hosting and servers. You can't beat the AI in the first game, but see what happens when you try.</p>
81+
<p>A developer-in-training, exploring the world of self-hosting and servers. Try to beat my AI in a game of Tic-Tac-Toe below!</p>
8482
<a href="https://github.com/DeveloperCrcodiles" target="_blank" class="github-button">View My Projects on GitHub</a>
8583

8684
<h2 class="status"></h2>
@@ -91,10 +89,9 @@ <h2 class="status"></h2>
9189
</div>
9290
</div>
9391

94-
<div id="rules-modal" class="modal-overlay"> <!-- Rules Modal HTML is unchanged --> </div>
92+
<div id="rules-modal" class="modal-overlay"> <!-- Rules Modal is unchanged --> </div>
9593

9694
<script>
97-
// --- All JavaScript logic from the previous version is here, with key updates ---
9895
const gameWrapper = document.getElementById('game-wrapper');
9996
const statusDisplay = document.querySelector('.status');
10097
const restartButton = document.getElementById('restart-button');
@@ -105,8 +102,10 @@ <h2 class="status"></h2>
105102
const HUMAN = 'X', AI = 'O';
106103
const WIN_CONDITIONS = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
107104
let gameActive, isUltimateMode, initialBoardState, localBoards, mainBoardState, activeBoardIndex, initialAiLastMove;
105+
let aiTurnTimeout = null;
108106

109107
function initGame(startAsUltimate = false) {
108+
if (aiTurnTimeout) clearTimeout(aiTurnTimeout);
110109
gameActive = true;
111110
isUltimateMode = startAsUltimate;
112111
statusDisplay.textContent = `Your Turn (${HUMAN})`;
@@ -123,10 +122,23 @@ <h2 class="status"></h2>
123122
}, 500);
124123
}
125124

126-
function setupInitialBoard() { /* Unchanged */ }
125+
function setupInitialBoard() {
126+
if(rulesButton) rulesButton.style.display = 'none';
127+
const board = document.createElement('div');
128+
board.className = 'initial-board';
129+
initialBoardState = Array(9).fill('');
130+
for (let i=0; i<9; i++) {
131+
const cell = document.createElement('div');
132+
cell.className = 'cell';
133+
cell.dataset.index = i;
134+
cell.addEventListener('click', handleInitialClick);
135+
board.appendChild(cell);
136+
}
137+
gameWrapper.appendChild(board);
138+
}
127139

128140
function setupUltimateBoard(previousGameState) {
129-
rulesButton.style.display = 'inline-block';
141+
if(rulesButton) rulesButton.style.display = 'inline-block';
130142
localBoards = Array(9).fill(null).map(() => Array(9).fill(''));
131143
mainBoardState = Array(9).fill('');
132144
localBoards[4] = previousGameState;
@@ -150,7 +162,6 @@ <h2 class="status"></h2>
150162
}
151163
localBoardEl.appendChild(cell);
152164
}
153-
// Add winner overlay if board is already won
154165
if (mainBoardState[i]) {
155166
handleLocalWin(localBoardEl, mainBoardState[i]);
156167
}
@@ -159,108 +170,92 @@ <h2 class="status"></h2>
159170
gameWrapper.appendChild(board);
160171

161172
activeBoardIndex = initialAiLastMove;
173+
if(mainBoardState[activeBoardIndex] !== '') {
174+
activeBoardIndex = null;
175+
}
162176
updateActiveBoardHighlights();
163177
}
164-
165-
// NEW: Function to handle the visual state of a won local board
178+
166179
function handleLocalWin(localBoardElement, winner) {
167180
localBoardElement.classList.add('won');
168-
const overlay = document.createElement('div');
169-
overlay.className = `winner-overlay ${winner.toLowerCase()}`;
170-
overlay.textContent = winner;
171-
localBoardElement.appendChild(overlay);
181+
if (winner !== 'draw') {
182+
const overlay = document.createElement('div');
183+
overlay.className = `winner-overlay ${winner.toLowerCase()}`;
184+
overlay.textContent = winner;
185+
localBoardElement.appendChild(overlay);
186+
}
172187
}
173188

174189
function updateActiveBoardHighlights() {
175190
document.querySelectorAll('.local-board').forEach(board => {
176191
const bIndex = parseInt(board.dataset.boardIndex);
177-
board.classList.remove('active'); // Remove scaling from all boards
178-
if (gameActive && mainBoardState[bIndex] === '') {
192+
board.classList.remove('active');
193+
if (!gameActive) return;
194+
if (mainBoardState[bIndex] === '') {
179195
if (activeBoardIndex === null || activeBoardIndex === bIndex) {
180-
board.classList.add('active'); // Add scaling only to active ones
196+
board.classList.add('active');
181197
}
182198
}
183199
});
184200
}
185201

186-
function checkUltimateEnd(lastCellIndex, player) {
187-
const boardIndex = activeBoardIndex;
188-
if (boardIndex === null || mainBoardState[boardIndex] !== '') return false;
189-
190-
if (checkWinner(localBoards[boardIndex], player)) {
191-
mainBoardState[boardIndex] = player;
192-
handleLocalWin(gameWrapper.querySelector(`[data-board-index='${boardIndex}']`), player);
193-
}
194-
195-
activeBoardIndex = lastCellIndex;
196-
if (activeBoardIndex === null || mainBoardState[activeBoardIndex] !== '') {
197-
activeBoardIndex = null;
198-
}
199-
updateActiveBoardHighlights();
200-
201-
if (checkWinner(mainBoardState, player)) { endGame(false, player); return true; }
202-
if (mainBoardState.every(s => s !== '')) { endGame(false, 'draw'); return true; }
203-
return false;
204-
}
205-
206-
/* The rest of the JS functions (initial game logic, AI, etc.) are unchanged */
207-
function setupInitialBoard() {
208-
rulesButton.style.display = 'none';
209-
const board = document.createElement('div');
210-
board.className = 'initial-board';
211-
initialBoardState = Array(9).fill('');
212-
for (let i=0; i<9; i++) {
213-
const cell = document.createElement('div');
214-
cell.className = 'cell';
215-
cell.dataset.index = i;
216-
cell.addEventListener('click', handleInitialClick);
217-
board.appendChild(cell);
218-
}
219-
gameWrapper.appendChild(board);
220-
}
221202
function handleInitialClick(e) {
222203
if (!gameActive) return;
223204
const index = parseInt(e.target.dataset.index);
224205
if (initialBoardState[index] !== '') return;
225-
226206
makeMove(index, HUMAN);
227-
if (checkWinner(initialBoardState, HUMAN)) { endGame(true); return; }
228-
if (initialBoardState.every(c => c !== '')) { endGame(true); return; }
229-
207+
if (checkWinner(initialBoardState, HUMAN) || initialBoardState.every(c => c !== '')) {
208+
endGame(true);
209+
return;
210+
}
230211
gameActive = false;
231212
statusDisplay.textContent = 'AI is thinking...';
232-
setTimeout(initialAiTurn, 700);
233-
}
234-
function makeMove(index, player, board = null, boardIndex = null) {
235-
if (isUltimateMode) {
236-
board = localBoards[boardIndex];
237-
board[index] = player;
238-
const cell = gameWrapper.querySelector(`[data-board-index='${boardIndex}'][data-cell-index='${index}']`);
239-
cell.textContent = player;
240-
cell.classList.add(player.toLowerCase());
241-
} else {
242-
board = initialBoardState;
243-
board[index] = player;
244-
const cell = gameWrapper.querySelector(`[data-index='${index}']`);
245-
cell.textContent = player;
246-
cell.classList.add(player.toLowerCase());
247-
}
213+
aiTurnTimeout = setTimeout(initialAiTurn, 700);
248214
}
215+
249216
function handleUltimateClick(e) {
250217
if (!gameActive) return;
251218
const { boardIndex, cellIndex } = e.target.dataset;
252219
const bIndex = parseInt(boardIndex), cIndex = parseInt(cellIndex);
253-
254220
if (activeBoardIndex !== null && bIndex !== activeBoardIndex) return;
255221
if (localBoards[bIndex][cIndex] !== '' || mainBoardState[bIndex] !== '') return;
256-
257-
makeMove(cIndex, HUMAN, null, bIndex);
222+
223+
makeMove(cIndex, HUMAN, bIndex);
258224
if (checkUltimateEnd(cIndex, HUMAN)) return;
259225

260226
gameActive = false;
261227
statusDisplay.textContent = 'AI is thinking...';
262-
setTimeout(ultimateAiTurn, 700);
228+
aiTurnTimeout = setTimeout(ultimateAiTurn, 700);
263229
}
230+
231+
function makeMove(index, player, boardIndex = null) {
232+
let cell;
233+
if (isUltimateMode) {
234+
localBoards[boardIndex][index] = player;
235+
cell = gameWrapper.querySelector(`[data-board-index='${boardIndex}'][data-cell-index='${index}']`);
236+
} else {
237+
initialBoardState[index] = player;
238+
cell = gameWrapper.querySelector(`[data-index='${index}']`);
239+
}
240+
cell.textContent = player;
241+
cell.classList.add(player.toLowerCase());
242+
}
243+
244+
function endGame(isInitialGame, winner = 'draw') {
245+
gameActive = false;
246+
if (isInitialGame) {
247+
statusDisplay.textContent = "A worthy effort... but inevitable.";
248+
setTimeout(() => {
249+
statusDisplay.textContent = "Now, for the real challenge.";
250+
isUltimateMode = true;
251+
initGame(true);
252+
}, 2500);
253+
} else {
254+
statusDisplay.textContent = winner === 'draw' ? "It's a Draw!" : `${winner} Wins the Game!`;
255+
updateActiveBoardHighlights();
256+
}
257+
}
258+
264259
function initialAiTurn() {
265260
let bestScore = -Infinity, move;
266261
for (let i = 0; i < 9; i++) {
@@ -280,6 +275,7 @@ <h2 class="status"></h2>
280275
gameActive = true;
281276
statusDisplay.textContent = `Your Turn (${HUMAN})`;
282277
}
278+
283279
function minimax(board, isMaximizing) {
284280
if (checkWinner(board, HUMAN)) return -1;
285281
if (checkWinner(board, AI)) return 1;
@@ -295,29 +291,46 @@ <h2 class="status"></h2>
295291
}
296292
return bestScore;
297293
}
298-
function endGame(isInitialGame, winner = 'draw') {
299-
gameActive = false;
300-
if (isInitialGame) {
301-
statusDisplay.textContent = "A worthy effort... but inevitable.";
302-
setTimeout(() => {
303-
statusDisplay.textContent = "Now, let's play for real.";
304-
isUltimateMode = true;
305-
initGame(true);
306-
}, 2500);
307-
} else {
308-
statusDisplay.textContent = winner === 'draw' ? "It's a Draw!" : `${winner} Wins the Game!`;
309-
}
310-
}
294+
311295
function ultimateAiTurn() {
312296
const move = findBestUltimateMove();
313297
if (move) {
314-
makeMove(move.cellIndex, AI, null, move.boardIndex);
298+
makeMove(move.cellIndex, AI, move.boardIndex);
315299
if(checkUltimateEnd(move.cellIndex, AI)) return;
316-
} else { endGame(false, 'draw'); }
317-
300+
} else {
301+
endGame(false, 'draw');
302+
}
318303
gameActive = true;
319304
statusDisplay.textContent = `Your Turn (${HUMAN})`;
305+
// BUG FIX: Highlight is now updated AFTER the AI turn is fully complete.
306+
updateActiveBoardHighlights();
307+
}
308+
309+
function checkUltimateEnd(lastCellIndex, player) {
310+
const boardIndex = activeBoardIndex;
311+
// This logic is simplified; it relies on the fact that `handleUltimateClick` ensures the move is valid.
312+
if (mainBoardState[boardIndex] === '') {
313+
if (checkWinner(localBoards[boardIndex], player)) {
314+
mainBoardState[boardIndex] = player;
315+
handleLocalWin(gameWrapper.querySelector(`[data-board-index='${boardIndex}']`), player);
316+
} else if (localBoards[boardIndex].every(cell => cell !== '')) {
317+
mainBoardState[boardIndex] = 'draw';
318+
handleLocalWin(gameWrapper.querySelector(`[data-board-index='${boardIndex}']`), 'draw');
319+
}
320+
}
321+
322+
// This is the core state update that determines the next turn's highlight.
323+
activeBoardIndex = lastCellIndex;
324+
if (activeBoardIndex === null || mainBoardState[activeBoardIndex] !== '') {
325+
activeBoardIndex = null;
326+
}
327+
328+
if (checkWinner(mainBoardState, player)) { endGame(false, player); return true; }
329+
if (mainBoardState.every(s => s !== '')) { endGame(false, 'draw'); return true; }
330+
331+
return false;
320332
}
333+
321334
function findBestUltimateMove() {
322335
const moves = getValidMoves();
323336
if (moves.length === 0) return null;
@@ -327,24 +340,31 @@ <h2 class="status"></h2>
327340
});
328341
return moves[0];
329342
}
343+
330344
function getValidMoves() {
331345
const moves = [];
332346
const targetBoards = (activeBoardIndex === null)
333347
? [0,1,2,3,4,5,6,7,8].filter(i => mainBoardState[i] === '')
334348
: [activeBoardIndex];
335349
for (const bIndex of targetBoards) {
336-
for(let cIndex=0; cIndex<9; cIndex++) {
337-
if(localBoards[bIndex][cIndex] === '') moves.push({boardIndex: bIndex, cellIndex: cIndex});
350+
if(localBoards && localBoards[bIndex]) {
351+
for(let cIndex=0; cIndex<9; cIndex++) {
352+
if(localBoards[bIndex][cIndex] === '') moves.push({boardIndex: bIndex, cellIndex: cIndex});
353+
}
338354
}
339355
}
340356
return moves;
341357
}
358+
342359
function checkWinner(board, player) {
360+
if(!board) return false;
343361
return WIN_CONDITIONS.some(combo => combo.every(index => board[index] === player));
344362
}
345-
restartButton.addEventListener('click', () => initGame(isUltimateMode));
346-
rulesButton.addEventListener('click', () => rulesModal.classList.add('visible'));
347-
closeRulesButton.addEventListener('click', () => rulesModal.classList.remove('visible'));
363+
364+
if (restartButton) restartButton.addEventListener('click', () => initGame(isUltimateMode));
365+
if (rulesButton) rulesButton.addEventListener('click', () => rulesModal && rulesModal.classList.add('visible'));
366+
if (closeRulesButton) closeRulesButton.addEventListener('click', () => rulesModal && rulesModal.classList.remove('visible'));
367+
348368
initGame(false);
349369
</script>
350370
</body>

0 commit comments

Comments
 (0)