Skip to content

Commit

Permalink
Add optional scrubber to game controls.
Browse files Browse the repository at this point in the history
  • Loading branch information
bvanvugt committed Aug 26, 2023
1 parent d8f8ae0 commit 5e5e270
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 18 deletions.
42 changes: 42 additions & 0 deletions src/lib/components/Scrubber.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<script lang="ts">
import { playbackState } from '$lib/playback/stores';
// Range input properties
let min = 0;
let max = 10;
let step = 1;
let disabled = true;
// Scrubber value
let value = min;
// Enable the scrubber once the final frame is known
$: if (disabled && $playbackState && $playbackState.finalFrame) {
disabled = false;
max = $playbackState.finalFrame.turn;
}
// Update range value to reflect currently displayed frame
$: if (!disabled && $playbackState) {
value = $playbackState.frame.turn;
}
// Jump to frame on scrub event. Note that we can't use
// the bound `value` here because it hasn't updated yet.
function onScrub(e: Event) {
playbackState?.controls.jumpToFrame(e.target.value);
}
</script>

{#if $playbackState}
<input
class="w-full cursor-pointer disabled:cursor-not-allowed"
type="range"
{min}
{max}
{step}
{disabled}
on:input={onScrub}
bind:value
/>
{/if}
7 changes: 5 additions & 2 deletions src/lib/playback/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function httpToWsProtocol(url: string) {
}


export function fetchGame(fetchFunc: typeof fetch, gameID: string, engineURL: string, frames: Frame[], onFrameLoad: FrameCallback, onError: (message: string) => void) {
export function fetchGame(fetchFunc: typeof fetch, gameID: string, engineURL: string, frames: Frame[], onFrameLoad: FrameCallback, onFinalFrame: FrameCallback, onError: (message: string) => void) {
console.debug(`[playback] loading game ${gameID}`);

// Reset
Expand Down Expand Up @@ -59,8 +59,11 @@ export function fetchGame(fetchFunc: typeof fetch, gameID: string, engineURL: st

} else if (engineEvent.Type == 'game_end') {
console.debug('[playback] received final frame');
frames[frames.length - 1].isFinalFrame = true;
if (ws) ws.close();

// Flag last frame as the last one and fire callback
frames[frames.length - 1].isFinalFrame = true;
onFinalFrame(frames[frames.length - 1]);
}
};

Expand Down
18 changes: 16 additions & 2 deletions src/lib/playback/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ const controls = {
} else if (get(writableState)?.mode == PlaybackMode.PLAYING) {
controls.pause();
}
},
jumpToFrame: (i: number) => {
controls.pause();
setCurrentFrame(i);
}
}

Expand All @@ -136,7 +140,8 @@ const onFrameLoad = (frame: Frame) => {
if (frame.turn == settings.turn) {
writableState.set({
frame: frame,
mode: PlaybackMode.PAUSED
mode: PlaybackMode.PAUSED,
finalFrame: null,
});

setCurrentFrame(settings.turn);
Expand All @@ -146,6 +151,15 @@ const onFrameLoad = (frame: Frame) => {
}
}

const onFinalFrame = (frame: Frame) => {
writableState.update(($state) => {
if ($state) {
$state.finalFrame = frame;
}
return $state
});
}

const onEngineError = (message: string) => {
playbackError.set(message);
}
Expand All @@ -157,7 +171,7 @@ function createPlaybackState() {
subscribe: writableState.subscribe,
load: (fetchFunc: typeof fetch, s: Settings) => {
settings = { ...s }
fetchGame(fetchFunc, s.game, s.engine, frames, onFrameLoad, onEngineError);
fetchGame(fetchFunc, s.game, s.engine, frames, onFrameLoad, onFinalFrame, onEngineError);
},
reset,
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/playback/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type PlaybackHandler = () => void;
export type PlaybackState = {
frame: Frame,
mode: PlaybackMode,
finalFrame: null | Frame,
}

// We're lenient with typing data that's received from the game engine
Expand Down
10 changes: 10 additions & 0 deletions src/lib/settings/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export enum Setting {
LOOP = 'loop',
SHOW_CONTROLS = 'showControls',
SHOW_COORDS = 'showCoords',
SHOW_SCRUBBER = 'showScrubber',
SHOW_SCOREBOARD = 'showScoreboard',
THEME = 'theme',
TITLE = 'title',
Expand All @@ -41,6 +42,7 @@ export type Settings = {
loop: boolean,
showControls: boolean,
showCoords: boolean,
showScrubber: boolean,
showScoreboard: boolean,
theme: Theme,
title: string,
Expand All @@ -56,6 +58,7 @@ export function getDefaultSettings(): Settings {
loop: false,
showControls: true,
showCoords: false,
showScrubber: false,
showScoreboard: true,
theme: Theme.SYSTEM,
title: '',
Expand Down Expand Up @@ -83,6 +86,12 @@ showCoords.subscribe((value: boolean) => {
toLocalStorage(Setting.SHOW_COORDS, value);
});

// Show Turn Scrubber
export const showScrubber = writable<boolean>(fromLocalStorage(Setting.SHOW_SCRUBBER, getDefaultSettings().showScrubber));
showScrubber.subscribe((value: boolean) => {
toLocalStorage(Setting.SHOW_SCRUBBER, value);
});

// Theme
export const theme = writable<Theme>(fromLocalStorage(Setting.THEME, getDefaultSettings().theme));
theme.subscribe((value: Theme) => {
Expand All @@ -101,6 +110,7 @@ export function loadSettingsWithURLOverrides(url: URL): Settings {
autoplay: getBoolFromURL(url, Setting.AUTOPLAY, get(autoplay)),
fps: getIntFromURL(url, Setting.FPS, get(fps)),
showCoords: getBoolFromURL(url, Setting.SHOW_COORDS, get(showCoords)),
showScrubber: getBoolFromURL(url, Setting.SHOW_SCRUBBER, get(showScrubber)),
theme: getStringFromURL(url, Setting.THEME, get(theme)) as Theme,
// URL param controlled
engine: getStringFromURL(url, Setting.ENGINE, defaults.engine),
Expand Down
7 changes: 7 additions & 0 deletions src/routes/(iframed)/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
import Scoreboard from '$lib/components/Scoreboard.svelte';
import TooltipTemplateHotkeys from '$lib/components/TooltipTemplateHotkeys.svelte';
import TooltipTemplateSettings from '$lib/components/TooltipTemplateSettings.svelte';
import Scrubber from '$lib/components/Scrubber.svelte';
import IconCog from '~icons/heroicons/cog-8-tooth';
import IconHelp from '~icons/heroicons/question-mark-circle';
import type { PageData } from '../$types';
export let data: PageData;
const helpTooltipOptions = {
Expand Down Expand Up @@ -82,6 +84,11 @@
{/if}
<Gameboard showCoordinates={data.settings.showCoords} />
{#if data.settings.showControls}
{#if data.settings.showScrubber}
<div class="w-full px-[7.5%]">
<Scrubber />
</div>
{/if}
<div class="flex justify-evenly text-xl py-2 px-6">
<div use:tooltip={helpTooltipOptions}>
<IconHelp />
Expand Down
23 changes: 9 additions & 14 deletions src/routes/(iframed)/settings/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { autoplay, fps, showCoords, theme } from '$lib/settings/stores';
import { autoplay, fps, showCoords, showScrubber, theme } from '$lib/settings/stores';
const playbackSpeedOptions = [
{ value: 2, text: 'Slow' },
Expand Down Expand Up @@ -43,29 +43,24 @@
<input type="checkbox" bind:checked={$autoplay} />
<span class="font-bold mx-2">Autoplay</span>
</label>
<p class="text-sm">All games will start playing as soon as the game loads.</p>
<p class="text-sm">Start playback as soon as the first turn is ready.</p>
</div>

<div class="mb-4">
<label class="inline-flex items-center">
<input type="checkbox" bind:checked={$showCoords} />
<span class="font-bold mx-2">Display Coordinates</span>
<span class="font-bold mx-2">Board Coordinates</span>
</label>
<p class="text-sm">
Display coordinate labels on the game board. These go from 0 to the width/height of the board
to make it easier to debug games.
</p>
<p class="text-sm">Show coordinate labels on the game board.</p>
</div>

<!-- <div class="mb-4">
<div class="mb-4">
<label class="inline-flex items-center">
<input type="checkbox" />
<span class="font-bold mx-2">Frame Scrubber</span>
<input type="checkbox" bind:checked={$showScrubber} />
<span class="font-bold mx-2">Turn Scrubber</span>
</label>
<p class="text-sm">
Display progress bar that shows current frame and allows quickly jumping to specific frames.
</p>
</div> -->
<p class="text-sm">Show a scrubbable progress bar under the game board.</p>
</div>

<p><a on:click={navigateBack} href="/">Back</a></p>
</div>

0 comments on commit 5e5e270

Please sign in to comment.