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}
<label>{label}</label>
{/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} />
<ADFC {...adfc} />
<!--
<AddWeapon />
<AddWeapon />
{#each weapons as weapon (weapon.id)}
<Weapon {weapon} id={weapon.id} />
{/each}
-->
{#each weapons as weapon (weapon.id)}
<Weapon {...weapon} />
{/each}
</Section>
<script>
@ -22,17 +20,14 @@
import Firecons from "./Weaponry/Firecons.svelte";
import ADFC from "./Weaponry/ADFC.svelte";
import AddWeapon from "./Weaponry/AddWeapon.svelte";
export let firecons = {};
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>
<style>

View File

@ -1,24 +1,25 @@
<Field label="weapon type">
<select bind:value={type}>
{#each weaponTypes as weapon (weapon.type)}
<option value={weapon.type}>{weapon.name}</option>
{/each}
</select>
<div>
<Field label="">
<select bind:value={type}>
{#each weaponTypes as weapon (weapon.type)}
<option value={weapon.type}>{weapon.name}</option>
{/each}
</select>
</Field>
<button class="button small primary" on:click={addWeapon}>add weapon</button>
</Field>
</div>
<script>
import { getContext } from "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>
<style>
@ -26,4 +27,17 @@
width: inherit;
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>

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">
{#each all_arcs as arc (arc)}
{#each arcs as arc (arc)}
<Arc
{arc}
radius={size / 2}
active={selected.includes(arc)}
on:click={() => click_arc(arc)}
on:click={() => clickArc(arc)}
/>
{/each}
<circle cx="50%" cy="50%" r={size / 3} />
@ -12,17 +12,17 @@
</svg>
<script>
import Arc from "./Arc.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 size = 60;
const dispatch = createEventDispatcher();
const click_arc = (arc) => dispatch("click_arc", arc);
const clickArc = (arc) => dispatch("clickArc", arc);
</script>
<style>

Before

Width:  |  Height:  |  Size: 639 B

After

Width:  |  Height:  |  Size: 634 B

View File

@ -1,80 +1,93 @@
<label>beam</label>
<Field label="beam class">
<select bind:value={weaponClass}>
<option value={1}>1</option>
<option value={2}>2</option>
<option value={3}>3</option>
<option value={4}>4</option>
</select>
<select bind:value={weaponClass}>
<option value={1}>1</option>
<option value={2}>2</option>
<option value={3}>3</option>
<option value={4}>4</option>
</select>
</Field>
<Field label="arcs">
<select bind:value={nbrArcs}>
{#each arc_options[weaponClass] || [] as nbrArcs (nbrArcs)}
<option>{nbrArcs}</option>
{/each}
</select>
<select bind:value={nbrArcs}>
{#each arc_options[weaponClass] || [] as nbrArcs (nbrArcs)}
<option>{nbrArcs}</option>
{/each}
</select>
</Field>
<Arcs selected={arcs} on:click_arc={({ detail }) => setArcs(detail)} />
<Arcs selected={arcs} on:clickArc={({ detail }) => setArcs(detail)} />
<script>
import { getContext } from "svelte";
import { getContext } from "svelte";
import * as R from "remeda";
import Arcs from "../Arcs.svelte";
import ShipItem from "$lib/components/ShipItem.svelte";
import Field from "$lib/components/Field.svelte";
import Arcs from "../Arcs.svelte";
import ShipItem from "$lib/components/ShipItem.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"];
export let weaponClass = 1;
export let arcs = ["F"];
export let weaponClass = 1;
export let arcs = ["F"];
let arc_options = {
1: [6],
2: [3, 6],
3: [1, 2, 3, 4, 5, 6, "broadside"],
4: [1, 2, 3, 4, 5, 6, "broadside"],
};
let arc_options = {
1: [6],
2: [3, 6],
3: [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)) {
nbrArcs = arc_options[weaponClass][0];
console.log({ nbrArcs, label: "in if" });
}
$: if (!arc_options[weaponClass].includes(nbrArcs)) {
nbrArcs = arc_options[weaponClass][0];
console.log({nbrArcs, label:"in if"})
const broadside = ["FS", "FP", "AP", "AS"];
function setArcs(firstArc) {
console.log(firstArc);
let newArcs;
if (nbrArcs === "broadside") {
newArcs = broadside;
} else {
let first_index = all_arcs.findIndex((arc) => arc === firstArc);
if (first_index === -1) first_index = 0;
newArcs = Array.from({ length: nbrArcs }).map(
(_dummy, i) => all_arcs[(first_index + i) % all_arcs.length]
);
}
const broadside = ["FS", "FP", "AP", "AS"];
function setArcs(firstArc) {
if (nbrArcs === "broadside") {
arcs = broadside;
return;
}
let first_index = all_arcs.findIndex((arc) => arc === firstArc);
if (first_index === -1) first_index = 0;
arcs = Array.from({length: nbrArcs}).map(
(_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!", arcsCaches)
$: console.log("it changed!", arcs);
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher();
$: dispatch("change", {
weaponClass,
arcs: arcsCaches.split(','),
});
let i = 5;
const memoChange = memoize((weaponClass, ...arcs) =>
dispatch("change", {
weaponClass,
arcs,
})
);
$: memoChange(weaponClass, ...arcs);
</script>

View File

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

View File

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

View File

@ -1,15 +1,8 @@
<ShipItem {...reqs}>
<div class="weapon_row">
<button
class="button small red remove"
on:click={remove}>remove
</button>
<button class="button small red remove" on:click={remove}>remove </button>
<svelte:component
this={component[type]}
{...weapon}
on:change={update}
/>
<svelte:component this={component[type]} {...specs} on:change={update} />
</div>
</ShipItem>
@ -19,34 +12,33 @@
import Arc from "./Arc.svelte";
import ShipItem from "$lib/components/ShipItem.svelte";
import Field from "$lib/components/Field.svelte";
import Beam from "./Beam/index.svelte";
import Submunition from "./Submunition.svelte";
import PointDefenceSystem from "./PDS.svelte";
import Scattergun from "./Scattergun.svelte";
import Beam from "./Beam/index.svelte";
import Submunition from "./Submunition.svelte";
import PointDefenceSystem from "./PDS.svelte";
import Scattergun from "./Scattergun.svelte";
import Needle from "./Needle.svelte";
const component = {
beam: Beam,
submunition: Submunition,
pds: PointDefenceSystem,
scattergun: Scattergun,
needle: Needle,
beam: Beam,
submunition: Submunition,
pds: PointDefenceSystem,
scattergun: Scattergun,
needle: Needle,
};
export let weapon = {};
$: reqs = weapon.reqs;
export let reqs = {};
export let specs = {};
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 }) => {
console.log({id,type})
ship.dispatch.setWeapon({
id,
console.log({ id, type });
api?.dispatch?.setWeapon?.(id, {
type,
...detail,
});
@ -54,6 +46,9 @@
</script>
<style>
button {
width: inherit;
}
.weapon {
display: flex;
align-items: center;

View File

@ -47,4 +47,11 @@ test("kicking the tires", () => {
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,28 +66,40 @@ shipDux.addReaction((api) => {
);
});
shipDux.addReaction((api) => (state) => {
let cost = 0;
let mass = 0;
shipDux.addReaction((api) => {
const setShipReqs = memoize((cost, usedMass) =>
api.dispatch.setShipReqs({ cost, usedMass })
);
let subsystems = R.values(R.omit(state, ["identification"]));
return (state) => {
let cost = 0;
let mass = 0;
while (subsystems.length > 0) {
const subsystem = subsystems.shift();
if (typeof subsystem !== "object") continue;
let subsystems = R.values(R.omit(state, ["identification"]));
if (subsystem.reqs) {
cost += subsystem.reqs.cost;
mass += subsystem.reqs.mass;
while (subsystems.length > 0) {
const subsystem = subsystems.shift();
if (typeof subsystem !== "object") continue;
if (subsystem.reqs) {
cost += subsystem.reqs.cost ?? 0;
mass += subsystem.reqs.mass ?? 0;
}
subsystems.push(...Object.values(subsystem));
}
subsystems.push(...Object.values(subsystem));
}
if (Number.isNaN(cost)) {
console.log(state.weaponry.weapons);
throw new Error();
}
api.dispatch.setShipReqs({ cost, usedMass: mass });
setShipReqs(cost, mass);
};
});
shipDux.addEffect((api) => (next) => (action) => {
console.log(action);
next(action);
});

View File

@ -1,58 +1,94 @@
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 = [
{
name: "beam",
type: "beam",
name: "beam",
reqs: beamReqs,
initial: {
type: "beam",
weaponClass: 1,
},
arcs,
} as any as Beam,
},
{
name: "submunition pack",
type: "submunition",
name: "submunition pack",
reqs: { mass: 1, cost: 3 },
initial: { arc: "F" },
initial: { type: "submunition", arc: "F" } as Submunition,
},
{
name: "point defence system",
type: "pds",
reqs: { mass: 1, cost: 3 },
initial: {},
initial: {
type: "pds",
},
},
{
name: "scattergun",
type: "scattergun",
reqs: { mass: 1, cost: 4 },
initial: {},
initial: { type: "scattergun" },
},
{
name: "needle weapon",
type: "needle",
reqs: { mass: 2, cost: 6 },
initial: { arc: "F" },
initial: { arc: "F", type: "needle" },
},
];
export function weaponReqs(weapon): Reqs {
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);
return reqs;
}
const isBroadside = (arcs) => {
const isBroadside = (arcs: Arc[]) => {
if (arcs.length !== 4) return false;
// that'd be A or F
return !arcs.some((a) => a.length === 1);
};
function beamReqs({ weaponClass, arcs }) {
function beamReqs({ weaponClass, arcs }: Beam) {
console.log(weaponClass, arcs);
let mass;
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 u from "@yanick/updeep-remeda";
import * as R from "remeda";
import { weaponReqs } from "./rules";
import { nanoid } from "@reduxjs/toolkit";
import { weaponReqs, weaponTypes, type WeaponType, type Weapon } from "./rules";
type Weapon = {
weaponClass: string;
arcs?: unknown[];
type: string;
};
type IndexedWeapon = { id: string; reqs: Reqs; specs: Weapon };
type IndexedWeapon = { id: number; reqs: Reqs; specs: Weapon };
export const weaponsDux = new Updux({
initialState: [] as IndexedWeapon[],
actions: {
removeWeapon: (id: string) => id,
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 }))
);
weaponsDux.addMutation(
weaponsDux.actions.setWeapon,
({ id, specs }) =>
(state) => {
const weapon = state.find(u.matches({ id }));
if (!weapon) return;
weapon.specs = specs;
weapon.reqs = weaponReqs(specs);
}
// TODO not needed anymore
const mergeArcs = (newArcs) => (original) => {
if (original === undefined) return undefined;
let merged = u.filter(original, (a) => newArcs.includes(a));
let toAdd = newArcs.filter((a) => !merged.includes(a));
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({
id: nanoid(),
id,
specs,
reqs: weaponReqs(specs),
});