Minimal HomeAction and Tabs
This commit is contained in:
parent
e35219c1cf
commit
625000c308
14 changed files with 347 additions and 81 deletions
16
src/components/Game.svelte
Normal file
16
src/components/Game.svelte
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { SharkGame } from "../shark/SharkGame";
|
||||||
|
import type { Tabs } from "../shark/Tabs";
|
||||||
|
|
||||||
|
export let currentTab: typeof Tabs.currentTab;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<svelte:component this={SharkGame.Tabs.Tabs[currentTab]} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div {
|
||||||
|
height: calc(100% - 2em);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,10 +1,11 @@
|
||||||
<svelte:options immutable />
|
<svelte:options immutable />
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ResourceEntry } from "./ResourceTable.svelte";
|
import type { Resource } from "../../shark/data/Resources";
|
||||||
|
|
||||||
export let categoryName: string;
|
export let categoryName: string;
|
||||||
export let collapsed: boolean;
|
export let collapsed: boolean;
|
||||||
export let resources: ResourceEntry[];
|
export let resources: [string, Resource][];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<tr on:click class="subhead" tabindex="0" role="button">
|
<tr on:click class="subhead" tabindex="0" role="button">
|
||||||
|
|
|
@ -1,85 +1,29 @@
|
||||||
<svelte:options immutable={true} />
|
<svelte:options immutable />
|
||||||
|
|
||||||
<script context="module" lang="ts">
|
|
||||||
export type ResourceEntry = [string, typeof TEST_RESOURCES["Shark"]];
|
|
||||||
const TEST_RESOURCES = {
|
|
||||||
Shark: {
|
|
||||||
amount: 100,
|
|
||||||
category: "Frenzy",
|
|
||||||
change: (Math.random() - 0.5) * 30,
|
|
||||||
},
|
|
||||||
Ray: {
|
|
||||||
amount: 170,
|
|
||||||
category: "Frenzy",
|
|
||||||
change: (Math.random() - 0.5) * 30,
|
|
||||||
},
|
|
||||||
Sand: {
|
|
||||||
amount: 12,
|
|
||||||
category: "Garbage",
|
|
||||||
change: (Math.random() - 0.5) * 30,
|
|
||||||
},
|
|
||||||
Sand1: {
|
|
||||||
amount: 12,
|
|
||||||
category: "Garbage",
|
|
||||||
change: (Math.random() - 0.5) * 30,
|
|
||||||
},
|
|
||||||
Sand2: {
|
|
||||||
amount: 12,
|
|
||||||
category: "Garbage",
|
|
||||||
change: (Math.random() - 0.5) * 30,
|
|
||||||
},
|
|
||||||
Sand3: {
|
|
||||||
amount: 12,
|
|
||||||
category: "Garbage",
|
|
||||||
change: (Math.random() - 0.5) * 30,
|
|
||||||
},
|
|
||||||
Sand4: {
|
|
||||||
amount: 12,
|
|
||||||
category: "Garbage",
|
|
||||||
change: (Math.random() - 0.5) * 30,
|
|
||||||
},
|
|
||||||
Sand5: {
|
|
||||||
amount: 12,
|
|
||||||
category: "Garbage",
|
|
||||||
change: (Math.random() - 0.5) * 30,
|
|
||||||
},
|
|
||||||
Sand6: {
|
|
||||||
amount: 12,
|
|
||||||
category: "Garbage",
|
|
||||||
change: (Math.random() - 0.5) * 30,
|
|
||||||
},
|
|
||||||
Sand7: {
|
|
||||||
amount: 12,
|
|
||||||
category: "Garbage",
|
|
||||||
change: (Math.random() - 0.5) * 30,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { Writable } from "svelte/store";
|
||||||
|
|
||||||
|
import type { Resource } from "../../shark/data/Resources";
|
||||||
import ResourceGroup from "./ResourceGroup.svelte";
|
import ResourceGroup from "./ResourceGroup.svelte";
|
||||||
|
|
||||||
let collapsed: string[] = [];
|
let collapsed: string[] = [];
|
||||||
$: groups = resourceGroups();
|
let groups: ReturnType<typeof resourceGroups>;
|
||||||
|
export let resources: Writable<Record<string, Resource>>;
|
||||||
|
|
||||||
function resourceGroups() {
|
resources.subscribe((val) => {
|
||||||
|
groups = resourceGroups(val);
|
||||||
|
});
|
||||||
|
|
||||||
|
function resourceGroups(resources: Record<string, Resource>) {
|
||||||
return Object.entries(
|
return Object.entries(
|
||||||
Object.entries(TEST_RESOURCES).reduce(
|
Object.entries(resources).reduce((reduced, [resourceName, resource]) => {
|
||||||
(
|
if (reduced[resource.category] === undefined) {
|
||||||
reduced: { [key: string]: ResourceEntry[] },
|
reduced[resource.category] = [];
|
||||||
[resourceName, resource]
|
}
|
||||||
) => {
|
reduced[resource.category].push([resourceName, resource]);
|
||||||
if (reduced[resource.category] === undefined) {
|
|
||||||
reduced[resource.category] = [];
|
|
||||||
}
|
|
||||||
reduced[resource.category] = [
|
|
||||||
...reduced[resource.category],
|
|
||||||
[resourceName, resource],
|
|
||||||
];
|
|
||||||
|
|
||||||
return reduced;
|
return reduced;
|
||||||
},
|
}, {} as Record<string, [string, Resource][]>)
|
||||||
{}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
71
src/components/TabSelector.svelte
Normal file
71
src/components/TabSelector.svelte
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Tabs } from "../shark/Tabs";
|
||||||
|
|
||||||
|
export let tabs: typeof Tabs.Tabs;
|
||||||
|
export let selectedTab: typeof Tabs.currentTab;
|
||||||
|
$: tabNames = Object.keys(tabs) as typeof selectedTab[];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
{#each tabNames as tab}
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
on:click={() => {
|
||||||
|
selectedTab = tab;
|
||||||
|
}}
|
||||||
|
aria-selected={tab === selectedTab}
|
||||||
|
href={"javascript:;"}>{tab}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--color-dark);
|
||||||
|
text-shadow: 2px 2px 3px var(--color-darker);
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
> ul {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
margin: 0.2em;
|
||||||
|
padding: 0.2em 0.6em;
|
||||||
|
|
||||||
|
> li {
|
||||||
|
pointer-events: initial;
|
||||||
|
list-style-type: none;
|
||||||
|
display: inline;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:not(:first-child):before {
|
||||||
|
content: " | ";
|
||||||
|
}
|
||||||
|
|
||||||
|
> a {
|
||||||
|
color: var(--color-lighter);
|
||||||
|
}
|
||||||
|
> a:not([aria-selected="true"]):hover {
|
||||||
|
filter: brightness(130%);
|
||||||
|
}
|
||||||
|
> a[aria-selected="true"] {
|
||||||
|
pointer-events: none;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--color-light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
41
src/components/Tabs/Home.svelte
Normal file
41
src/components/Tabs/Home.svelte
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HomeAction } from "../../shark/data/HomeActions";
|
||||||
|
import { HomeActions } from "../../shark/data/HomeActions";
|
||||||
|
import { SharkGame } from "../../shark/SharkGame";
|
||||||
|
|
||||||
|
function homeActionClick(homeAction: HomeAction) {
|
||||||
|
if (homeAction.effect.resource) {
|
||||||
|
const resources = SharkGame.Resources.getResources(
|
||||||
|
Object.keys(homeAction.effect.resource)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resources !== null) {
|
||||||
|
for (const [resourceName, resource] of Object.entries(resources)) {
|
||||||
|
resource.amount += homeAction.effect.resource[resourceName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (homeAction.outcomes) {
|
||||||
|
SharkGame.Log.addMessage(
|
||||||
|
homeAction.outcomes[
|
||||||
|
Math.floor(Math.random() * homeAction.outcomes.length)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{#each Object.entries(HomeActions.getActionTable()) as [homeActionId, homeAction]}
|
||||||
|
<button on:click={() => homeActionClick(homeAction)} id={homeActionId}>
|
||||||
|
{homeAction.name}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
1
src/components/Tabs/Lab.svelte
Normal file
1
src/components/Tabs/Lab.svelte
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Haha, Laboratory go <em>BRRRRRRRRRRRRRRR!</em>
|
|
@ -1,14 +1,23 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { SharkGame } from "../shark/SharkGame";
|
import { SharkGame } from "../shark/SharkGame";
|
||||||
|
import Game from "./Game.svelte";
|
||||||
import Log from "./Log.svelte";
|
import Log from "./Log.svelte";
|
||||||
import ResourceTable from "./ResourceTable/ResourceTable.svelte";
|
import ResourceTable from "./ResourceTable/ResourceTable.svelte";
|
||||||
|
import TabSelector from "./TabSelector.svelte";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div id="left-column">
|
<div id="left-column">
|
||||||
<ResourceTable />
|
<ResourceTable bind:resources={SharkGame.Resources.Resources} />
|
||||||
<Log bind:messages={SharkGame.Log.messages} />
|
<Log bind:messages={SharkGame.Log.messages} />
|
||||||
</div>
|
</div>
|
||||||
|
<div id="right-column">
|
||||||
|
<TabSelector
|
||||||
|
bind:tabs={SharkGame.Tabs.Tabs}
|
||||||
|
bind:selectedTab={SharkGame.Tabs.currentTab}
|
||||||
|
/>
|
||||||
|
<Game bind:currentTab={SharkGame.Tabs.currentTab} />
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -30,5 +39,10 @@
|
||||||
width: 20%;
|
width: 20%;
|
||||||
min-width: 20em;
|
min-width: 20em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> #right-column {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
8
src/global.d.ts
vendored
8
src/global.d.ts
vendored
|
@ -1 +1,9 @@
|
||||||
/// <reference types="svelte" />
|
/// <reference types="svelte" />
|
||||||
|
|
||||||
|
import type { SharkGame } from "./shark/SharkGame";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
SharkGame: SharkGame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import { Resources } from "./data/Resources";
|
||||||
import { Log } from "./Log";
|
import { Log } from "./Log";
|
||||||
import { Settings } from "./Settings";
|
import { Settings } from "./Settings";
|
||||||
import { StaticClass } from "./StaticClass";
|
import { StaticClass } from "./StaticClass";
|
||||||
|
import { Tabs } from "./Tabs";
|
||||||
|
|
||||||
export class SharkGame extends StaticClass {
|
export class SharkGame extends StaticClass {
|
||||||
static readonly #GAME_NAMES = [
|
static readonly #GAME_NAMES = [
|
||||||
|
@ -55,6 +57,8 @@ export class SharkGame extends StaticClass {
|
||||||
static title: string;
|
static title: string;
|
||||||
static readonly Settings = Settings;
|
static readonly Settings = Settings;
|
||||||
static readonly Log = Log;
|
static readonly Log = Log;
|
||||||
|
static readonly Resources = Resources;
|
||||||
|
static readonly Tabs = Tabs;
|
||||||
|
|
||||||
static init(): void {
|
static init(): void {
|
||||||
for (const setting of Object.values(Settings)) {
|
for (const setting of Object.values(Settings)) {
|
||||||
|
@ -68,4 +72,4 @@ export class SharkGame extends StaticClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
globalThis.SharkGame = SharkGame;
|
window.SharkGame = SharkGame;
|
||||||
|
|
11
src/shark/Tabs.ts
Normal file
11
src/shark/Tabs.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import Home from "../components/Tabs/Home.svelte";
|
||||||
|
import Lab from "../components/Tabs/Lab.svelte";
|
||||||
|
import { StaticClass } from "./StaticClass";
|
||||||
|
|
||||||
|
export class Tabs extends StaticClass {
|
||||||
|
static readonly Tabs = {
|
||||||
|
Home,
|
||||||
|
Lab,
|
||||||
|
} as const;
|
||||||
|
static currentTab: keyof typeof Tabs.Tabs = "Home";
|
||||||
|
}
|
73
src/shark/data/HomeActions.ts
Normal file
73
src/shark/data/HomeActions.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import { StaticClass } from "../StaticClass";
|
||||||
|
|
||||||
|
export type HomeAction = {
|
||||||
|
name: string;
|
||||||
|
effect: {
|
||||||
|
resource: Record<string, number>;
|
||||||
|
};
|
||||||
|
outcomes: string[];
|
||||||
|
multiOutcomes?: string[];
|
||||||
|
helpText: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class HomeActions extends StaticClass {
|
||||||
|
static getActionTable(): { [actionName: string]: HomeAction } {
|
||||||
|
return HomeActions.actionTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
static actionTable = {
|
||||||
|
catchFish: {
|
||||||
|
name: "Catch fish",
|
||||||
|
effect: {
|
||||||
|
resource: {
|
||||||
|
fish: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cost: {},
|
||||||
|
prereq: {},
|
||||||
|
outcomes: [
|
||||||
|
"Dropped the bass.",
|
||||||
|
"Ate a kipper. Wait. Hang on.",
|
||||||
|
"You eat a fish hooray!",
|
||||||
|
"Fish.",
|
||||||
|
"Ate a shark. Wait. No, it wasn't a shark.",
|
||||||
|
"Ate an anchovy.",
|
||||||
|
"Ate a catfish.",
|
||||||
|
"Ate a flounder.",
|
||||||
|
"Ate a haddock.",
|
||||||
|
"Ate a herring.",
|
||||||
|
"Ate a mackerel.",
|
||||||
|
"Ate a mullet.",
|
||||||
|
"Ate a perch.",
|
||||||
|
"Ate a pollock.",
|
||||||
|
"Ate a salmon.",
|
||||||
|
"Ate a sardine.",
|
||||||
|
"Ate a sole.",
|
||||||
|
"Ate a tilapia.",
|
||||||
|
"Ate a trout.",
|
||||||
|
"Ate a whitefish.",
|
||||||
|
"Ate a bass.",
|
||||||
|
"Ate a carp.",
|
||||||
|
"Ate a cod.",
|
||||||
|
"Ate a halibut.",
|
||||||
|
"Ate a mahi mahi.",
|
||||||
|
"Ate a monkfish.",
|
||||||
|
"Ate a perch.",
|
||||||
|
"Ate a snapper.",
|
||||||
|
"Ate a bluefish.",
|
||||||
|
"Ate a grouper.",
|
||||||
|
"Ate a sea bass.",
|
||||||
|
"Ate a yellowfin tuna.",
|
||||||
|
"Ate a marlin.",
|
||||||
|
"Ate an orange roughy.",
|
||||||
|
"Ate a shark.",
|
||||||
|
"Ate a swordfish.",
|
||||||
|
"Ate a tilefish.",
|
||||||
|
"Ate a tuna.",
|
||||||
|
"Ate a swedish fish.",
|
||||||
|
"Ate a goldfish.",
|
||||||
|
],
|
||||||
|
helpText: "Use your natural shark prowess to find and catch a fish.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
73
src/shark/data/Resources.ts
Normal file
73
src/shark/data/Resources.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
import { StaticClass } from "../StaticClass";
|
||||||
|
|
||||||
|
export class Resource {
|
||||||
|
category: string;
|
||||||
|
amount: number;
|
||||||
|
change: number;
|
||||||
|
|
||||||
|
constructor(category: string) {
|
||||||
|
this.category = category;
|
||||||
|
this.amount = 0;
|
||||||
|
this.change = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Resources extends StaticClass {
|
||||||
|
static readonly Resources = writable<Record<string, Resource>>({
|
||||||
|
shark: new Resource("Frenzy"),
|
||||||
|
ray: new Resource("Frenzy"),
|
||||||
|
fish: new Resource("Animals"),
|
||||||
|
});
|
||||||
|
|
||||||
|
static createResource(name: string, category: string): void {
|
||||||
|
Resources.Resources.update((res) => {
|
||||||
|
res[name] = new Resource(category);
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static setResource(resourceName: string, amount: number): void {
|
||||||
|
Resources.Resources.update((res) => {
|
||||||
|
res[resourceName].amount = amount;
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static getResource(resourceName: string): Resource | null {
|
||||||
|
let result: null | Resource = null;
|
||||||
|
Resources.Resources.update((res) => {
|
||||||
|
if (Object.hasOwnProperty.call(res, resourceName))
|
||||||
|
result = res[resourceName];
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getResources(resourceNames: string[]): Record<string, Resource> | null;
|
||||||
|
static getResources(
|
||||||
|
...resourceNames: string[]
|
||||||
|
): Record<string, Resource> | null;
|
||||||
|
static getResources(
|
||||||
|
...resourceNames: string[] | string[][]
|
||||||
|
): Record<string, Resource> | null {
|
||||||
|
if (Array.isArray(resourceNames[0])) {
|
||||||
|
resourceNames = resourceNames[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: null | [string, Resource][] = null;
|
||||||
|
Resources.Resources.update((res) => {
|
||||||
|
for (const resourceName of resourceNames as string[]) {
|
||||||
|
if (Object.hasOwnProperty.call(res, resourceName)) {
|
||||||
|
if (result === null) {
|
||||||
|
result = [];
|
||||||
|
}
|
||||||
|
result.push([resourceName, res[resourceName]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
return result === null ? null : Object.fromEntries(result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,13 +23,18 @@ a {
|
||||||
button {
|
button {
|
||||||
background-color: var(--color-light);
|
background-color: var(--color-light);
|
||||||
border: 1px solid var(--color-lighter);
|
border: 1px solid var(--color-lighter);
|
||||||
border-radius: 10px;
|
border-radius: 5px;
|
||||||
text-shadow: 0 0 2px black;
|
text-shadow: 0 0 2px black;
|
||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 0 2px 2px var(--color-lighter);
|
box-shadow: 0 0 2px 2px var(--color-lighter);
|
||||||
}
|
}
|
||||||
|
&:active {
|
||||||
|
background-color: var(--color-dark);
|
||||||
|
color: var(--color-light);
|
||||||
|
box-shadow: 0 3px 7px 7px var(--color-darker) inset;
|
||||||
|
}
|
||||||
|
|
||||||
&:not(:disabled) {
|
&:not(:disabled) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -4,5 +4,9 @@
|
||||||
"target": "es2021",
|
"target": "es2021",
|
||||||
|
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
|
"exclude": ["node_modules/*", "__sapper__/*", "public/*"],
|
||||||
|
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue