even more changes

main
Antonio De Lucreziis 2 months ago
parent 2ad01a11a9
commit 043ac80784

Binary file not shown.

@ -19,6 +19,7 @@
"@paralleldrive/cuid2": "^2.2.2", "@paralleldrive/cuid2": "^2.2.2",
"astro": "^4.16.13", "astro": "^4.16.13",
"better-sqlite3": "^11.5.0", "better-sqlite3": "^11.5.0",
"clsx": "^2.1.1",
"nanoid": "^5.0.8", "nanoid": "^5.0.8",
"preact": "^10.24.3", "preact": "^10.24.3",
"typescript": "^5.6.3" "typescript": "^5.6.3"

@ -1,4 +1,5 @@
import type { Question, Scoreboard } from '@/ggwp' import type { Question, Scoreboard } from '@/ggwp'
import clsx from 'clsx'
type Props = { type Props = {
questions: Question[] questions: Question[]
@ -12,7 +13,7 @@ export const Leaderboard = ({ questions, scoreboard, selectTeamQuestion }: Props
<div <div
class="leaderboard" class="leaderboard"
style={{ style={{
'--team-count': scoreboard.length, '--team-count': scoreboard.board.length,
'--question-count': questions.length, '--question-count': questions.length,
}} }}
> >
@ -24,7 +25,7 @@ export const Leaderboard = ({ questions, scoreboard, selectTeamQuestion }: Props
))} ))}
</div> </div>
<div class="team-answers-container"> <div class="team-answers-container">
{scoreboard.map(({ team, totalScore, questionScores }, i) => ( {scoreboard.board.map(({ team, totalScore, questionScores }, i) => (
<div class="row" key={i}> <div class="row" key={i}>
<div class="team"> <div class="team">
<div class="name">{team}</div> <div class="name">{team}</div>
@ -33,11 +34,16 @@ export const Leaderboard = ({ questions, scoreboard, selectTeamQuestion }: Props
<div class="answers"> <div class="answers">
{questions.map((question, j) => ( {questions.map((question, j) => (
<div <div
class="answer" class={clsx(
'answer',
question.group === scoreboard.teamJollyGroup[team] && 'jolly'
)}
key={j} key={j}
onClick={() => { onClick={() => {
if (selectTeamQuestion) {
console.log('emitting', { team, question: question.id }) console.log('emitting', { team, question: question.id })
return selectTeamQuestion?.({ team, question: question.id }) selectTeamQuestion({ team, question: question.id })
}
}} }}
> >
{questionScores[question.id]} {questionScores[question.id]}

@ -1,17 +1,54 @@
import { requestJSON } from '@/client/utils'
import type { Room } from '@/db/model' import type { Room } from '@/db/model'
import { useEffect, useState } from 'preact/hooks'
import { Leaderboard } from './Leaderboard' import { Leaderboard } from './Leaderboard'
import { type Dispatch, type StateUpdater } from 'preact/hooks'
import { computeScoreboardState } from '@/ggwp' import { computeScoreboardState } from '@/ggwp'
type Props = { type Props = {
room: Room roomId: string
setRoom: Dispatch<StateUpdater<Room>> }
const useEventSource = (roomId: string, onMessage: (data: any) => void) => {
useEffect(() => {
const es = new EventSource(`/api/room/${roomId}?sse`)
es.onmessage = e => {
onMessage(JSON.parse(e.data))
}
selectTeamQuestion?: ({ team, question }: { team: string; question: string }) => void return () => {
es.close()
}
}, [roomId])
} }
export const LiveLeaderboard = ({ room, selectTeamQuestion }: Props) => { export const LiveLeaderboard = ({ roomId }: Props) => {
const [room, setRoom] = useState<Room | null>(null)
const fetchRoom = async () => {
await requestJSON(`/api/room/${roomId}`).then(room => {
setRoom({ id: roomId, ...room })
})
}
useEffect(() => {
fetchRoom()
}, [])
useEventSource(roomId, data => {
console.log('event', data)
fetchRoom()
})
if (!room) { if (!room) {
return <div>Loading...</div> return <div>Loading...</div>
} }
const scoreboard = computeScoreboardState(
{
teamIds: room.teams,
questions: room.questions,
},
room.actions
)
return <Leaderboard questions={room.questions} scoreboard={scoreboard} />
} }

@ -2,6 +2,7 @@ import cuid2 from '@paralleldrive/cuid2'
import Database from 'better-sqlite3' import Database from 'better-sqlite3'
import type { Room, RoomData } from './model' import type { Room, RoomData } from './model'
import type { Question } from '@/ggwp' import type { Question } from '@/ggwp'
import { emitRoomUpdate } from './events'
const db = new Database('ggwp.db') const db = new Database('ggwp.db')
db.pragma('journal_mode = WAL') db.pragma('journal_mode = WAL')
@ -54,6 +55,8 @@ export function createRoom(id: string, teams: string[], questions: Question[]):
} }
export function updateRoom(id: string, data: RoomData): void { export function updateRoom(id: string, data: RoomData): void {
emitRoomUpdate(id, data)
db.prepare<[string, string]>( db.prepare<[string, string]>(
` `
UPDATE rooms UPDATE rooms

@ -12,10 +12,13 @@ export type Game = {
} }
export type Scoreboard = { export type Scoreboard = {
teamJollyGroup: { [team: string]: string }
board: {
team: string team: string
totalScore: number totalScore: number
questionScores: { [id: string]: number } questionScores: { [id: string]: number }
}[] }[]
}
function getCorrectnessMultiplier(outcome: 'correct' | 'partial' | 'wrong'): number { function getCorrectnessMultiplier(outcome: 'correct' | 'partial' | 'wrong'): number {
switch (outcome) { switch (outcome) {
@ -101,8 +104,6 @@ export function computeScoreboardState(game: Game, rawActions: Action[]): Scoreb
}) })
} }
console.log(actionsByQuestion)
const scoreboardByTeam: { const scoreboardByTeam: {
[team: string]: { [team: string]: {
totalScore: number totalScore: number
@ -163,17 +164,20 @@ export function computeScoreboardState(game: Game, rawActions: Action[]): Scoreb
scoreboardByTeam[team].bonusJolly = teamBonus scoreboardByTeam[team].bonusJolly = teamBonus
} }
const scoreboard: Scoreboard = [] const scoreboard: Scoreboard = {
teamJollyGroup: teamsJollyGroup,
board: [],
}
for (const team of game.teamIds) { for (const team of game.teamIds) {
scoreboard.push({ scoreboard.board.push({
team, team,
totalScore: scoreboardByTeam[team].totalScore, totalScore: scoreboardByTeam[team].totalScore,
questionScores: scoreboardByTeam[team].questionScores, questionScores: scoreboardByTeam[team].questionScores,
}) })
} }
scoreboard.sort((a, b) => b.totalScore - a.totalScore) scoreboard.board.sort((a, b) => b.totalScore - a.totalScore)
return scoreboard return scoreboard
} }

@ -1,10 +1,35 @@
import { getRoom, updateRoom } from '@/db' import { getRoom, updateRoom } from '@/db'
import { addRoomUpdateListener, removeRoomUpdateListener } from '@/db/events'
import type { RoomData } from '@/db/model' import type { RoomData } from '@/db/model'
import type { APIRoute } from 'astro' import type { APIRoute } from 'astro'
async function sseHandler() { function sseHandler(roomId: string) {
// https://github.com/MicroWebStacks/astro-examples/blob/main/03_sse-counter/src/pages/api/stream.js let events_listener: () => void
return new Response('SSE not implemented', { status: 501 })
const stream = new ReadableStream({
start(controller) {
events_listener = () => {
const data = `data: ${JSON.stringify('room')}\r\n\r\n`
controller.enqueue(data)
}
removeRoomUpdateListener(roomId, events_listener)
addRoomUpdateListener(roomId, events_listener)
},
cancel() {
console.log('stream.js> cancel()')
removeRoomUpdateListener(roomId, events_listener)
},
})
return new Response(stream, {
status: 200,
headers: {
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
'Cache-Control': 'no-cache',
},
})
} }
export const GET: APIRoute = async ({ params, url }) => { export const GET: APIRoute = async ({ params, url }) => {
@ -14,7 +39,7 @@ export const GET: APIRoute = async ({ params, url }) => {
} }
if (url.searchParams.has('sse')) { if (url.searchParams.has('sse')) {
return await sseHandler() return sseHandler(roomId)
} }
const room = getRoom(roomId) const room = getRoom(roomId)

@ -452,6 +452,13 @@ button {
border-right: 2px solid #333; border-right: 2px solid #333;
border-bottom: 2px solid #333; border-bottom: 2px solid #333;
&.jolly {
/* background: oklch(from gold 1 0.15 h); */
color: oklch(from gold 0.2 0.2 h);
background: linear-gradient(-45deg, oklch(from gold 0.9 0.2 h), oklch(from gold 1 0.05 h));
}
&:first-child { &:first-child {
border-left: 2px solid #333; border-left: 2px solid #333;
} }

Loading…
Cancel
Save