Two teams try and fill in any horizontal, vertical, or diagonal line on a bingo board by playing maps on osu!
osu.bingo
osu
1<script lang="ts">
2 import { getEvents } from '$lib/gamerules/get_rules';
3 import { getEventMeaning } from '$lib/gamerules/meaning';
4 import type { Options } from '$lib/gamerules/options';
5 import { game } from '$lib/stores';
6 import { BookText, Flag, Infinity, Timer } from 'lucide-svelte';
7
8 let gametime: number;
9 let progress: number;
10 let duration: number;
11 let minutes: number = 80;
12 let seconds: number = 8;
13
14 let cw: number;
15
16 let start_time: Date;
17 let end_time: Date;
18
19 let endless: boolean = false;
20
21 let events: Options.Event[] = [];
22 let last_event: Options.Event;
23
24 let interval: globalThis.Timer;
25
26 const update = () => {
27 const now = new Date().valueOf();
28 const start = start_time.valueOf();
29
30 gametime = now - start;
31 seconds = Math.floor((gametime / 1000) % 60);
32 minutes = Math.floor(gametime / 1000 / 60);
33
34 progress = gametime / duration;
35 };
36
37 game.subscribe((game) => {
38 if (!game || !game.start_time) return;
39
40 start_time = new Date(game.start_time);
41 events = getEvents(game);
42 endless = false;
43
44 let last_event_check = null;
45 for (const ev of events) {
46 if (last_event_check == null) {
47 last_event_check = ev;
48 continue;
49 }
50
51 if (ev.seconds_after_start > last_event_check.seconds_after_start) {
52 last_event_check = ev;
53 }
54 }
55 if (last_event_check == null) return;
56 last_event = last_event_check;
57 end_time = new Date(start_time.valueOf() + last_event.seconds_after_start * 1000);
58
59 // Padding is added to the right side of the timer
60 // if the event is not a "final" event, since there
61 // technically isn't an end
62 if (!last_event.event.startsWith('final')) {
63 end_time = new Date(end_time.valueOf() + 5 * 60 * 1000);
64 endless = true;
65 }
66 duration = end_time.valueOf() - start_time.valueOf();
67
68 clearInterval(interval);
69 interval = setInterval(update, 1000);
70 update();
71 });
72</script>
73
74<div class="mb-2 flex">
75 <div class="flex h-[50px] w-full rounded bg-base-800 p-2">
76 <div class="flex items-center pr-2">
77 <Timer />
78 </div>
79 <div class="h-full w-full">
80 <div class="relative h-full w-full">
81 <div
82 bind:clientWidth={cw}
83 class="absolute bottom-0 left-0 right-2 h-3 overflow-hidden rounded-full bg-zinc-900"
84 >
85 <div
86 class="absolute left-0 h-full rounded-full bg-pink-300"
87 style="width: calc(100% * {progress})"
88 ></div>
89 </div>
90 {#each events as event}
91 <div
92 class="absolute left-[--i] top-0 h-full"
93 style="--i: {(new Date(event.seconds_after_start * 1000).valueOf() / duration) * cw}px"
94 >
95 <div class="peer absolute left-[-25px] flex w-[50px] justify-center">
96 {#if event.event == 'finalcall'}
97 <Flag size={20} />
98 {:else}
99 <BookText size={20} />
100 {/if}
101 </div>
102 <div
103 class="invisible absolute -bottom-12 left-[-110px] z-20 w-[220px] rounded bg-zinc-900/80 p-2 text-sm opacity-0 backdrop-blur-sm transition peer-hover:visible peer-hover:opacity-100"
104 >
105 {getEventMeaning(event)}
106 </div>
107 </div>
108 {/each}
109 </div>
110 </div>
111 {#if endless}
112 <div class="relative flex items-center pl-2">
113 <div class="peer">
114 <Infinity />
115 </div>
116
117 <div
118 class="invisible absolute -bottom-12 left-[-110px] z-20 w-[220px] rounded bg-zinc-900/80 p-2 text-sm opacity-0 backdrop-blur-sm transition peer-hover:visible peer-hover:opacity-100"
119 >
120 This game does not have a end condition
121 </div>
122 </div>
123 {/if}
124 </div>
125 <div class="ml-2 flex h-full items-center rounded bg-base-800 p-2 font-mono font-bold">
126 {minutes < 10 ? '0' + minutes : minutes}:{seconds < 10 ? '0' + seconds : seconds}
127 </div>
128</div>