- 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.
-
-
- The creator of the game is very likely interested in the code
- below along with a description of what you were doing at the
- time.{" "}
- Send these to the creator of the game you are playing.
-
-
-
-
- The owners of this website are not necessarily the creators of the
- game you are playing.
-
-
-
Backtrace
- {backtrace && }
-
-
+ <>
+
+
agbrs crash backtrace viewer
+
+
+
+ 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.
+
+
+ The creator of the game is very likely interested in the code
+ below along with a description of what you were doing at the
+ time.{" "}
+
+ Send these to the creator of the game you are playing.
+
+
+
+
+
+ The owners of this website are not necessarily the creators of the
+ game you are playing.
+
+
+
Backtrace
+ {backtrace && }
+
+
+ >
);
}
diff --git a/website/agb/src/app/crash/debug.tsx b/website/agb/src/app/crash/debug.tsx
index a573cef2..19eeb3cd 100644
--- a/website/agb/src/app/crash/debug.tsx
+++ b/website/agb/src/app/crash/debug.tsx
@@ -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`
diff --git a/website/agb/src/app/gba-parts/left.png b/website/agb/src/app/left.png
similarity index 100%
rename from website/agb/src/app/gba-parts/left.png
rename to website/agb/src/app/left.png
diff --git a/website/agb/src/app/page.tsx b/website/agb/src/app/page.tsx
index 0f8e833a..225ede6d 100644
--- a/website/agb/src/app/page.tsx
+++ b/website/agb/src/app/page.tsx
@@ -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(null);
@@ -136,13 +124,13 @@ function MgbaWithControllerSides() {
export default function Home() {
return (
<>
-
+
agb - a rust framework for making Game Boy Advance games
-
+
GitHub
@@ -151,6 +139,7 @@ export default function Home() {
Docs
+ Showcase
>
diff --git a/website/agb/src/app/gba-parts/right.png b/website/agb/src/app/right.png
similarity index 100%
rename from website/agb/src/app/gba-parts/right.png
rename to website/agb/src/app/right.png
diff --git a/website/agb/src/app/showcase/[game]/page.tsx b/website/agb/src/app/showcase/[game]/page.tsx
new file mode 100644
index 00000000..cf3b2138
--- /dev/null
+++ b/website/agb/src/app/showcase/[game]/page.tsx
@@ -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 ;
+}
+
+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 (
+
+ {screenshots.map((screenshot) => (
+
+
+
+ ))}
+
+ );
+}
+
+function Display({ game }: { game: ShowcaseGame }) {
+ return (
+ <>
+
+
+
+ < Back to showcase
+
+
+
+ ‘The Hat Chooses the Wizard’ is a 2D platformer. This game
+ was developed as an entry for the GMTK game jam 2021, with the theme
+ “joined together”. The entire game, except for the music,
+ was produced in just 48 hours.
+
+
+ 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.
+
+
+ With this unique mechanic, you can explore the game's levels and
+ defeat enemies. The game's simple but challenging gameplay will put
+ your platforming skills to the test as you try to reach the end.
+
+ >
+ ),
+ itch: new URL("https://lostimmortal.itch.io/the-hat-chooses-the-wizard"),
+};
diff --git a/website/agb/src/app/showcase/data/tapir/hatwiz/the-hat-chooses-the-wizard-0.png b/website/agb/src/app/showcase/data/tapir/hatwiz/the-hat-chooses-the-wizard-0.png
new file mode 100644
index 00000000..1dbe0a0f
Binary files /dev/null and b/website/agb/src/app/showcase/data/tapir/hatwiz/the-hat-chooses-the-wizard-0.png differ
diff --git a/website/agb/src/app/showcase/data/tapir/hatwiz/the-hat-chooses-the-wizard-1.png b/website/agb/src/app/showcase/data/tapir/hatwiz/the-hat-chooses-the-wizard-1.png
new file mode 100644
index 00000000..3c101a4d
Binary files /dev/null and b/website/agb/src/app/showcase/data/tapir/hatwiz/the-hat-chooses-the-wizard-1.png differ
diff --git a/website/agb/src/app/showcase/data/tapir/hatwiz/the-hat-chooses-the-wizard-2.png b/website/agb/src/app/showcase/data/tapir/hatwiz/the-hat-chooses-the-wizard-2.png
new file mode 100644
index 00000000..34e07902
Binary files /dev/null and b/website/agb/src/app/showcase/data/tapir/hatwiz/the-hat-chooses-the-wizard-2.png differ
diff --git a/website/agb/src/app/showcase/data/tapir/hatwiz/the-hat-chooses-the-wizard-3.png b/website/agb/src/app/showcase/data/tapir/hatwiz/the-hat-chooses-the-wizard-3.png
new file mode 100644
index 00000000..17130c0a
Binary files /dev/null and b/website/agb/src/app/showcase/data/tapir/hatwiz/the-hat-chooses-the-wizard-3.png differ
diff --git a/website/agb/src/app/showcase/data/tapir/purple/purple.tsx b/website/agb/src/app/showcase/data/tapir/purple/purple.tsx
new file mode 100644
index 00000000..34fa37ec
--- /dev/null
+++ b/website/agb/src/app/showcase/data/tapir/purple/purple.tsx
@@ -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: (
+ <>
+
Save a lost soul and take them safely back to the afterlife!
+
+ 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.
+
+
+ Do you choose to stay at high health but low mobility, or low health and
+ higher mobility?
+
+ >
+ ),
+ itch: new URL("https://lostimmortal.itch.io/the-purple-night"),
+};
diff --git a/website/agb/src/app/showcase/data/tapir/purple/the-purple-night-0.png b/website/agb/src/app/showcase/data/tapir/purple/the-purple-night-0.png
new file mode 100644
index 00000000..3b39ff12
Binary files /dev/null and b/website/agb/src/app/showcase/data/tapir/purple/the-purple-night-0.png differ
diff --git a/website/agb/src/app/showcase/data/tapir/purple/the-purple-night-1.png b/website/agb/src/app/showcase/data/tapir/purple/the-purple-night-1.png
new file mode 100644
index 00000000..ad53bb8a
Binary files /dev/null and b/website/agb/src/app/showcase/data/tapir/purple/the-purple-night-1.png differ
diff --git a/website/agb/src/app/showcase/games.tsx b/website/agb/src/app/showcase/games.tsx
new file mode 100644
index 00000000..3ba4a542
--- /dev/null
+++ b/website/agb/src/app/showcase/games.tsx
@@ -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(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];
diff --git a/website/agb/src/app/showcase/page.tsx b/website/agb/src/app/showcase/page.tsx
new file mode 100644
index 00000000..7387c023
--- /dev/null
+++ b/website/agb/src/app/showcase/page.tsx
@@ -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 (
+ <>
+
+