Skip to content

Commit a74170e

Browse files
tryfix front-end deployment, add README images
1 parent 58262f2 commit a74170e

7 files changed

Lines changed: 53 additions & 29 deletions

File tree

README.md

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
# 1v1me
22

3-
A fast, simple platform for 1v1 minigames. Frontend is Next.js + Tailwind; backend is Spring Boot. The homepage offers a game selector; each game integrates with backend APIs over time.
3+
| <img src="preview1.png" alt="Preview 1" height="220"> | <img src="preview2.png" alt="Preview 2" height="220"> |
4+
|--------------------------|--------------------------|
5+
**Play now at [https://1v1me-flame.vercel.app](https://1v1me-flame.vercel.app)!**
6+
7+
Jump into quick, head-to-head minigames on a lightweight platform.
8+
Looking for an instant 1v1? 1v1me is ready when you are!
9+
Choose classics like tic-tac-toe or rock-paper-scissors and play in seconds!
10+
11+
Built with a Next.js + Tailwind frontend and a Spring Boot backend, the site lets you pick a username, jump into a queue, and connect to game APIs over REST.
12+
Grab your phone; every screen is mobile-ready.
413

514
## Tech Stack
615

7-
- Frontend: Next.js 14, TypeScript, Tailwind CSS
16+
- Frontend: Next.js 15, TypeScript, Tailwind CSS
817
- Backend: Java 21, Spring Boot 3, Maven
918
- Hosting: Vercel (frontend), Google Cloud Run (backend)
1019

@@ -26,11 +35,13 @@ A fast, simple platform for 1v1 minigames. Frontend is Next.js + Tailwind; backe
2635
Prereqs: Node.js 18+, Java 21, Maven
2736

2837
- Frontend
29-
- `cd frontend && npm install`
38+
- `cd frontend`
39+
- `npm install`
3040
- `npm run dev`http://localhost:3000
3141

3242
- Backend
33-
- `cd backend && mvn -B -ntp spring-boot:run`
43+
- `cd backend`
44+
- `mvn -B -ntp spring-boot:run`
3445
- API base: http://localhost:8080 (`GET /api/health`)
3546

3647
## Environment
@@ -47,17 +58,30 @@ Prereqs: Node.js 18+, Java 21, Maven
4758

4859
## Status
4960

50-
- Minigames are being added; backend endpoints will evolve. Frontend can operate with placeholders when backend is not ready.
61+
- Tic-tac-toe is fully functional with live frontend/backend integration.
62+
- Rock-paper-scissors needs fixes; targeting completion this week.
63+
- Goal: ship 100 minigames by month-end; backend endpoints and frontend placeholders will scale in parallel.
5164

5265
## Contributors / Thanks
5366

54-
- Alex Spaulding
55-
- Mekai Johnson
56-
- Liam Earl
57-
- Joel Sivanish
67+
<table>
68+
<tr>
69+
<td colspan="4" align="center">
70+
<img src="team.jpg" alt="Team Photo" width="100%">
71+
</td>
72+
</tr>
73+
<tr>
74+
<th>Liam Earl</th>
75+
<th>Alex Spaulding</th>
76+
<th>Mekai Johnson</th>
77+
<th>Joel Sivanish</th>
78+
</tr>
79+
</table>
5880

5981
## License
6082

83+
See [LICENSE](LICENSE) for details.
84+
6185
All rights reserved.
6286

6387
This is proprietary software. No permission is granted to use, copy, modify,

frontend/app/page.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"use client"
2-
import { useEffect, useState } from 'react'
2+
import { useCallback, useEffect, useState } from 'react'
33
import { useRouter } from 'next/navigation'
44

55
type Game = { type?: string } | null
@@ -53,15 +53,15 @@ export default function Home() {
5353
return () => clearInterval(interval)
5454
}, [])
5555

56-
const navigateToGame = (game: Game) => {
56+
const navigateToGame = useCallback((game: Game) => {
5757
const type = game?.type
5858
if (type === 'tictactoe') {
5959
// Username already stored in localStorage from registration
6060
router.push('/tictactoe')
6161
} else if (type === 'rockpaperscissors' || type === 'rps') {
6262
router.push('/rock-paper-scissors')
6363
}
64-
}
64+
}, [router])
6565

6666
useEffect(() => {
6767
if (!polling || !username) return
@@ -88,7 +88,7 @@ export default function Home() {
8888
}
8989
const interval = setInterval(poll, 2000)
9090
return () => clearInterval(interval)
91-
}, [polling, username, router])
91+
}, [polling, username, router, navigateToGame])
9292

9393
const registered = !!(username && users?.[username])
9494

frontend/app/tictactoe/page.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client"
22

3-
import { useEffect, useState } from "react"
3+
import { useCallback, useEffect, useState } from "react"
44
import { useRouter } from "next/navigation"
55

66
type GameState = {
@@ -25,7 +25,7 @@ export default function TicTacToe() {
2525
const [gameEnded, setGameEnded] = useState(false)
2626
const [redirectCountdown, setRedirectCountdown] = useState(10)
2727

28-
const updateGameState = (data: GameState) => {
28+
const updateGameState = useCallback((data: GameState) => {
2929
console.log('Updating game state:', { turn: data.turn, winner: data.winner, won: data.won, board: data.totalBoard })
3030
// Always update gameData first to trigger re-renders
3131
setGameData(data)
@@ -69,9 +69,9 @@ export default function TicTacToe() {
6969
console.log('Updating board state:', flatBoard)
7070
setBoard(flatBoard)
7171
}
72-
}
72+
}, [username, gameEnded])
7373

74-
const fetchGameState = async () => {
74+
const fetchGameState = useCallback(async () => {
7575
if (!username) return
7676
try {
7777
const res = await fetch(`/api/gamestate/${encodeURIComponent(username)}`, { cache: 'no-store' })
@@ -118,7 +118,7 @@ export default function TicTacToe() {
118118
} catch {
119119
setGameData(null)
120120
}
121-
}
121+
}, [username, router, gameEnded, updateGameState])
122122

123123
// Get username from localStorage on mount (stored during registration)
124124
useEffect(() => {
@@ -139,7 +139,7 @@ export default function TicTacToe() {
139139
if (username) {
140140
fetchGameState()
141141
}
142-
}, [username])
142+
}, [username, fetchGameState])
143143

144144
// Countdown timer when game ends
145145
useEffect(() => {
@@ -225,7 +225,7 @@ export default function TicTacToe() {
225225
console.log('Clearing polling interval')
226226
clearInterval(interval)
227227
}
228-
}, [username, gameData?.turn, gameData?.winner, gameData?.won, gameData?.usernameToTacNumber, gameEnded])
228+
}, [username, gameData, gameData?.turn, gameData?.winner, gameData?.won, gameData?.usernameToTacNumber, gameEnded, fetchGameState])
229229

230230
const handleCellClick = async (cellIndex: number) => {
231231
if (!username || !gameData || isMakingMove) {
@@ -353,13 +353,13 @@ export default function TicTacToe() {
353353

354354
<div className="mt-4 text-center text-sm sm:text-base">
355355
{gameWinner === 0 ? (
356-
<p>{currentTurn === myTacNumber ? "Your turn!" : "Opponent's turn"}</p>
356+
<p>{currentTurn === myTacNumber ? "Your turn!" : "Opponent&#39;s turn"}</p>
357357
) : gameWinner === -1 ? (
358358
<div>
359-
<p className="font-bold text-xl sm:text-2xl mb-2">Cat's game! 🐱 It's a tie!</p>
359+
<p className="font-bold text-xl sm:text-2xl mb-2">Cat&#39;s game! 🐱 It&#39;s a tie!</p>
360360
{gameEnded && (
361361
<p className="text-xs sm:text-sm text-gray-600 mt-2">
362-
Redirecting to homepage in {redirectCountdown} second{redirectCountdown !== 1 ? 's' : ''}...
362+
Redirecting to homepage in {redirectCountdown} second{redirectCountdown !== 1 ? "s" : ""}...
363363
</p>
364364
)}
365365
</div>
@@ -368,7 +368,7 @@ export default function TicTacToe() {
368368
<p className="font-bold text-xl sm:text-2xl mb-2">{gameWinner === myTacNumber ? "You won! 🎉" : "You lost! 😔"}</p>
369369
{gameEnded && (
370370
<p className="text-xs sm:text-sm text-gray-600 mt-2">
371-
Redirecting to homepage in {redirectCountdown} second{redirectCountdown !== 1 ? 's' : ''}...
371+
Redirecting to homepage in {redirectCountdown} second{redirectCountdown !== 1 ? "s" : ""}...
372372
</p>
373373
)}
374374
</div>

frontend/components/StatusTray.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"use client"
2-
import { useEffect, useState } from 'react'
2+
import { useCallback, useEffect, useState } from 'react'
33

44
type Status = { ok: boolean | null; code?: number }
55

@@ -9,24 +9,24 @@ export default function StatusTray() {
99
const [open, setOpen] = useState(false)
1010
const [status, setStatus] = useState<Record<string, Status>>({})
1111

12-
const checkEndpoint = async (path: string) => {
12+
const checkEndpoint = useCallback(async (path: string) => {
1313
try {
1414
const res = await fetch(path, { cache: 'no-store' })
1515
setStatus(s => ({ ...s, [path]: { ok: res.ok, code: res.status } }))
1616
} catch {
1717
setStatus(s => ({ ...s, [path]: { ok: false } }))
1818
}
19-
}
19+
}, [])
2020

21-
const refreshAll = () => endpoints.forEach(checkEndpoint)
21+
const refreshAll = useCallback(() => endpoints.forEach(checkEndpoint), [checkEndpoint])
2222

2323
useEffect(() => {
2424
if (open) {
2525
refreshAll()
2626
const id = setInterval(refreshAll, 30000)
2727
return () => clearInterval(id)
2828
}
29-
}, [open])
29+
}, [open, refreshAll])
3030

3131
return (
3232
<>

preview1.png

23.4 MB
Loading

preview2.png

719 KB
Loading

team.jpg

1.58 MB
Loading

0 commit comments

Comments
 (0)