This commit is contained in:
Yanick Champoux 2023-04-08 13:47:00 -04:00
parent 0098e899c9
commit 3fb959d9f3
15 changed files with 304 additions and 176 deletions

View File

@ -1,4 +1,4 @@
<div> <div class="field">
{#if label} {#if label}
<label>{label}</label> <label>{label}</label>
{/if} {/if}

View File

@ -0,0 +1,17 @@
<Hst.Story>
<Weaponry {weapons} />
</Hst.Story>
<script>
export let Hst;
import Weaponry from "./Weaponry.svelte";
import u from "@yanick/updeep-remeda";
import { weaponTypes } from "$lib/store/ship/weaponry/rules";
import Changelog from "../Changelog.svelte";
const weapons = [
weaponTypes.find(u.matches({ type: "beam" })).initial,
weaponTypes.find(u.matches({ type: "submunition" })).initial,
].map((specs, id) => ({ specs, id }));
console.log(weapons);
</script>

View File

@ -2,14 +2,12 @@
<Firecons {...firecons} /> <Firecons {...firecons} />
<ADFC {...adfc} /> <ADFC {...adfc} />
<!--
<AddWeapon /> <AddWeapon />
{#each weapons as weapon (weapon.id)} {#each weapons as weapon (weapon.id)}
<Weapon {weapon} id={weapon.id} /> <Weapon {...weapon} />
{/each} {/each}
-->
</Section> </Section>
<script> <script>
@ -22,17 +20,14 @@
import Firecons from "./Weaponry/Firecons.svelte"; import Firecons from "./Weaponry/Firecons.svelte";
import ADFC from "./Weaponry/ADFC.svelte"; import ADFC from "./Weaponry/ADFC.svelte";
import AddWeapon from "./Weaponry/AddWeapon.svelte";
export let firecons = {}; export let firecons = {};
export let adfc = {}; export let adfc = {};
/*
import AddWeapon from './AddWeapon.svelte';
import Weapon from './Weapon/index.svelte';
import Weapon from "./Weaponry/Weapon/index.svelte";
export let weapons = []; export let weapons = [];
*/
</script> </script>
<style> <style>

View File

@ -1,24 +1,25 @@
<Field label="weapon type"> <div>
<Field label="">
<select bind:value={type}> <select bind:value={type}>
{#each weaponTypes as weapon (weapon.type)} {#each weaponTypes as weapon (weapon.type)}
<option value={weapon.type}>{weapon.name}</option> <option value={weapon.type}>{weapon.name}</option>
{/each} {/each}
</select> </select>
<button class="button small primary" on:click={addWeapon}>add weapon</button>
</Field> </Field>
<button class="button small primary" on:click={addWeapon}>add weapon</button>
</div>
<script> <script>
import { getContext } from "svelte"; import { getContext } from "svelte";
import Field from "../../Field.svelte"; import Field from "../../Field.svelte";
import { weaponTypes } from "$lib/shipDux/weaponry/weapons"; import { weaponTypes } from "$lib/store/ship/weaponry/rules";
export let ship = getContext("ship"); export let api = getContext("api");
let type = weaponTypes[0].value; let type = weaponTypes[0].type;
const addWeapon = () => ship.dispatch.addWeapon(type); const addWeapon = () => api?.dispatch?.addWeapon?.(type);
</script> </script>
<style> <style>
@ -26,4 +27,17 @@
width: inherit; width: inherit;
display: inline-block; display: inline-block;
} }
button {
width: inherit;
display: inline-block;
}
div :global(.field) {
display: flex;
margin-right: 2em;
}
div {
display: flex;
margin-left: 5em;
margin-bottom: 2em;
}
</style> </style>

View File

@ -0,0 +1,8 @@
<Hst.Story>
<Arcs />
</Hst.Story>
<script>
export let Hst;
import Arcs from "./Arcs.svelte";
</script>

View File

@ -1,10 +1,10 @@
<svg width="{size}px" height="{size}px"> <svg width="{size}px" height="{size}px">
{#each all_arcs as arc (arc)} {#each arcs as arc (arc)}
<Arc <Arc
{arc} {arc}
radius={size / 2} radius={size / 2}
active={selected.includes(arc)} active={selected.includes(arc)}
on:click={() => click_arc(arc)} on:click={() => clickArc(arc)}
/> />
{/each} {/each}
<circle cx="50%" cy="50%" r={size / 3} /> <circle cx="50%" cy="50%" r={size / 3} />
@ -12,17 +12,17 @@
</svg> </svg>
<script> <script>
import Arc from "./Arc.svelte";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
const all_arcs = ["FS", "F", "FP", "AP", "A", "AS"]; import { arcs } from "$lib/store/ship/weaponry/rules";
import Arc from "./Arc.svelte";
export let selected = []; export let selected = [];
export let size = 60; export let size = 60;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const click_arc = (arc) => dispatch("click_arc", arc); const clickArc = (arc) => dispatch("clickArc", arc);
</script> </script>
<style> <style>

Before

Width:  |  Height:  |  Size: 639 B

After

Width:  |  Height:  |  Size: 634 B

View File

@ -17,16 +17,18 @@
</select> </select>
</Field> </Field>
<Arcs selected={arcs} on:click_arc={({ detail }) => setArcs(detail)} /> <Arcs selected={arcs} on:clickArc={({ detail }) => setArcs(detail)} />
<script> <script>
import { getContext } from "svelte"; import { getContext } from "svelte";
import * as R from "remeda";
import Arcs from "../Arcs.svelte"; import Arcs from "../Arcs.svelte";
import ShipItem from "$lib/components/ShipItem.svelte"; import ShipItem from "$lib/components/ShipItem.svelte";
import Field from "$lib/components/Field.svelte"; import Field from "$lib/components/Field.svelte";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import memoize from "memoize-one";
const all_arcs = ["FS", "F", "FP", "AP", "A", "AS"]; const all_arcs = ["FS", "F", "FP", "AP", "A", "AS"];
@ -40,41 +42,52 @@
4: [1, 2, 3, 4, 5, 6, "broadside"], 4: [1, 2, 3, 4, 5, 6, "broadside"],
}; };
$: arcsCaches = arcs.join(',');
let nbrArcs = arcs.length; let nbrArcs = arcs.length;
$: if (!arc_options[weaponClass].includes(nbrArcs)) { $: if (!arc_options[weaponClass].includes(nbrArcs)) {
nbrArcs = arc_options[weaponClass][0]; nbrArcs = arc_options[weaponClass][0];
console.log({nbrArcs, label:"in if"}) console.log({ nbrArcs, label: "in if" });
} }
const broadside = ["FS", "FP", "AP", "AS"]; const broadside = ["FS", "FP", "AP", "AS"];
function setArcs(firstArc) { function setArcs(firstArc) {
console.log(firstArc);
let newArcs;
if (nbrArcs === "broadside") { if (nbrArcs === "broadside") {
arcs = broadside; newArcs = broadside;
return; } else {
}
let first_index = all_arcs.findIndex((arc) => arc === firstArc); let first_index = all_arcs.findIndex((arc) => arc === firstArc);
if (first_index === -1) first_index = 0; if (first_index === -1) first_index = 0;
arcs = Array.from({length: nbrArcs}).map( newArcs = Array.from({ length: nbrArcs }).map(
(_dummy, i) => all_arcs[(first_index + i) % all_arcs.length] (_dummy, i) => all_arcs[(first_index + i) % all_arcs.length]
); );
arcsCaches = arcs.join(','); }
$: console.log({ newArcs, arcs });
if (
arcs.length !== newArcs.length ||
arcs.length !== R.intersection(arcs, newArcs).length
) {
arcs = newArcs;
}
} }
$: if (arcs.length !== nbrArcs) setArcs(arcs[0]); $: if (arcs.length !== nbrArcs) setArcs(arcs[0]);
$: console.log("it changed!", arcs) $: console.log("it changed!", arcs);
$: console.log("it changed!", arcsCaches)
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
$: dispatch("change", { let i = 5;
const memoChange = memoize((weaponClass, ...arcs) =>
dispatch("change", {
weaponClass, weaponClass,
arcs: arcsCaches.split(','), arcs,
}); })
);
$: memoChange(weaponClass, ...arcs);
</script> </script>

View File

@ -1,27 +1,14 @@
<label>needle weapon</label> <label>needle weapon</label>
<Arcs selected={[arc]} on:click_arc={({ detail }) => click_arc(detail)} /> <Arcs selected={[arc]} on:clickArc={({ detail }) => clickArc(detail)} />
<script> <script lang="ts">
import { getContext } from "svelte";
import Arcs from "./Arcs.svelte"; import Arcs from "./Arcs.svelte";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
const all_arcs = ["FS", "F", "FP", "AP", "A", "AS"];
export let arc = "F"; export let arc = "F";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const click_arc = (arc) => { const clickArc = (arc) => dispatch("change", { arc });
dispatch("change",{arc});
};
</script> </script>
<style>
.arc {
display: flex;
flex-direction: column;
margin-right: 1em;
}
</style>

View File

@ -1,19 +1,16 @@
<label>submunition pack</label> <label>submunition pack</label>
<Arcs selected={[arc]} on:click_arc={({ detail }) => click_arc(detail)} /> <Arcs selected={[arc]} on:clickArc={({ detail }) => clickArc(detail)} />
<script> <script>
import { getContext } from "svelte";
import Arcs from "./Arcs.svelte"; import Arcs from "./Arcs.svelte";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
const all_arcs = ["FS", "F", "FP", "AP", "A", "AS"];
export let arc = "F"; export let arc = "F";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const click_arc = (arc) => { const clickArc = (arc) => {
dispatch("change", { arc }); dispatch("change", { arc });
}; };
</script> </script>

View File

@ -1,15 +1,8 @@
<ShipItem {...reqs}> <ShipItem {...reqs}>
<div class="weapon_row"> <div class="weapon_row">
<button <button class="button small red remove" on:click={remove}>remove </button>
class="button small red remove"
on:click={remove}>remove
</button>
<svelte:component <svelte:component this={component[type]} {...specs} on:change={update} />
this={component[type]}
{...weapon}
on:change={update}
/>
</div> </div>
</ShipItem> </ShipItem>
@ -33,20 +26,19 @@
needle: Needle, needle: Needle,
}; };
export let weapon = {}; export let reqs = {};
$: reqs = weapon.reqs; export let specs = {};
export let id; export let id;
const ship = getContext("ship"); const api = getContext("api");
$: type = weapon.type; $: type = specs.type;
const remove = () => ship.dispatch.removeWeapon(id); const remove = () => api?.dispatch?.removeWeapon?.(id);
const update = ({ detail }) => { const update = ({ detail }) => {
console.log({id,type}) console.log({ id, type });
ship.dispatch.setWeapon({ api?.dispatch?.setWeapon?.(id, {
id,
type, type,
...detail, ...detail,
}); });
@ -54,6 +46,9 @@
</script> </script>
<style> <style>
button {
width: inherit;
}
.weapon { .weapon {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -47,4 +47,11 @@ test("kicking the tires", () => {
mass: 6, mass: 6,
}, },
}); });
store.dispatch.addWeapon("beam");
expect(store.getState().weaponry.weapons[0]).toEqual({
id: 1,
reqs: { cost: 3, mass: 1 },
specs: { arcs: ["F"], type: "beam", weaponClass: 1 },
});
}); });

View File

@ -66,7 +66,12 @@ shipDux.addReaction((api) => {
); );
}); });
shipDux.addReaction((api) => (state) => { shipDux.addReaction((api) => {
const setShipReqs = memoize((cost, usedMass) =>
api.dispatch.setShipReqs({ cost, usedMass })
);
return (state) => {
let cost = 0; let cost = 0;
let mass = 0; let mass = 0;
@ -77,17 +82,24 @@ shipDux.addReaction((api) => (state) => {
if (typeof subsystem !== "object") continue; if (typeof subsystem !== "object") continue;
if (subsystem.reqs) { if (subsystem.reqs) {
cost += subsystem.reqs.cost; cost += subsystem.reqs.cost ?? 0;
mass += subsystem.reqs.mass; mass += subsystem.reqs.mass ?? 0;
} }
subsystems.push(...Object.values(subsystem)); subsystems.push(...Object.values(subsystem));
} }
api.dispatch.setShipReqs({ cost, usedMass: mass }); if (Number.isNaN(cost)) {
console.log(state.weaponry.weapons);
throw new Error();
}
setShipReqs(cost, mass);
};
}); });
shipDux.addEffect((api) => (next) => (action) => { shipDux.addEffect((api) => (next) => (action) => {
console.log(action);
next(action); next(action);
}); });

View File

@ -1,58 +1,94 @@
import type { Reqs } from "$lib/shipDux/reqs"; import type { Reqs } from "$lib/shipDux/reqs";
export const arcs = ["FS", "F", "FP", "AP", "A", "AS"] as const;
export type Arc = (typeof arcs)[number];
export type WeaponType = "beam";
type Beam = {
type: "beam";
weaponClass: 1 | 2 | 3 | 4;
arcs: Arc[];
};
type Submunition = {
type: "submunition";
arc: Arc;
};
type PDS = {
type: "pds";
};
type Scattergun = { type: "scattergun" };
type Needle = { type: "needle"; arc: Arc };
export type Weapon = Beam | Submunition | PDS | Scattergun | Needle;
export const weaponTypes = [ export const weaponTypes = [
{ {
name: "beam",
type: "beam", type: "beam",
name: "beam",
reqs: beamReqs, reqs: beamReqs,
initial: { initial: {
type: "beam",
weaponClass: 1, weaponClass: 1,
}, arcs,
} as any as Beam,
}, },
{ {
name: "submunition pack",
type: "submunition", type: "submunition",
name: "submunition pack",
reqs: { mass: 1, cost: 3 }, reqs: { mass: 1, cost: 3 },
initial: { arc: "F" }, initial: { type: "submunition", arc: "F" } as Submunition,
}, },
{ {
name: "point defence system", name: "point defence system",
type: "pds", type: "pds",
reqs: { mass: 1, cost: 3 }, reqs: { mass: 1, cost: 3 },
initial: {}, initial: {
type: "pds",
},
}, },
{ {
name: "scattergun", name: "scattergun",
type: "scattergun", type: "scattergun",
reqs: { mass: 1, cost: 4 }, reqs: { mass: 1, cost: 4 },
initial: {}, initial: { type: "scattergun" },
}, },
{ {
name: "needle weapon", name: "needle weapon",
type: "needle", type: "needle",
reqs: { mass: 2, cost: 6 }, reqs: { mass: 2, cost: 6 },
initial: { arc: "F" }, initial: { arc: "F", type: "needle" },
}, },
]; ];
export function weaponReqs(weapon): Reqs { export function weaponReqs(weapon): Reqs {
const { reqs } = weaponTypes.find((wt) => wt.type === weapon.type) || {}; const { reqs } = weaponTypes.find((wt) => wt.type === weapon.type) || {};
if (!reqs) return {}; if (!reqs)
return {
cost: 0,
mass: 0,
};
if (typeof reqs === "function") return reqs(weapon); if (typeof reqs === "function") return reqs(weapon);
return reqs; return reqs;
} }
const isBroadside = (arcs) => { const isBroadside = (arcs: Arc[]) => {
if (arcs.length !== 4) return false; if (arcs.length !== 4) return false;
// that'd be A or F // that'd be A or F
return !arcs.some((a) => a.length === 1); return !arcs.some((a) => a.length === 1);
}; };
function beamReqs({ weaponClass, arcs }) { function beamReqs({ weaponClass, arcs }: Beam) {
console.log(weaponClass, arcs);
let mass; let mass;
if (weaponClass === 1) { if (weaponClass === 1) {

View File

@ -0,0 +1,39 @@
import { weaponsDux } from "./weapons";
import Debug from "debug";
const debug = Debug("aotds:weapons");
import u from "@yanick/updeep-remeda";
process.env.UPDEEP_MODE = "dangerously_never_freeze";
test("setWeapon", () => {
const store = weaponsDux.createStore();
store.dispatch.addWeapon("beam");
store.dispatch.addWeapon("submunition");
expect(store.getState()).toMatchObject([
{
specs: {
type: "beam",
},
},
{
specs: {
type: "submunition",
},
},
]);
store.dispatch.setWeapon(1, {
type: "beam",
weaponClass: 2,
arcs: ["F", "FP", "FS"],
});
debug(store.getState());
expect(store.getState().find(u.matches({ id: 1 }))).toMatchObject({
specs: {
type: "beam",
weaponClass: 2,
arcs: ["FS", "F", "FP"],
},
});
});

View File

@ -2,23 +2,16 @@ import type { Reqs } from "$lib/shipDux/reqs";
import Updux from "updux"; import Updux from "updux";
import u from "@yanick/updeep-remeda"; import u from "@yanick/updeep-remeda";
import * as R from "remeda"; import * as R from "remeda";
import { weaponReqs } from "./rules"; import { weaponReqs, weaponTypes, type WeaponType, type Weapon } from "./rules";
import { nanoid } from "@reduxjs/toolkit";
type Weapon = { type IndexedWeapon = { id: number; reqs: Reqs; specs: Weapon };
weaponClass: string;
arcs?: unknown[];
type: string;
};
type IndexedWeapon = { id: string; reqs: Reqs; specs: Weapon };
export const weaponsDux = new Updux({ export const weaponsDux = new Updux({
initialState: [] as IndexedWeapon[], initialState: [] as IndexedWeapon[],
actions: { actions: {
removeWeapon: (id: string) => id, removeWeapon: (id: string) => id,
setWeapon: (id: string, specs: Weapon) => ({ id, specs }), setWeapon: (id: string, specs: Weapon) => ({ id, specs }),
addWeapon: (specs: Weapon) => specs, addWeapon: (type: WeaponType) => type,
}, },
}); });
@ -26,20 +19,35 @@ weaponsDux.addMutation(weaponsDux.actions.removeWeapon, (id) =>
R.reject(u.matches({ id })) R.reject(u.matches({ id }))
); );
weaponsDux.addMutation( // TODO not needed anymore
weaponsDux.actions.setWeapon, const mergeArcs = (newArcs) => (original) => {
({ id, specs }) => if (original === undefined) return undefined;
(state) => {
const weapon = state.find(u.matches({ id })); let merged = u.filter(original, (a) => newArcs.includes(a));
if (!weapon) return;
weapon.specs = specs; let toAdd = newArcs.filter((a) => !merged.includes(a));
weapon.reqs = weaponReqs(specs); if (toAdd.length) return [...merged, ...toAdd];
}
return merged;
};
weaponsDux.addMutation(weaponsDux.actions.setWeapon, ({ id, specs }) =>
u.map(
u.if(u.matches({ id }), {
specs: {
...specs,
arcs: mergeArcs((specs as any).arcs),
},
reqs: weaponReqs(specs),
})
)
); );
weaponsDux.addMutation(weaponsDux.actions.addWeapon, (specs) => (state) => { weaponsDux.addMutation(weaponsDux.actions.addWeapon, (type) => (state) => {
const specs = weaponTypes.find(u.matches({ type }))?.initial ?? {};
const id = 1 + Math.max(0, ...state.map(R.prop("id")));
state.push({ state.push({
id: nanoid(), id,
specs, specs,
reqs: weaponReqs(specs), reqs: weaponReqs(specs),
}); });