some sweeping changes that at some level involves adding a showcase

This commit is contained in:
Corwin 2024-04-28 22:52:59 +01:00
parent 3d0de3535e
commit 6d7e6f934a
No known key found for this signature in database
42 changed files with 423 additions and 99 deletions

View file

@ -98,14 +98,14 @@ setup-cargo-wasm:
build-website-backtrace:
(cd website/backtrace && wasm-pack build --target web)
rm -rf website/agb/src/app/vendor/backtrace
mkdir -p website/agb/src/app/vendor
cp website/backtrace/pkg website/agb/src/app/vendor/backtrace -r
rm -rf website/agb/src/vendor/backtrace
mkdir -p website/agb/src/vendor
cp website/backtrace/pkg website/agb/src/vendor/backtrace -r
build-mgba-wasm:
rm -rf website/agb/src/app/mgba/vendor
mkdir website/agb/src/app/mgba/vendor
{{podman_command}} build --file website/mgba-wasm/BuildMgbaWasm --output=website/agb/src/app/mgba/vendor .
rm -rf website/agb/src/components/mgba/vendor
mkdir website/agb/src/components/mgba/vendor
{{podman_command}} build --file website/mgba-wasm/BuildMgbaWasm --output=website/agb/src/components/mgba/vendor .
build-combo-rom-site:
just _build-rom "examples/combo" "AGBGAMES"

View file

@ -1,6 +1,6 @@
"use client";
import { ContentBlock } from "../contentBlock";
import { ContentBlock } from "../../components/contentBlock";
import { useState } from "react";
import { styled } from "styled-components";
@ -69,44 +69,48 @@ export default function ColourPicker() {
}
return (
<ContentBlock>
<h1>agbrs colour converter</h1>
<PickerWrapper>
<PickerColumn>
<h2>Regular RGB8</h2>
<ColourInput
type="color"
value={hexColour}
onChange={(evt) => setHexColour(evt.target.value)}
/>
<Input
type="text"
value={hexColour}
onChange={(evt) => setHexColour(evt.target.value)}
/>
</PickerColumn>
<PickerColumn>
<h2>GBA RGB5</h2>
<ColourInput
type="color"
value={gbaHexColour}
onChange={(evt) => setGbaHexColour(evt.target.value)}
/>
<Input
type="text"
value={gbaHexColour}
onChange={(evt) => setGbaHexColour(evt.target.value)}
/>
<Input
type="text"
value={gbaU16}
onChange={(evt) =>
setColour(fromRgb15(parseInt(evt.target.value, 16)))
}
/>
</PickerColumn>
</PickerWrapper>
</ContentBlock>
<>
<ContentBlock color="#AAAFFF">
<h1>agbrs colour converter</h1>
</ContentBlock>
<ContentBlock>
<PickerWrapper>
<PickerColumn>
<h2>Regular RGB8</h2>
<ColourInput
type="color"
value={hexColour}
onChange={(evt) => setHexColour(evt.target.value)}
/>
<Input
type="text"
value={hexColour}
onChange={(evt) => setHexColour(evt.target.value)}
/>
</PickerColumn>
<PickerColumn>
<h2>GBA RGB5</h2>
<ColourInput
type="color"
value={gbaHexColour}
onChange={(evt) => setGbaHexColour(evt.target.value)}
/>
<Input
type="text"
value={gbaHexColour}
onChange={(evt) => setGbaHexColour(evt.target.value)}
/>
<Input
type="text"
value={gbaU16}
onChange={(evt) =>
setColour(fromRgb15(parseInt(evt.target.value, 16)))
}
/>
</PickerColumn>
</PickerWrapper>
</ContentBlock>
</>
);
}

View file

@ -2,7 +2,7 @@
import { useEffect, useState } from "react";
import { ContentBlock } from "../contentBlock";
import { ContentBlock } from "../../components/contentBlock";
import { GameDeveloperSummary } from "./gameDeveloperSummary";
import { styled } from "styled-components";
import { Debug } from "./debug";
@ -14,30 +14,39 @@ export function BacktracePage() {
}, []);
return (
<ContentBlock>
<h1>agbrs crash backtrace viewer</h1>
<p>
You likely got here from the link / QR code that was displayed when a
game you were playing crashed. This is the default crash page for games
made using the agb library.
</p>
<p>
The creator of the game is <em>very</em> likely interested in the code
below <em>along with</em> a description of what you were doing at the
time.{" "}
<strong>Send these to the creator of the game you are playing.</strong>
</p>
<BacktraceCopyDisplay backtrace={backtrace} setBacktrace={setBacktrace} />
<p>
<em>
The owners of this website are not necessarily the creators of the
game you are playing.
</em>
</p>
<h2>Backtrace</h2>
{backtrace && <Debug encodedBacktrace={backtrace} />}
<GameDeveloperSummary />
</ContentBlock>
<>
<ContentBlock color="#AAAFFF">
<h1>agbrs crash backtrace viewer</h1>
</ContentBlock>
<ContentBlock>
<p>
You likely got here from the link / QR code that was displayed when a
game you were playing crashed. This is the default crash page for
games made using the agb library.
</p>
<p>
The creator of the game is <em>very</em> likely interested in the code
below <em>along with</em> a description of what you were doing at the
time.{" "}
<strong>
Send these to the creator of the game you are playing.
</strong>
</p>
<BacktraceCopyDisplay
backtrace={backtrace}
setBacktrace={setBacktrace}
/>
<p>
<em>
The owners of this website are not necessarily the creators of the
game you are playing.
</em>
</p>
<h2>Backtrace</h2>
{backtrace && <Debug encodedBacktrace={backtrace} />}
<GameDeveloperSummary />
</ContentBlock>
</>
);
}

View file

@ -1,5 +1,9 @@
import { styled } from "styled-components";
import { AddressInfo, AgbDebug, useAgbDebug } from "../useAgbDebug.hook";
import {
AddressInfo,
AgbDebug,
useAgbDebug,
} from "../../hooks/useAgbDebug.hook";
import { ReactNode, useMemo, useState } from "react";
const BacktraceListWrapper = styled.div`

View file

Before

Width:  |  Height:  |  Size: 181 B

After

Width:  |  Height:  |  Size: 181 B

View file

@ -1,30 +1,18 @@
"use client";
import styled from "styled-components";
import { CenteredBlock, ContentBlock } from "./contentBlock";
import MgbaWrapper from "./mgba/mgbaWrapper";
import { CenteredBlock, ContentBlock } from "../components/contentBlock";
import MgbaWrapper from "../components/mgba/mgbaWrapper";
import Image from "next/image";
import left from "./gba-parts/left.png";
import right from "./gba-parts/right.png";
import { MobileController } from "./mobileController";
import left from "./left.png";
import right from "./right.png";
import { MobileController } from "../components/mobileController/mobileController";
import { useMemo, useRef, useState } from "react";
import { GbaKey } from "./mgba/bindings";
import { useClientValue } from "./useClientValue.hook";
import { MgbaHandle } from "./mgba/mgba";
const ExternalLink = styled.a`
text-decoration: none;
color: black;
background-color: #fad288;
border: solid #fad288 2px;
border-radius: 5px;
padding: 5px 10px;
&:hover {
border: solid black 2px;
}
`;
import { GbaKey } from "../components/mgba/bindings";
import { useClientValue } from "../hooks/useClientValue.hook";
import { MgbaHandle } from "../components/mgba/mgba";
import { ExternalLink } from "@/components/externalLink";
const HelpLinks = styled.div`
display: flex;
@ -78,7 +66,7 @@ function shouldStartPlaying(isTouchScreen: boolean | undefined) {
return !isTouchScreen;
}
const COMBO_GAME = new URL("combo.gba.gz", import.meta.url);
const COMBO_GAME = new URL("../roms/combo.gba.gz", import.meta.url);
function MgbaWithControllerSides() {
const mgba = useRef<MgbaHandle>(null);
@ -136,13 +124,13 @@ function MgbaWithControllerSides() {
export default function Home() {
return (
<>
<ContentBlock>
<ContentBlock color="#AAAFFF">
<h1>agb - a rust framework for making Game Boy Advance games</h1>
</ContentBlock>
<ContentBlock uncentered>
<MgbaWithControllerSides />
</ContentBlock>
<ContentBlock color="#f5755e">
<ContentBlock color="#256256">
<HelpLinks>
<ExternalLink href="https://github.com/agbrs/agb">
GitHub
@ -151,6 +139,7 @@ export default function Home() {
<ExternalLink href="https://docs.rs/agb/latest/agb/">
Docs
</ExternalLink>
<ExternalLink href="./showcase">Showcase</ExternalLink>
</HelpLinks>
</ContentBlock>
</>

View file

Before

Width:  |  Height:  |  Size: 206 B

After

Width:  |  Height:  |  Size: 206 B

View file

@ -0,0 +1,93 @@
import { slugify } from "@/sluggify";
import { Games, ShowcaseGame } from "../games";
import { ContentBlock } from "@/components/contentBlock";
import { ExternalLink } from "@/components/externalLink";
import Link from "next/link";
import Image, { StaticImageData } from "next/image";
import {
ScreenshotsWrapper,
ScreenshotWrapper,
BackToShowcaseWrapper,
DescriptionAndScreenshots,
Description,
} from "./styled";
export async function generateStaticParams() {
return Games.map((game) => ({
game: slugify(game.name),
}));
}
export function getGame(slug: string) {
const game = Games.find((game) => slugify(game.name) === slug);
if (!game) {
throw new Error("Not valid game name, this should never happen");
}
return game;
}
export function generateMetadata({ params }: { params: { game: string } }) {
const game = getGame(params.game);
return { title: game.name };
}
export default function Page({ params }: { params: { game: string } }) {
const game = getGame(params.game);
return <Display game={game} />;
}
function DeveloperNames({ names }: { names: string[] }) {
if (names.length === 0) {
throw new Error("You must specify developer names");
}
if (names.length === 1) {
return names[0];
}
if (names.length === 2) {
return names.join(" and ");
}
const first = names.slice(0, -1);
return first.join(", ") + `, and ${names[names.length - 1]}`;
}
function Screenshots({ screenshots }: { screenshots: StaticImageData[] }) {
return (
<ScreenshotsWrapper>
{screenshots.map((screenshot) => (
<ScreenshotWrapper key={screenshot.src}>
<Image src={screenshot} alt="" />
</ScreenshotWrapper>
))}
</ScreenshotsWrapper>
);
}
function Display({ game }: { game: ShowcaseGame }) {
return (
<>
<ContentBlock color="#AAAFFF">
<BackToShowcaseWrapper>
<Link href="../showcase">
<strong>&lt;</strong> Back to showcase
</Link>
</BackToShowcaseWrapper>
<h1>{game.name}</h1>
<div>
By: <DeveloperNames names={game.developers} />
</div>
</ContentBlock>
<ContentBlock>
<DescriptionAndScreenshots>
<Description>{game.description}</Description>
<Screenshots screenshots={game.screenshots} />
</DescriptionAndScreenshots>
</ContentBlock>
<ContentBlock color="#256256">
{game.itch && (
<ExternalLink href={game.itch.href}>View on itch.io</ExternalLink>
)}
</ContentBlock>
</>
);
}

View file

@ -0,0 +1,36 @@
"use client";
import { styled } from "styled-components";
export const ScreenshotsWrapper = styled.div`
flex: 4;
`;
export const ScreenshotWrapper = styled.div`
text-align: center;
img {
width: 100%;
width: round(down, 100%, 240px);
height: auto;
image-rendering: pixelated;
}
`;
export const Description = styled.div`
flex: 5;
:first-child {
margin-top: 0;
}
`;
export const DescriptionAndScreenshots = styled.div`
display: flex;
gap: 16px;
`;
export const BackToShowcaseWrapper = styled.div`
a {
text-decoration: none;
color: black;
}
`;

View file

@ -0,0 +1,43 @@
import { ShowcaseGame, shuffle } from "../../../games";
import h1 from "./the-hat-chooses-the-wizard-0.png";
import h2 from "./the-hat-chooses-the-wizard-1.png";
import h3 from "./the-hat-chooses-the-wizard-2.png";
import h4 from "./the-hat-chooses-the-wizard-3.png";
const HatWizScreenshots = [h1, h2, h3, h4];
export const HatWiz: ShowcaseGame = {
name: "The Hat Chooses the Wizard",
developers: shuffle(["Corwin Kuiper", "Gwilym Inzani"]),
screenshots: HatWizScreenshots,
description: (
<>
<p>
&lsquo;The Hat Chooses the Wizard&rsquo; is a 2D platformer. This game
was developed as an entry for the GMTK game jam 2021, with the theme
&ldquo;joined together&rdquo;. The entire game, except for the music,
was produced in just 48 hours.
</p>
<p>
In this game, you play as a wizard searching for his missing staff.
However, the path to the staff is filled with dangerous obstacles and
monsters. Luckily, you have a powerful magic hat that can be thrown and
recalled, allowing you to fly towards it and reach otherwise
inaccessible platforms.
</p>
<p>
With this unique mechanic, you can explore the game&apos;s levels and
defeat enemies. The game&apos;s simple but challenging gameplay will put
your platforming skills to the test as you try to reach the end.
</p>
<p>
The music is by Otto Halmén released under creative commons attribution
3.0 and can be found here:{" "}
<a href="https://opengameart.org/content/sylvan-waltz-standard-looped-version">
opengameart.org/content/sylvan-waltz-standard-looped-version
</a>
</p>
</>
),
itch: new URL("https://lostimmortal.itch.io/the-hat-chooses-the-wizard"),
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -0,0 +1,26 @@
import { ShowcaseGame, shuffle } from "@/app/showcase/games";
import p1 from "./the-purple-night-0.png";
import p2 from "./the-purple-night-1.png";
const Screenshots = [p1, p2];
export const Purple: ShowcaseGame = {
name: "The Purple Night",
developers: shuffle(["Corwin Kuiper", "Gwilym Inzani", "Sam Williams"]),
screenshots: Screenshots,
description: (
<>
<p>Save a lost soul and take them safely back to the afterlife!</p>
<p>
The purple night is a platformer game where your health bar is your
sword. The more damage you take, the shorter your sword gets, making you
more nimble and your attacks faster, but also increasing your risk.
</p>
<p>
Do you choose to stay at high health but low mobility, or low health and
higher mobility?
</p>
</>
),
itch: new URL("https://lostimmortal.itch.io/the-purple-night"),
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,27 @@
import { StaticImageData } from "next/image";
import { ReactNode } from "react";
import { HatWiz } from "./data/tapir/hatwiz/hatwiz";
import { Purple } from "./data/tapir/purple/purple";
export interface ShowcaseGame {
name: string;
developers: string[];
rom?: URL;
screenshots: StaticImageData[];
description: ReactNode;
itch?: URL;
otherLink?: URL;
}
export function shuffle<T>(a: T[]) {
var j, x, i;
for (i = a.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
}
export const Games: ShowcaseGame[] = [HatWiz, Purple];

View file

@ -0,0 +1,42 @@
import { Metadata } from "next";
import { ContentBlock } from "@/components/contentBlock";
import { Games, ShowcaseGame } from "./games";
import Link from "next/link";
import { slugify } from "@/sluggify";
import { GameDisplay, GameGrid, GameImage } from "./styles";
import Image from "next/image";
export const metadata: Metadata = {
title: "Games made with agb",
};
export default function ColourPickerPage() {
return (
<>
<ContentBlock color="#AAAFFF">
<h1>Showcase</h1>
</ContentBlock>
<ContentBlock uncentered>
<GameGrid>
{Games.map((game, idx) => (
<Game key={idx} game={game} />
))}
</GameGrid>
</ContentBlock>
</>
);
}
function Game({ game }: { game: ShowcaseGame }) {
const lastImage = game.screenshots[game.screenshots.length - 1];
return (
<GameDisplay>
<Link href={`./showcase/${slugify(game.name)}`}>
<GameImage>
<Image src={lastImage} alt={`Screenshot of ${game.name}`} />
</GameImage>
<h2>{game.name}</h2>
</Link>
</GameDisplay>
);
}

View file

@ -0,0 +1,32 @@
"use client";
import styled from "styled-components";
export const GameGrid = styled.div`
display: flex;
flex-wrap: wrap;
justify-content: center;
`;
export const GameImage = styled.div`
img {
width: 100%;
width: round(down, 100%, 240px);
height: auto;
image-rendering: pixelated;
}
`;
export const GameDisplay = styled.div`
width: 600px;
a {
text-align: center;
color: black;
text-decoration: none;
}
h2 {
margin: 0;
margin-top: 8px;
}
`;

View file

@ -0,0 +1,16 @@
"use client";
import Link from "next/link";
import { styled } from "styled-components";
export const ExternalLink = styled(Link)`
text-decoration: none;
color: black;
background-color: #fad288;
border: solid #fad288 2px;
border-radius: 5px;
padding: 5px 10px;
&:hover {
border: solid black 2px;
}
`;

View file

Before

Width:  |  Height:  |  Size: 163 B

After

Width:  |  Height:  |  Size: 163 B

View file

Before

Width:  |  Height:  |  Size: 173 B

After

Width:  |  Height:  |  Size: 173 B

View file

Before

Width:  |  Height:  |  Size: 190 B

After

Width:  |  Height:  |  Size: 190 B

View file

Before

Width:  |  Height:  |  Size: 189 B

After

Width:  |  Height:  |  Size: 189 B

View file

Before

Width:  |  Height:  |  Size: 241 B

After

Width:  |  Height:  |  Size: 241 B

View file

Before

Width:  |  Height:  |  Size: 156 B

After

Width:  |  Height:  |  Size: 156 B

View file

@ -1,4 +1,4 @@
import { FC, useMemo, useState } from "react";
import { useMemo, useState } from "react";
import styled from "styled-components";
import Image from "next/image";
@ -8,8 +8,8 @@ import DPad from "./gba-parts/dpad.png";
import ABButtons from "./gba-parts/ab.png";
import Select from "./gba-parts/SELECT.png";
import Start from "./gba-parts/START.png";
import { GbaKey } from "./mgba/bindings";
import { MgbaHandle } from "./mgba/mgba";
import { GbaKey } from "../mgba/bindings";
import { MgbaHandle } from "../mgba/mgba";
const MobileControls = styled.div`
display: flex;

View file

@ -4,7 +4,7 @@ import debugInit, {
DebugFile,
InitOutput,
AddressInfo,
} from "./vendor/backtrace/backtrace";
} from "../vendor/backtrace/backtrace";
let agbDebug: Promise<InitOutput> | undefined;

View file

@ -0,0 +1,3 @@
export function slugify(x: string) {
return x.toLowerCase().split(" ").join("-");
}