Skip to content

Commit

Permalink
Add a minigame
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhincore committed Oct 4, 2024
1 parent d2e8803 commit bd82b68
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 12 deletions.
2 changes: 1 addition & 1 deletion content/teams.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ redkid:
label: Redkids
description: People who work on modding tools
discordRole: "905518587777806366"
color: "#ed453e"
color: "#f00"

wiki:
label: Wiki Contributors
Expand Down
9 changes: 9 additions & 0 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@
.absolutely-centered {
@apply absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2;
}

.crt {
background-image: radial-gradient(transparent, #000a);
background-size: 3px 3px;
}
}

@layer utilities {
Expand All @@ -62,6 +67,10 @@
.fade-in {
animation: fade-in var(--fade-duration, 2s) var(--fade-delay, 0s) both;
}

.small-caps {
font-variant: small-caps;
}
}

@keyframes fade-in {
Expand Down
257 changes: 257 additions & 0 deletions src/lib/components/parts/DinoGame.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
<script lang="ts">
import { createEventDispatcher, onMount } from "svelte";
import Button from "../elements/Button.svelte";
import ThemeButton from "../theme/ThemeButton.svelte";
import ErrorAlert from "./ErrorAlert.svelte";
const DELTA = 1000 / 60;
const MAX_ITERS = 64;
const GRAVITY = -0.3;
const JUMP = 10;
const SPEED = 3;
const ACCELERATION = 0.0001;
const GAME_WIDTH = 1024;
const GAME_MAX = BigInt(Math.floor(GAME_WIDTH * 0.75));
const GAME_MIN = BigInt(Math.floor(-GAME_WIDTH * 0.23));
const PLAYER_SIZE = 30n;
const MIN_SPIKE_DIST = 16n;
const MAX_SPIKE_DIST = GAME_MAX;
const SPIKE_HEIGHT = 16;
export let sprite: string;
const emit = createEventDispatcher<{ exit: void }>();
// "Engine"
let frame = 0;
let lastTimestamp = 0;
let deltaAccumulator = 0;
// Game
let spikes = [GAME_MAX];
let nextSpike = GAME_MAX;
let canDoubleJump = true;
let gameSpeed = 1;
let velocityY = 0;
let y = 0;
let x = 0n;
let targetAngle = 0;
let tps = 60;
// Controls
let shouldJump = false;
let isDead = false;
function reset() {
lastTimestamp = 0;
deltaAccumulator = 0;
spikes = [GAME_MAX];
nextSpike = GAME_MAX;
canDoubleJump = true;
gameSpeed = 1;
velocityY = 0;
y = 0;
x = 0n;
isDead = false;
}
function jump() {
if (!frame) return;
shouldJump = true;
targetAngle = targetAngle + 90;
}
function onKeyDown(ev: KeyboardEvent) {
switch (ev.code) {
case "ArrowUp":
case "Space":
jump();
ev.preventDefault();
break;
case "Escape":
exit();
break;
}
}
function exit() {
stop();
emit("exit");
}
function die() {
isDead = true;
stop();
}
function update(timestamp = 0) {
frame = window.requestAnimationFrame(update);
if (!timestamp) return;
const ready = !!lastTimestamp;
const delta = timestamp - lastTimestamp;
lastTimestamp = timestamp;
if (!ready) return;
deltaAccumulator += delta;
if (delta) {
tps = tps * 0.9 + (1000 / delta) * 0.1;
}
let i = 0;
while (deltaAccumulator >= DELTA && i++ < MAX_ITERS) {
deltaAccumulator -= DELTA;
// Controls
if (shouldJump) {
shouldJump = false;
if (canDoubleJump) {
if (y != 0) canDoubleJump = false;
velocityY += JUMP * gameSpeed;
}
}
// Gravity
velocityY += GRAVITY * gameSpeed;
// Jump
y += velocityY;
// Move
x += BigInt(Math.round(SPEED * gameSpeed));
// Touch ground
if (y < 0) {
canDoubleJump = true;
y = 0;
velocityY = 0;
}
// Spikes
const nearGround = y < SPIKE_HEIGHT;
const newSpikes = [];
const maxX = x + GAME_MAX;
const minX = x + GAME_MIN;
let lastSpike = 0n;
for (const spike of spikes) {
if (spike < minX) continue;
newSpikes.push(spike);
if (nearGround) {
// Is in the spike's square
if (Math.abs(Number(x - spike)) < MIN_SPIKE_DIST + PLAYER_SIZE) {
die();
}
}
lastSpike = spike;
}
if (nextSpike < maxX) {
if (nextSpike > lastSpike) newSpikes.push(nextSpike);
nextSpike += MIN_SPIKE_DIST + BigInt(Math.trunc(Math.random() * Number(MAX_SPIKE_DIST)));
}
spikes = newSpikes;
gameSpeed += gameSpeed * ACCELERATION;
}
}
function start() {
reset();
update();
}
function stop() {
if (frame) window.cancelAnimationFrame(frame);
frame = 0;
}
onMount(() => {
start();
return stop;
});
</script>

<svelte:window on:keydown={onKeyDown} />

<div class="absolute -inset-4 bg-black blur-lg" />
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="absolute inset-0 select-none" on:click={jump}>
<div class="-mt-4 flex items-start justify-between p-1 px-4 font-semibold tabular-nums text-red">
<div>
<Button class="-ml-4" on:click={exit}>Quit game</Button>
<span class="text-sm text-cyan opacity-75">{Math.round(tps)}</span>
</div>

<div class="text-center">
<div class="small-caps text-sm">Score</div>
<div class="text-2xl">{x}</div>
</div>

<div class="text-right">
<div class="small-caps text-sm">Heat Level</div>
<div class="text-xl">{gameSpeed.toFixed(2)}</div>
</div>
</div>

<!-- game -->
<div class="absolute left-1/4 top-3/4 w-3/4">
<!-- player -->
<div class="absolute left-0 mt-px" style:transform="translate(-50%, {-100 - y}%)">
<img
class="size-16 border border-red transition-transform duration-300 ease-out"
src={sprite}
width={64}
height={64}
alt=""
style:transform="rotate({targetAngle}deg)"
/>
</div>
<!-- spikes -->
<div style:transform="translateX({-x}px)" style:will-change="transform">
{#each spikes as spikeX (spikeX)}
<span
class="absolute bottom-0 left-0 inline-block origin-bottom-right border-[16px] border-transparent border-b-red"
style:transform="translateX(-50%) translateX({spikeX}px)"
/>
<!-- transition:scale={{ duration: 500 }} -->
{/each}
</div>
</div>

<!-- ground -->
<span class="absolute top-3/4 w-full border-b-2 border-red" />

<div class="small-caps absolute top-3/4 w-full p-4 text-center text-sm text-red opacity-50">
Tap or press space to avoid ICE traps
</div>

<!-- death -->
{#if isDead}
<div class="absolute inset-0 top-8 flex items-center justify-center bg-black/75">
<div class="flex flex-col bg-black p-4">
<ErrorAlert title="GAME OVER">
The ICE traps got you.<br />
</ErrorAlert>

<ThemeButton theme="cyberpunk" size="sm" class="ml-auto" on:click={start}>Try again?</ThemeButton>
</div>
</div>
{/if}
</div>
2 changes: 1 addition & 1 deletion src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import CursorTrail from "$components/parts/CursorTrail.svelte";
import DotsSidesBackground from "$components/parts/DotsSidesBackground.svelte";
import Sammy from "$components/parts/Sammy.svelte";
import Divider from "$lib/components/elements/Divider.svelte";
import Divider from "$components/elements/Divider.svelte";
import { site } from "$lib/content/site";
export let data;
Expand Down
5 changes: 0 additions & 5 deletions src/routes/HeroBackground.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,6 @@
</div>

<style>
.crt {
background-image: radial-gradient(transparent, #000a);
background-size: 3px 3px;
}
.wave {
--repeats: 2;
Expand Down
2 changes: 1 addition & 1 deletion src/routes/about/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Button from "$components/elements/Button.svelte";
import Image from "$components/elements/Image.svelte";
import PageRoot from "$components/parts/PageRoot.svelte";
import Divider from "$lib/components/elements/Divider.svelte";
import Divider from "$components/elements/Divider.svelte";
</script>

<PageRoot title="About us" description="What is Red Modding? Who is it? When is it?">
Expand Down
14 changes: 11 additions & 3 deletions src/routes/members/[member]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
import GlitchingImage from "$components/elements/GlitchingImage.svelte";
import Heading from "$components/elements/Heading.svelte";
import Image from "$components/elements/Image.svelte";
import GlitchingWebsite from "$components/parts/BlackwallScreensaver/GlitchingWebsite.svelte";
import Card from "$components/parts/Card.svelte";
import ErrorAlert from "$components/parts/ErrorAlert.svelte";
import Loading from "$components/parts/Loading.svelte";
import PageRoot from "$components/parts/PageRoot.svelte";
import Section from "$components/parts/Section.svelte";
import Warning from "$components/parts/Warning.svelte";
import ThemeFrameBig from "$components/theme/ThemeFrameBig.svelte";
import GlitchingWebsite from "$lib/components/parts/BlackwallScreensaver/GlitchingWebsite.svelte";
import Warning from "$lib/components/parts/Warning.svelte";
import { projects } from "$lib/content/projects";
import { teams } from "$lib/content/teams";
import { THEME_CLASSES } from "$lib/themes";
Expand All @@ -26,6 +26,7 @@
export let data;
let game = false;
let header: HTMLElement;
let headerReady = false;
Expand Down Expand Up @@ -94,7 +95,8 @@
{/if}

<div class="mb-8 inline-flex flex-wrap gap-4 gap-x-8 px-2 max-md:justify-center">
<div class="fade-in relative flex-shrink-0" style:--fade-delay=".7s">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="fade-in relative flex-shrink-0" style:--fade-delay=".7s" on:dblclick={() => (game = true)}>
<ThemeFrameBig theme={themeName}>
<svelte:component
this={themeName == "cyberpunk" ? GlitchingImage : Image}
Expand Down Expand Up @@ -215,6 +217,12 @@
maxArea={0.5}
/>
{/if}

{#if game}
{#await import("$components/parts/DinoGame.svelte") then { default: DinoGame }}
<DinoGame sprite={member.Image + "?size=64"} on:exit={() => (game = false)} />
{/await}
{/if}
</header>

<div class="contents" style:--fade-delay="2.5s">
Expand Down
2 changes: 1 addition & 1 deletion src/routes/projects/ProjectItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Heading from "$components/elements/Heading.svelte";
import ThemeButton from "$components/theme/ThemeButton.svelte";
import ThemeFrameBig from "$components/theme/ThemeFrameBig.svelte";
import Button from "$lib/components/elements/Button.svelte";
import Button from "$components/elements/Button.svelte";
import type { Project } from "$lib/content/projects";
import type { TeamMember } from "$lib/server/members";
import { outlineToPath, THEME_CLASSES, THEME_CORNERS, type GameTheme } from "$lib/themes";
Expand Down

0 comments on commit bd82b68

Please sign in to comment.