feat: Initial project structure for WebTriviasMulti
- Backend: FastAPI + Python-SocketIO + SQLAlchemy - Models for categories, questions, game sessions, events - AI services for answer validation and question generation (Claude) - Room management with Redis - Game logic with stealing mechanics - Admin API for question management - Frontend: React + Vite + TypeScript + Tailwind - 5 visual themes (DRRR, Retro, Minimal, RGB, Anime 90s) - Real-time game with Socket.IO - Achievement system - Replay functionality - Sound effects per theme - Docker Compose for deployment - Design documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
175
frontend/src/hooks/useSocket.ts
Normal file
175
frontend/src/hooks/useSocket.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import { useEffect, useRef, useCallback } from 'react'
|
||||
import { io, Socket } from 'socket.io-client'
|
||||
import { useGameStore } from '../stores/gameStore'
|
||||
import type { GameRoom, ChatMessage, AnswerResult } from '../types'
|
||||
|
||||
const SOCKET_URL = import.meta.env.VITE_WS_URL || 'http://localhost:8000'
|
||||
|
||||
export function useSocket() {
|
||||
const socketRef = useRef<Socket | null>(null)
|
||||
const { setRoom, addMessage, setShowStealPrompt, setCurrentQuestion, setTimerEnd } =
|
||||
useGameStore()
|
||||
|
||||
useEffect(() => {
|
||||
// Create socket connection
|
||||
socketRef.current = io(SOCKET_URL, {
|
||||
transports: ['websocket', 'polling'],
|
||||
autoConnect: true,
|
||||
})
|
||||
|
||||
const socket = socketRef.current
|
||||
|
||||
// Connection events
|
||||
socket.on('connect', () => {
|
||||
console.log('Connected to server')
|
||||
})
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('Disconnected from server')
|
||||
})
|
||||
|
||||
socket.on('error', (data: { message: string }) => {
|
||||
console.error('Socket error:', data.message)
|
||||
})
|
||||
|
||||
// Room events
|
||||
socket.on('room_created', (data: { room: GameRoom }) => {
|
||||
setRoom(data.room)
|
||||
})
|
||||
|
||||
socket.on('player_joined', (data: { room: GameRoom }) => {
|
||||
setRoom(data.room)
|
||||
})
|
||||
|
||||
socket.on('player_left', (data: { room: GameRoom }) => {
|
||||
setRoom(data.room)
|
||||
})
|
||||
|
||||
socket.on('team_changed', (data: { room: GameRoom }) => {
|
||||
setRoom(data.room)
|
||||
})
|
||||
|
||||
// Game events
|
||||
socket.on('game_started', (data: { room: GameRoom }) => {
|
||||
setRoom(data.room)
|
||||
})
|
||||
|
||||
socket.on('question_selected', (data: { room: GameRoom; question_id: number }) => {
|
||||
setRoom(data.room)
|
||||
// Fetch full question details
|
||||
})
|
||||
|
||||
socket.on('answer_result', (data: AnswerResult) => {
|
||||
setRoom(data.room)
|
||||
if (!data.valid && !data.was_steal && data.room.can_steal) {
|
||||
setShowStealPrompt(true)
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('steal_attempted', (data: { room: GameRoom }) => {
|
||||
setRoom(data.room)
|
||||
setShowStealPrompt(false)
|
||||
})
|
||||
|
||||
socket.on('steal_passed', (data: { room: GameRoom }) => {
|
||||
setRoom(data.room)
|
||||
setShowStealPrompt(false)
|
||||
})
|
||||
|
||||
socket.on('time_up', (data: { room: GameRoom; was_steal: boolean }) => {
|
||||
setRoom(data.room)
|
||||
if (!data.was_steal && data.room.can_steal) {
|
||||
setShowStealPrompt(true)
|
||||
} else {
|
||||
setShowStealPrompt(false)
|
||||
}
|
||||
})
|
||||
|
||||
// Chat events
|
||||
socket.on('chat_message', (data: ChatMessage) => {
|
||||
addMessage(data)
|
||||
})
|
||||
|
||||
socket.on('emoji_reaction', (data: { player_name: string; team: string; emoji: string }) => {
|
||||
// Handle emoji reaction display
|
||||
console.log(`${data.player_name} reacted with ${data.emoji}`)
|
||||
})
|
||||
|
||||
return () => {
|
||||
socket.disconnect()
|
||||
}
|
||||
}, [setRoom, addMessage, setShowStealPrompt, setCurrentQuestion, setTimerEnd])
|
||||
|
||||
// Socket methods
|
||||
const createRoom = useCallback((playerName: string) => {
|
||||
socketRef.current?.emit('create_room', { player_name: playerName })
|
||||
}, [])
|
||||
|
||||
const joinRoom = useCallback((roomCode: string, playerName: string, team: 'A' | 'B') => {
|
||||
socketRef.current?.emit('join_room', {
|
||||
room_code: roomCode,
|
||||
player_name: playerName,
|
||||
team,
|
||||
})
|
||||
}, [])
|
||||
|
||||
const changeTeam = useCallback((team: 'A' | 'B') => {
|
||||
socketRef.current?.emit('change_team', { team })
|
||||
}, [])
|
||||
|
||||
const startGame = useCallback((board: Record<string, unknown>) => {
|
||||
socketRef.current?.emit('start_game', { board })
|
||||
}, [])
|
||||
|
||||
const selectQuestion = useCallback((questionId: number, categoryId: number) => {
|
||||
socketRef.current?.emit('select_question', {
|
||||
question_id: questionId,
|
||||
category_id: categoryId,
|
||||
})
|
||||
}, [])
|
||||
|
||||
const submitAnswer = useCallback(
|
||||
(answer: string, question: Record<string, unknown>, isSteal: boolean = false) => {
|
||||
socketRef.current?.emit('submit_answer', {
|
||||
answer,
|
||||
question,
|
||||
is_steal: isSteal,
|
||||
})
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const stealDecision = useCallback((attempt: boolean, questionId: number, answer?: string) => {
|
||||
socketRef.current?.emit('steal_decision', {
|
||||
attempt,
|
||||
question_id: questionId,
|
||||
answer,
|
||||
})
|
||||
}, [])
|
||||
|
||||
const sendChatMessage = useCallback((message: string) => {
|
||||
socketRef.current?.emit('chat_message', { message })
|
||||
}, [])
|
||||
|
||||
const sendEmojiReaction = useCallback((emoji: string) => {
|
||||
socketRef.current?.emit('emoji_reaction', { emoji })
|
||||
}, [])
|
||||
|
||||
const notifyTimerExpired = useCallback(() => {
|
||||
socketRef.current?.emit('timer_expired', {})
|
||||
}, [])
|
||||
|
||||
return {
|
||||
socket: socketRef.current,
|
||||
createRoom,
|
||||
joinRoom,
|
||||
changeTeam,
|
||||
startGame,
|
||||
selectQuestion,
|
||||
submitAnswer,
|
||||
stealDecision,
|
||||
sendChatMessage,
|
||||
sendEmojiReaction,
|
||||
notifyTimerExpired,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user