Skip to content

Commit 558c230

Browse files
committed
refactored api call
1 parent 09ab5f4 commit 558c230

2 files changed

Lines changed: 2 additions & 339 deletions

File tree

frontend/app/test/health/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default function HealthTestPage() {
2626
Test backend Spring API via <code>/api/health</code> and frontend API route at <code>/api/frontend-health</code>.
2727
</p>
2828

29-
{(['/api/health', '/api/frontend-health'] as const).map((endpoint, i) => {
29+
{(['/api/backend-health', '/api/frontend-health'] as const).map((endpoint, i) => {
3030
const state = i === 0 ? backend : frontend
3131
const setter = i === 0 ? setBackend : setFrontend
3232
return (

frontend/app/tictactoe/page.tsx

Lines changed: 1 addition & 338 deletions
Original file line numberDiff line numberDiff line change
@@ -25,101 +25,6 @@ export default function TicTacToe() {
2525
const [gameEnded, setGameEnded] = useState(false)
2626
const [redirectCountdown, setRedirectCountdown] = useState(10)
2727

28-
const updateGameState = useCallback((data: GameState) => {
29-
console.log('Updating game state:', { turn: data.turn, winner: data.winner, won: data.won, board: data.totalBoard })
30-
// Always update gameData first to trigger re-renders
31-
setGameData(data)
32-
if (data.usernameToTacNumber) {
33-
const entries = Object.entries(data.usernameToTacNumber)
34-
// Find opponent from usernameToTacNumber (current player is username)
35-
const myTacNumber = data.usernameToTacNumber[username] || 0
36-
entries.forEach(([name, num]) => {
37-
if (num !== myTacNumber && num !== 0) {
38-
setOpponent(name)
39-
}
40-
})
41-
}
42-
// Always update turn from gameData.turn
43-
if (data.turn !== undefined) {
44-
console.log('Setting turn to:', data.turn)
45-
setTurn(data.turn)
46-
}
47-
// winner is the integer (0 = ongoing, 1 or 2 = winner, -1 = cat's game/tie)
48-
const winnerValue = data.winner ?? (typeof data.won === 'number' ? data.won : 0)
49-
console.log('Game state update:', { winner: data.winner, won: data.won, winnerValue, turn: data.turn })
50-
setWinner(winnerValue)
51-
52-
// Check if game has ended (winner is not 0, including -1 for cat's game)
53-
if (winnerValue !== 0 && !gameEnded) {
54-
setGameEnded(true)
55-
setRedirectCountdown(10)
56-
}
57-
// Always update board state from backend, even if game ended
58-
if (data.totalBoard && Array.isArray(data.totalBoard) && data.totalBoard.length >= 3) {
59-
const flatBoard: string[] = []
60-
for (let row = 0; row < 3; row++) {
61-
if (data.totalBoard[row] && Array.isArray(data.totalBoard[row])) {
62-
for (let col = 0; col < 3; col++) {
63-
const cellValue = data.totalBoard[row][col]
64-
const cellIndex = row * 3 + col
65-
flatBoard[cellIndex] = cellValue === 1 ? "X" : cellValue === 2 ? "O" : ""
66-
}
67-
}
68-
}
69-
console.log('Updating board state:', flatBoard)
70-
setBoard(flatBoard)
71-
}
72-
}, [username, gameEnded])
73-
74-
const fetchGameState = useCallback(async () => {
75-
if (!username) return
76-
try {
77-
const res = await fetch(`/api/gamestate/${encodeURIComponent(username)}`, { cache: 'no-store' })
78-
if (!res.ok) {
79-
// If game ended and both users acknowledged, backend returns null
80-
if (res.status === 404 || res.status === 200) {
81-
const text = await res.text()
82-
if (!text || text === "null" || text.trim() === "") {
83-
// Game has ended and been removed, redirect to homepage
84-
console.log('Game ended, redirecting to homepage')
85-
setTimeout(() => {
86-
router.push('/?message=Game ended! Please queue for another match.')
87-
}, 2000)
88-
return
89-
}
90-
}
91-
setGameData(null)
92-
return
93-
}
94-
const text = await res.text()
95-
if (!text || text === "null" || text.trim() === "") {
96-
// Game has ended and been removed, but make sure we've shown the winner first
97-
if (gameEnded) {
98-
// Already showed winner, redirect after countdown
99-
return
100-
}
101-
// Game was removed before we saw the winner, redirect immediately
102-
console.log('Game ended (null response), redirecting to homepage')
103-
setTimeout(() => {
104-
router.push('/?message=Game ended! Please queue for another match.')
105-
}, 2000)
106-
return
107-
}
108-
try {
109-
const data: GameState = JSON.parse(text)
110-
if (data && (data.type || data.usernameToTacNumber || data.turn !== undefined)) {
111-
updateGameState(data)
112-
} else {
113-
setGameData(null)
114-
}
115-
} catch {
116-
setGameData(null)
117-
}
118-
} catch {
119-
setGameData(null)
120-
}
121-
}, [username, router, gameEnded, updateGameState])
122-
12328
// Get username from localStorage on mount (stored during registration)
12429
useEffect(() => {
12530
if (typeof window === 'undefined') return
@@ -133,249 +38,7 @@ export default function TicTacToe() {
13338
router.push('/')
13439
}
13540
}, [router])
136-
137-
// Fetch game state immediately when username is available
138-
useEffect(() => {
139-
if (username) {
140-
fetchGameState()
141-
}
142-
}, [username, fetchGameState])
143-
144-
// Countdown timer when game ends
145-
useEffect(() => {
146-
if (!gameEnded) return
147-
148-
setRedirectCountdown(10)
149-
const countdownInterval = setInterval(() => {
150-
setRedirectCountdown((prev) => {
151-
if (prev <= 1) {
152-
clearInterval(countdownInterval)
153-
router.push('/?message=Game ended! Please queue for another match.')
154-
return 0
155-
}
156-
return prev - 1
157-
})
158-
}, 1000)
159-
160-
return () => clearInterval(countdownInterval)
161-
}, [gameEnded, router])
162-
163-
// Poll game state continuously when it's not the player's turn
164-
useEffect(() => {
165-
if (!username || !gameData) return
166-
167-
const myTacNumber = gameData.usernameToTacNumber?.[username] || 0
168-
const currentTurn = gameData.turn || 0
169-
const isMyTurn = currentTurn === myTacNumber
170-
const gameWinner = gameData.winner ?? (typeof gameData.won === 'number' ? gameData.won : 0)
171-
const gameHasEnded = gameWinner !== 0
172-
173-
console.log('Polling check:', { myTacNumber, currentTurn, isMyTurn, gameHasEnded, gameEndedState: gameEnded, gameData })
174-
175-
// Continue polling even after game ends to ensure both players see the result
176-
// IMPORTANT: If game has ended, we MUST keep polling so both clients see the result
177-
// This is especially important for the winner who just made the winning move
178-
179-
// If game ended and we've already set gameEnded state, continue polling briefly to ensure both clients see the result
180-
if (gameHasEnded && gameEnded) {
181-
// Already showing winner, continue polling a few more times to ensure both clients sync, then stop
182-
let pollCount = 0
183-
const interval = setInterval(() => {
184-
pollCount++
185-
// Poll for 3 more seconds to ensure both clients have time to see the countdown
186-
if (pollCount >= 3) {
187-
clearInterval(interval)
188-
return
189-
}
190-
fetchGameState()
191-
}, 1000)
192-
return () => clearInterval(interval)
193-
}
194-
195-
// If game has ended but we haven't set gameEnded state yet, keep polling to get the update
196-
// This handles the case where the winner just made a move and the game ended
197-
// We MUST continue polling here to ensure the winner sees their win screen
198-
if (gameHasEnded && !gameEnded) {
199-
console.log('Game ended detected during polling, continuing to poll to get final state')
200-
// Continue polling to get the final state - don't return early
201-
fetchGameState()
202-
const interval = setInterval(() => {
203-
console.log('Polling for final game state with winner...')
204-
fetchGameState()
205-
// Stop after a few polls once we've gotten the state
206-
setTimeout(() => clearInterval(interval), 3000)
207-
}, 500) // Poll more frequently to catch the update
208-
return () => clearInterval(interval)
209-
}
210-
211-
// Only stop polling if it's the player's turn and game hasn't ended
212-
if (isMyTurn && !gameHasEnded) {
213-
console.log('Stopping polling - my turn and game not ended')
214-
return
215-
}
216-
217-
console.log('Starting polling - waiting for opponent or checking game end')
218-
// Fetch immediately, then poll every second
219-
fetchGameState()
220-
const interval = setInterval(() => {
221-
console.log('Polling for game state update...')
222-
fetchGameState()
223-
}, 1000)
224-
return () => {
225-
console.log('Clearing polling interval')
226-
clearInterval(interval)
227-
}
228-
}, [username, gameData, gameData?.turn, gameData?.winner, gameData?.won, gameData?.usernameToTacNumber, gameEnded, fetchGameState])
229-
230-
const handleCellClick = async (cellIndex: number) => {
231-
if (!username || !gameData || isMakingMove) {
232-
console.log('Early return:', { username: !!username, gameData: !!gameData, isMakingMove })
233-
return
234-
}
235-
if (board[cellIndex] !== "") {
236-
console.log('Cell already occupied:', cellIndex)
237-
return
238-
}
239-
240-
const myTacNumber = gameData.usernameToTacNumber?.[username] || 0
241-
if (gameData.turn !== myTacNumber) {
242-
console.log('Not your turn:', { turn: gameData.turn, myTacNumber })
243-
return
244-
}
245-
// Check if game ended: winner should be 0 if game is ongoing, 1/2 if someone won, -1 if cat's game
246-
const gameWinner = gameData.winner ?? (typeof gameData.won === 'number' ? gameData.won : 0)
247-
if (gameWinner !== 0) {
248-
console.log('Game already ended:', { winner: gameData.winner, won: gameData.won, gameWinner })
249-
return
250-
}
251-
252-
const row = Math.floor(cellIndex / 3)
253-
const col = cellIndex % 3
254-
255-
// Optimistically update the board immediately
256-
const newBoard = [...board]
257-
newBoard[cellIndex] = myTacNumber === 1 ? "X" : "O"
258-
setBoard(newBoard)
259-
console.log('Optimistically updated board:', newBoard)
260-
261-
setIsMakingMove(true)
262-
setError(null)
263-
264-
try {
265-
console.log('Making move:', { username, location: [row, col] })
266-
const res = await fetch(`/api/make_move/tictactoe/${encodeURIComponent(username)}`, {
267-
method: 'POST',
268-
headers: { 'Content-Type': 'application/json' },
269-
cache: 'no-store',
270-
body: JSON.stringify({ username, location: [row, col] }),
271-
})
272-
273-
if (!res.ok) {
274-
const text = await res.text()
275-
console.error('Move failed:', text || res.status)
276-
setError(`Move failed: ${text || res.status}`)
277-
// Revert optimistic update on failure
278-
setBoard(board)
279-
} else {
280-
console.log('Move successful, fetching game state')
281-
// Small delay to ensure backend has processed the move
282-
await new Promise(resolve => setTimeout(resolve, 150))
283-
// Fetch updated game state after making move - this will update board and winner
284-
await fetchGameState()
285-
// Fetch again after a short delay to ensure we get the final state with winner
286-
// This is important because the backend might need a moment to process win detection
287-
await new Promise(resolve => setTimeout(resolve, 200))
288-
await fetchGameState()
289-
// One more fetch to be absolutely sure we have the final state
290-
await new Promise(resolve => setTimeout(resolve, 200))
291-
await fetchGameState()
292-
}
293-
} catch (err) {
294-
console.error('Error making move:', err)
295-
setError(`Error making move: ${err instanceof Error ? err.message : 'Unknown error'}`)
296-
} finally {
297-
setIsMakingMove(false)
298-
}
299-
}
300-
301-
const myTacNumber = username && gameData && gameData.usernameToTacNumber ? gameData.usernameToTacNumber[username] || 0 : 0
302-
const currentTurn = gameData?.turn || 0
303-
const gameWinner = gameData?.winner ?? (typeof gameData?.won === 'number' ? gameData.won : 0)
304-
const canMakeMove = !isMakingMove && currentTurn === myTacNumber && gameWinner === 0 && gameData !== null
305-
306-
console.log('Render check:', { myTacNumber, currentTurn, canMakeMove, turn, gameDataTurn: gameData?.turn })
307-
308-
if (!username) {
309-
return (
310-
<div className="flex flex-col items-center justify-center min-h-screen bg-white text-black dark:bg-black dark:text-white p-6">
311-
<p>Loading...</p>
312-
</div>
313-
)
314-
}
315-
31641
return (
317-
<div className="flex flex-col items-center justify-center min-h-screen bg-white text-black dark:bg-black dark:text-white p-4 sm:p-6">
318-
<h1 className="text-2xl sm:text-4xl font-bold mb-4 text-center">Tic - Tac - Toe</h1>
319-
320-
{error && <p className="mb-4 text-red-600">{error}</p>}
321-
322-
{!gameData && (
323-
<p className="mb-4 text-lg">Loading game...</p>
324-
)}
325-
326-
{gameData && (
327-
<div className="rounded-2xl border-4 border-black dark:border-white p-4 sm:p-8 w-full max-w-[400px] flex flex-col items-center justify-between">
328-
<div className="grid grid-cols-3 grid-rows-3 gap-2 w-full max-w-[192px] sm:w-48 sm:h-48 aspect-square">
329-
{board.map((cell, i) => (
330-
<button
331-
key={i}
332-
onClick={() => handleCellClick(i)}
333-
disabled={!canMakeMove || cell !== ""}
334-
className={`border-2 border-black dark:border-white w-full h-full flex items-center justify-center text-3xl font-bold ${
335-
canMakeMove && cell === "" ? "cursor-pointer hover:bg-gray-100 active:bg-gray-200" : "cursor-not-allowed opacity-60"
336-
}`}
337-
>
338-
{cell}
339-
</button>
340-
))}
341-
</div>
342-
343-
<div className="flex justify-between w-full mt-4 sm:mt-6 text-sm sm:text-base">
344-
<div className="text-left">
345-
<p className="font-semibold underline">You ({myTacNumber === 1 ? "X" : "O"})</p>
346-
<p className="break-all">{username}</p>
347-
</div>
348-
<div className="text-right">
349-
<p className="font-semibold underline">Opponent ({myTacNumber === 1 ? "O" : "X"})</p>
350-
<p className="break-all">{opponent || "Waiting..."}</p>
351-
</div>
352-
</div>
353-
354-
<div className="mt-4 text-center text-sm sm:text-base">
355-
{gameWinner === 0 ? (
356-
<p>{currentTurn === myTacNumber ? "Your turn!" : "Opponent's turn"}</p>
357-
) : gameWinner === -1 ? (
358-
<div>
359-
<p className="font-bold text-xl sm:text-2xl mb-2">Cat&apos;s game! 🐱 It&apos;s a tie!</p>
360-
{gameEnded && (
361-
<p className="text-xs sm:text-sm text-gray-600 mt-2">
362-
Redirecting to homepage in {redirectCountdown} second{redirectCountdown !== 1 ? "s" : ""}...
363-
</p>
364-
)}
365-
</div>
366-
) : (
367-
<div>
368-
<p className="font-bold text-xl sm:text-2xl mb-2">{gameWinner === myTacNumber ? "You won! 🎉" : "You lost! 😔"}</p>
369-
{gameEnded && (
370-
<p className="text-xs sm:text-sm text-gray-600 mt-2">
371-
Redirecting to homepage in {redirectCountdown} second{redirectCountdown !== 1 ? "s" : ""}...
372-
</p>
373-
)}
374-
</div>
375-
)}
376-
</div>
377-
</div>
378-
)}
379-
</div>
42+
<></>
38043
)
38144
}

0 commit comments

Comments
 (0)