Two teams try and fill in any horizontal, vertical, or diagonal line on a bingo board by playing maps on osu! osu.bingo
osu
at microservice 137 lines 3.5 kB view raw
1<script lang="ts"> 2 import EventScore from './EventScore.svelte'; 3 import { fly } from 'svelte/transition'; 4 import EventTime from './EventTime.svelte'; 5 import EventStart from './EventStart.svelte'; 6 import { afterUpdate } from 'svelte'; 7 import { game as store, game_rules } from '$lib/stores'; 8 import { getEvents } from '$lib/gamerules/get_rules'; 9 import type { Options } from '$lib/gamerules/options'; 10 11 type Event = ScoreInfo | GameEvent | StartEvent; 12 type StartEvent = { 13 type: 'start'; 14 date: Date; 15 }; 16 const isStart = (e: Event): e is StartEvent => e.type == 'start'; 17 18 type GameEvent = { 19 id: string; 20 type: 'time'; 21 date: Date; 22 data: Options.Event; 23 }; 24 const isGameEvent = (e: Event): e is GameEvent => e.type == 'time'; 25 26 type ScoreInfo = { 27 type: 'score'; 28 date: Date; 29 data: { 30 score: Bingo.Card.FullScore; 31 square: Bingo.Card.FullSquare; 32 square_index: number; 33 }; 34 }; 35 const isScore = (e: Event): e is ScoreInfo => e.type == 'score'; 36 37 let events: Event[] = []; 38 store.subscribe((card) => { 39 if (!card || !card.squares) return; 40 41 // Sort stuff by date to add to list 42 const scores: { 43 score: Bingo.Card.FullScore; 44 square: Bingo.Card.FullSquare; 45 square_index: number; 46 }[] = []; 47 const time_events: Options.Event[] = []; 48 for (let i = 0; i < card.squares.length; i++) { 49 const square = card.squares[i]; 50 for (const score of square.scores) { 51 scores.push({ 52 score, 53 square, 54 square_index: i 55 }); 56 } 57 } 58 59 const eventList = getEvents(card) ?? []; 60 for (const event of eventList) { 61 time_events.push(event); 62 } 63 64 if (card.start_time != null && card.state != 0) { 65 const startEvent = events.find(isStart); 66 if (!startEvent) { 67 events.push({ 68 type: 'start', 69 date: card.start_time 70 }); 71 } 72 } 73 74 // Add new scores 75 const score_id_map = events.filter((x) => isScore(x)).map((x) => x.data.score.id); 76 for (const score of scores) { 77 if (!score_id_map.includes(score.score.id)) { 78 events.push({ 79 type: 'score', 80 date: score.score.date, 81 data: score 82 }); 83 } 84 } 85 86 const event_id_map = events.filter((x) => isGameEvent(x)).map((x) => x.id); 87 for (const event of time_events) { 88 const id = event.event + ((card.start_time?.valueOf() ?? 0) + event.seconds_after_start); 89 if (!event_id_map.includes(id)) { 90 events.push({ 91 id, 92 type: 'time', 93 date: new Date((card.start_time?.valueOf() ?? 0) + event.seconds_after_start * 1000), 94 data: event 95 }); 96 } 97 } 98 99 events.sort((a, b) => new Date(a.date).valueOf() - new Date(b.date).valueOf()); 100 101 // Because we haven't upgraded to svelte 5 yet, we have to do this to tell svelte to update the list 102 events = [...events]; 103 }); 104 105 // Autoscroll 106 let box: HTMLDivElement; 107 const scrollToBottom = () => { 108 if (!box) return; 109 110 box.scroll({ 111 top: box.scrollHeight, 112 behavior: 'smooth' 113 }); 114 }; 115 afterUpdate(scrollToBottom); 116</script> 117 118<div bind:this={box} class="flex size-full flex-col gap-2 overflow-x-hidden overflow-y-scroll pr-2"> 119 {#each events as event} 120 <div class="relative w-full" transition:fly={{ x: 30, duration: 1000 }}> 121 {#if isScore(event)} 122 <EventScore 123 score={event.data.score} 124 square={event.data.square} 125 square_index={event.data.square_index} 126 stat={$game_rules?.reclaim_condition ?? ''} 127 /> 128 {:else if isGameEvent(event)} 129 {#if new Date(event.date).valueOf() < new Date().valueOf()} 130 <EventTime event={event.data} /> 131 {/if} 132 {:else if isStart(event)} 133 <EventStart /> 134 {/if} 135 </div> 136 {/each} 137</div>