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 { fly } from 'svelte/transition';
3 import { X } from 'lucide-svelte';
4 import MapCard from './MapCard.svelte';
5 import { square as store } from '$lib/stores';
6 import type { Writable } from 'svelte/store';
7 import ScoreList from './ScoreList.svelte';
8
9 export let tiebreaker: string;
10 export let gameStore: Writable<Bingo.Card | null>;
11
12 let square: Bingo.Card.FullSquare | null = null;
13 let sidebar: HTMLDivElement;
14 let transition = false;
15
16 store.subscribe(async (index) => {
17 if (index == null || $gameStore?.squares == null) {
18 square = null;
19 return;
20 }
21
22 square = $gameStore.squares[index];
23
24 square.scores.sort((a, b) => {
25 if (tiebreaker == 'accuracy') return b.accuracy - a.accuracy;
26 if (tiebreaker == 'combo') return b.max_combo - a.max_combo;
27 if (tiebreaker == 'pp') return (b.pp ?? 0) - (a.pp ?? 0);
28 return b.score - a.score;
29 });
30 square.scores.sort((a, b) => (b.claimworthy ? 1 : 0) - (a.claimworthy ? 1 : 0));
31 transition = !transition;
32 });
33 gameStore.subscribe((value) => {
34 if (!value) return;
35
36 const newSquare = value.squares?.find((x) => x.id == square?.id);
37 if (newSquare) {
38 square = newSquare;
39
40 square.scores.sort((a, b) => {
41 if (tiebreaker == 'accuracy') return b.accuracy - a.accuracy;
42 if (tiebreaker == 'combo') return b.max_combo - a.max_combo;
43 if (tiebreaker == 'pp') return (b.pp ?? 0) - (a.pp ?? 0);
44 return b.score - a.score;
45 });
46 square.scores.sort((a, b) => (b.claimworthy ? 1 : 0) - (a.claimworthy ? 1 : 0));
47 }
48 });
49 const close = () => {
50 $store = null;
51 };
52</script>
53
54{#if square != null}
55 <div
56 bind:this={sidebar}
57 class="group/sidebar relative size-full overflow-hidden rounded-xl bg-zinc-800 transition"
58 data-claimer={square.claimed_by?.team_name ?? 'UNCLAIMED'}
59 >
60 <div
61 class="absolute top-0 flex h-14 w-full items-center bg-gradient-to-b to-zinc-800 p-2 font-rounded text-xl transition group-data-[claimer=BLUE]/sidebar:from-blue-600 group-data-[claimer=RED]/sidebar:from-amber-600 group-data-[claimer=UNCLAIMED]/sidebar:from-zinc-600"
62 >
63 <button
64 on:click={close}
65 class="flex size-10 items-center justify-center rounded p-1 hover:bg-[rgba(0,0,0,0.5)]"
66 >
67 <X />
68 </button>
69 Back to team chat
70 </div>
71
72 <!-- This looks stupid, but is necessary for the transition to look the way it does -->
73 {#if transition}
74 <div in:fly={{ x: 100, duration: 300 }} out:fly={{ x: -100, duration: 150 }}>
75 <div class="absolute top-14 w-full">
76 <MapCard map={square.data} />
77 </div>
78
79 <ScoreList scores={square.scores} {tiebreaker} />
80 </div>
81 {:else}
82 <div in:fly={{ x: 100, duration: 300 }} out:fly={{ x: -100, duration: 150 }}>
83 <div class="absolute top-14 w-full">
84 <MapCard map={square.data} />
85 </div>
86
87 <ScoreList scores={square.scores} {tiebreaker} />
88 </div>
89 {/if}
90 </div>
91{/if}