Implement gameover check, add wall and artillery, buff laser

main
Shad Amethyst 10 months ago
parent fa07df8452
commit 91977f9f0f

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="173.2"
height="200"
viewBox="0 0 1.732 2"
version="1.1"
id="svg7723"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="tile-artillery.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7725"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="32"
inkscape:cx="119.29687"
inkscape:cy="65.734375"
inkscape:window-width="1920"
inkscape:window-height="1022"
inkscape:window-x="0"
inkscape:window-y="36"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs7720">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6395">
<rect
style="fill:#525252;stroke-width:0.025;stroke-linecap:round"
id="rect6397"
width="0.60000002"
height="0.23373011"
x="0.52991241"
y="0.81055784" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6778">
<path
style="display:inline;fill:#525252;stroke-width:0.025;stroke-linecap:round"
id="path6780"
sodipodi:type="arc"
sodipodi:cx="0.866"
sodipodi:cy="1.1831347"
sodipodi:rx="0.30000001"
sodipodi:ry="0.30000001"
sodipodi:start="3.1415927"
sodipodi:end="6.2831853"
sodipodi:arc-type="slice"
d="M 0.56599998,1.1831347 A 0.30000001,0.30000001 0 0 1 0.866,0.88313466 0.30000001,0.30000001 0 0 1 1.166,1.1831347 h -0.3 z"
clip-path="none" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath7670">
<path
style="color:#000000;display:inline;fill:#3d3d3d;stroke-width:1;stroke-linecap:square;-inkscape-stroke:none"
d="m 1.1679687,0.4921875 -0.023437,0.0429687 -0.027344,0.0449219 -0.023437,0.0429687 0.085937,0.0507813 0.025391,-0.0429687 0.025391,-0.0449219 0.025391,-0.0429687 z"
id="path7672" />
</clipPath>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:type="star"
style="display:none;fill:#707070;fill-opacity:1;stroke:#4050f0;stroke-width:0.132184;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
id="path10510"
inkscape:flatsided="true"
sodipodi:sides="6"
sodipodi:cx="2.6478419"
sodipodi:cy="0.71499872"
sodipodi:r1="0.60318714"
sodipodi:r2="0.5223754"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.0471976"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 3.1702173,1.0165923 2.6478419,1.3181859 2.1254665,1.0165923 l 0,-0.60318715 0.5223754,-0.30159357 0.5223754,0.30159357 z"
transform="matrix(1.471662,0,0,1.471662,-3.0307033,-0.05223681)"
inkscape:label="bg" />
<path
style="color:#000000;fill:#3d3d3d;stroke-linecap:square;-inkscape-stroke:none"
d="M 1.1614694,0.65416081 0.94458375,1.0298178 1.024946,1.0649095 1.2369364,0.6977322 Z"
id="path6834"
sodipodi:nodetypes="ccccc"
inkscape:label="barrel" />
<path
style="color:#000000;fill:#363636;fill-opacity:1;stroke-linecap:square;-inkscape-stroke:none"
d="M 1.2213559,0.69118625 0.995494,1.0823905 1.0106917,1.0913705 1.2353013,0.69879238 Z"
id="path7595"
sodipodi:nodetypes="ccccc"
inkscape:label="barrel" />
<path
style="fill:#3d3d3d;fill-opacity:1;stroke-width:0.025;stroke-linecap:round"
id="path6339"
sodipodi:type="arc"
sodipodi:cx="0.82991242"
sodipodi:cy="1.1105579"
sodipodi:rx="0.30000001"
sodipodi:ry="0.30000001"
sodipodi:start="3.1415927"
sodipodi:end="6.2831853"
sodipodi:arc-type="slice"
d="m 0.52991241,1.1105579 a 0.30000001,0.30000001 0 0 1 0.30000001,-0.3 0.30000001,0.30000001 0 0 1 0.29999998,0.3 H 0.82991242 Z"
clip-path="url(#clipPath6395)"
transform="translate(0.03608757,0.20878459)"
inkscape:label="base" />
<path
style="fill:#363636;fill-opacity:1;stroke-width:0.025;stroke-linecap:round"
id="path6408"
clip-path="url(#clipPath6778)"
sodipodi:type="arc"
sodipodi:cx="1.166"
sodipodi:cy="1.1168647"
sodipodi:rx="0.19997001"
sodipodi:ry="0.31266999"
sodipodi:start="3.1415927"
sodipodi:end="0"
sodipodi:open="true"
sodipodi:arc-type="arc"
d="M 0.96603,1.1168647 A 0.19997001,0.31266999 0 0 1 1.166,0.80419469 0.19997001,0.31266999 0 0 1 1.36597,1.1168647"
inkscape:label="base-shadow"
transform="translate(0,0.13620788)" />
<path
style="color:#000000;fill:#3d3d3d;stroke-linecap:square;-inkscape-stroke:none"
d="m 1.1527447,0.61960586 -0.075218,0.13028179 0.11848,0.0684044 0.075218,-0.13028175 z"
id="path6836"
sodipodi:nodetypes="ccccc" />
<path
style="display:inline;fill:#363636;fill-opacity:1;stroke:none;stroke-width:0.01px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 1.2123151,0.51702852 0.043534,0.0251342 -0.076244,0.13205773 -0.043534,-0.0251342 z"
id="path6838"
sodipodi:nodetypes="ccccc"
clip-path="url(#clipPath7670)"
inkscape:label="nuzzle-shadow"
transform="translate(0.0158886,0.14495959)" />
<path
style="fill:none;stroke:#525252;stroke-width:0.01;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 1.156027,0.64275753 0.022391,0.012937"
id="path7680"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#525252;stroke-width:0.01;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 1.1469438,0.65849014 0.022391,0.012937"
id="path7684"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

@ -8,7 +8,7 @@
version="1.1"
id="svg7723"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="tile-pawn.svg"
sodipodi:docname="tile-defender.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
@ -24,13 +24,13 @@
inkscape:deskcolor="#505050"
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="2.8284271"
inkscape:cx="54.623999"
inkscape:cy="121.44559"
inkscape:zoom="5.6568542"
inkscape:cx="61.429902"
inkscape:cy="70.799067"
inkscape:window-width="1920"
inkscape:window-height="1022"
inkscape:window-height="1058"
inkscape:window-x="0"
inkscape:window-y="36"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
@ -65,10 +65,6 @@
style="fill:#363636;stroke:none;stroke-width:0.01px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
d="M 0.86738879,0.71555401 0.86935783,1.5023993 1.2133134,1.1584438 V 0.62286381 Z"
id="path26980" />
<path
style="fill:none;stroke:none;stroke-width:0.01px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0.67836396,0.66490496 -0.18905953,0.32746071 7.14e-6,-0.07774 0.15018855,-0.26013421 z"
id="path26984" />
<path
style="fill:#525252;fill-opacity:1;stroke:none;stroke-width:0.01px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0.52308404,0.66314323 -1.5e-5,0.16340521 0.18903983,-0.11275616 z"

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="173.2"
height="200"
viewBox="0 0 1.732 2"
version="1.1"
id="svg7723"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="tile-wall.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7725"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="2.8284271"
inkscape:cx="85.736697"
inkscape:cy="87.504464"
inkscape:window-width="1920"
inkscape:window-height="1058"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs7720" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:type="star"
style="display:none;fill:#707070;fill-opacity:1;stroke:#4050f0;stroke-width:0.132184;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
id="path10510"
inkscape:flatsided="true"
sodipodi:sides="6"
sodipodi:cx="2.6478419"
sodipodi:cy="0.71499872"
sodipodi:r1="0.60318714"
sodipodi:r2="0.5223754"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.0471976"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 3.1702173,1.0165923 2.6478419,1.3181859 2.1254665,1.0165923 l 0,-0.60318715 0.5223754,-0.30159357 0.5223754,0.30159357 z"
transform="matrix(1.471662,0,0,1.471662,-3.0307033,-0.05223681)"
inkscape:label="bg" />
<rect
style="fill:#3d3d3d;fill-opacity:1;stroke-width:0.0601122;stroke-linecap:round"
id="rect451"
width="0.90000004"
height="0.60000002"
x="0.41599998"
y="0.69999999"
inkscape:label="wall-bg" />
<g
id="g2345"
inkscape:label="creases">
<path
style="fill:none;stroke:#363636;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 0.41599998,1.105 H 1.316"
id="path509" />
<path
style="fill:none;stroke:#363636;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 0.41599998,0.90500001 H 1.316"
id="path507" />
<path
style="fill:none;stroke:#363636;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 0.72100001,0.69999999 V 0.88000001"
id="path2136" />
<path
style="fill:none;stroke:#363636;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 1.021,0.69999999 V 0.88000001"
id="path2138" />
<path
style="fill:none;stroke:#363636;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 0.72100001,1.13 V 1.2984835"
id="path2209" />
<path
style="fill:none;stroke:#363636;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 1.021,1.13 V 1.2984835"
id="path2211" />
<path
style="fill:none;stroke:#363636;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 0.566,0.93000001 V 1.08"
id="path2213" />
<path
style="fill:none;stroke:#363636;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 0.86599999,0.93000001 V 1.08"
id="path2215" />
<path
style="fill:none;stroke:#363636;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 1.166,0.93000001 1.1660179,1.08"
id="path2219"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#363636;stroke-width:0.025;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 0.41599999,1.2875 H 1.316"
id="path2427" />
<path
style="fill:none;stroke:#363636;stroke-width:0.025;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 0.416,0.71249999 h 0.9"
id="path2429" />
<path
style="fill:none;stroke:#363636;stroke-width:0.025;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 0.42849998,0.69999999 V 0.88000001"
id="path4609" />
<path
style="fill:none;stroke:#363636;stroke-width:0.025;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 1.3035,0.69999999 V 0.88000001"
id="path4611" />
<path
style="fill:none;stroke:#363636;stroke-width:0.025;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 0.42849999,1.1199999 V 1.3"
id="path4613" />
<path
style="fill:none;stroke:#363636;stroke-width:0.025;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 1.3035,1.1199999 V 1.3"
id="path4615" />
</g>
<g
id="g2328"
inkscape:label="highlights">
<path
style="fill:#525252;fill-opacity:1;stroke:none;stroke-width:0.01px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0.90096226,0.94004225 v 0.0404534 l 0.0701364,-0.0404534 z"
id="path2319"
sodipodi:nodetypes="cccc" />
<path
style="fill:#525252;fill-opacity:1;stroke:none;stroke-width:0.01px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0.75596228,0.73504223 v 0.0404534 l 0.0701364,-0.0404534 z"
id="path2321"
sodipodi:nodetypes="cccc" />
<path
style="fill:#525252;fill-opacity:1;stroke:none;stroke-width:0.01px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 1.0559623,0.73504223 v 0.0404534 l 0.070136,-0.0404534 z"
id="path2323"
sodipodi:nodetypes="cccc" />
<path
style="fill:#525252;fill-opacity:1;stroke:none;stroke-width:0.01px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0.60096227,0.94004225 v 0.0404534 l 0.0701364,-0.0404534 z"
id="path2221"
sodipodi:nodetypes="cccc" />
<path
style="fill:#525252;fill-opacity:1;stroke:none;stroke-width:0.01px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0.45096225,0.73504223 v 0.0404534 l 0.0701364,-0.0404534 z"
id="path2348"
sodipodi:nodetypes="cccc" />
<path
style="fill:#525252;fill-opacity:1;stroke:none;stroke-width:0.01px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 1.2009802,0.94003926 v 0.0404534 l 0.070136,-0.0404534 z"
id="path2350"
sodipodi:nodetypes="cccc" />
<path
style="fill:#525252;fill-opacity:1;stroke:none;stroke-width:0.01px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0.75596228,1.1400422 v 0.040453 l 0.070136,-0.040453 z"
id="path2352"
sodipodi:nodetypes="cccc" />
<path
style="fill:#525252;fill-opacity:1;stroke:none;stroke-width:0.01px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 1.0559623,1.1400422 v 0.040453 l 0.070136,-0.040453 z"
id="path2354"
sodipodi:nodetypes="cccc" />
<path
style="fill:#525252;fill-opacity:1;stroke:none;stroke-width:0.01px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0.45096226,1.1400422 v 0.040453 l 0.070136,-0.040453 z"
id="path2356"
sodipodi:nodetypes="cccc" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

@ -49,6 +49,7 @@ export default function Game(props: GameProps) {
...(props.playerID ? { playerID: props.playerID } : {}),
...("credentials" in props ? { credentials: props.credentials } : {}),
});
const [gameOver, setGameOver] = createSignal(client.getState()?.ctx.gameover ?? false);
const [state, setState] = createStore<game.State>(clone(client.getState()?.G) ?? {
resources: {},
cells: {},
@ -62,7 +63,7 @@ export default function Game(props: GameProps) {
start: startAnimation,
running: animationRunning,
get: getAnimationState,
} = useAnimation(state, 250);
} = useAnimation(state, 333);
const [hoveredCell, setHoveredCell] = createSignal<[number, number] | null>(null);
const [selectedTool, setSelectedTool] = createSignal<Tool>({
@ -91,6 +92,7 @@ export default function Game(props: GameProps) {
state,
getTransform,
hoveredCell,
gameOver,
selectedTool,
getAnimationState,
playerID: props.playerID,
@ -115,7 +117,7 @@ export default function Game(props: GameProps) {
getTransform,
clickTile(x: number, y: number) {
if (!props.playerID) return;
if (animationRunning()) return;
if (animationRunning() || gameOver()) return;
const tool = selectedTool();
@ -159,6 +161,8 @@ export default function Game(props: GameProps) {
startAnimation();
}
setGameOver(state.ctx.gameover ?? false);
// For some ungodly reason, I need to clone the state, because otherwise the frozenness sporadically gets
// saved in the store, triggering various issues later down the line.
// My guess for as to why is that `state.G` can be mutated and some of its properties are passed to Object.freeze
@ -196,7 +200,7 @@ export default function Game(props: GameProps) {
}}
/>
<div class={classes.turn}>
Turn {turn()}
Turn {turn()}{gameOver() ? "- Game over" : ""}
</div>
</div>
<Show when={props.matchID}>

@ -14,6 +14,7 @@ export type UseDrawBoardOptions = {
getAnimationState: Accessor<AnimationState<game.State>>,
playerID?: string | undefined,
targetTiles: Accessor<[x: number, y: number][]>,
gameOver: Accessor<boolean>,
};
export default function useDrawBoard(options: UseDrawBoardOptions) {
@ -25,7 +26,8 @@ export default function useDrawBoard(options: UseDrawBoardOptions) {
selectedTool,
getAnimationState,
playerID,
targetTiles
targetTiles,
gameOver
} = options;
const [tileImages] = createResource(loadTileImages);
@ -54,7 +56,7 @@ export default function useDrawBoard(options: UseDrawBoardOptions) {
const transform = getTransform();
const transformedCtx = new draw.TransformedCanvas2DCtx(ctx, transform);
if (hoveredCell() && !animationRunning()) {
if (hoveredCell() && !animationRunning() && !gameOver()) {
const [x, y] = hoveredCell()!;
const tool = selectedTool();
let valid = true;

@ -1,5 +1,6 @@
import type { Game } from "boardgame.io";
import { INVALID_MOVE } from "boardgame.io/core";
import hexNeighborhood from "./utils/hexNeighborhood.ts";
// TODO: partial information
@ -60,7 +61,7 @@ export const Buildings = {
description: "Your first line of defense: has good HP and can attack a single adjacent tile to prevent enemy expansion to the border of your territory.",
humanName: "Defender",
cost: 2,
hp: 2,
hp: 3,
placedOn: ["road"],
attack: {
power: 1,
@ -85,23 +86,27 @@ export const Buildings = {
},
laser: {
name: "laser",
description: "A long-range defensive unit, able to cause moderate damage to 3 tiles in a row",
description: "A long-range defensive unit, able to cause moderate damage to 2 tiles in a row. Does not pierce",
humanName: "Laser",
cost: 5,
hp: 2,
cost: 4,
hp: 3,
placedOn: ["road", "defender"],
attack: {
power: 2,
cost: 2,
cost: 1,
maxMoves: 1,
targets(_cells, x, y) {
return [...adjacentCells(x, y)];
},
damageZone(_cells, x, y, targetX, targetY) {
damageZone(cells, x, y, targetX, targetY) {
const dx = targetX - x;
const dy = targetY - y;
const MAX_DIST = 2;
const squares = new Array(MAX_DIST).fill(null).map((_, dist): [number, number] => [x + dx * (dist + 1), y + dy * (dist + 1)]);
const maxDist = squares.findIndex(([x, y]) => !!getCell(cells, x, y)?.owner);
return new Array(3).fill(null).map((_, dist) => [x + dx * (dist + 1), y + dy * (dist + 1)]);
return squares.slice(0, maxDist === -1 ? MAX_DIST : maxDist + 1);
}
}
},
@ -113,7 +118,37 @@ export const Buildings = {
gain: 2,
hp: 2,
placedOn: ["road"]
}
},
wall: {
name: "wall",
description: "A strong defensive unit, that is unable to attack.",
humanName: "Wall",
cost: 3,
hp: 5,
placedOn: ["road", "defender"]
},
artillery: {
name: "artillery",
description: "A long-range unit, capable of shooting over defenses for moderate damage up to 3 tiles away.",
humanName: "Artillery",
cost: 6,
hp: 1,
placedOn: ["road", "defender"],
attack: {
cost: 2,
power: 2,
maxMoves: 1,
targets(_cells, x, y) {
return [
...hexNeighborhood(x, y, 2),
...hexNeighborhood(x, y, 3),
];
},
damageZone(_cells, _x, _y, targetX, targetY) {
return [[targetX, targetY]];
}
}
},
} as const satisfies Readonly<Record<string, Readonly<{
name: string,
description: string,
@ -196,6 +231,10 @@ export const AcrossTheHex: Game<State, Record<string, unknown>, SetupData> = {
}
applyMoves(ctx.G);
if (isGameOver(ctx.G)) {
ctx.events.endGame();
}
},
endIf(ctx) {
if (!ctx.ctx.activePlayers) return false;
@ -752,3 +791,13 @@ export function getLocalState(state: State, playerID: string): State["cells"] {
return res;
}
export function isGameOver(state: State): boolean {
const playersAlive: string[] = [];
for (const [_x, _y, cell] of iterateCells(state.cells)) {
if (cell.owner && cell.building === "base") {
if (!playersAlive.includes(cell.owner)) playersAlive.push(cell.owner);
}
}
return playersAlive.length <= 1;
}

@ -0,0 +1,39 @@
import { adjacentCells } from "../game.ts";
type Neighborhood = [x: number, y: number][];
const neighborhoodCache: Neighborhood[] = [];
function offset(neighborhood: Neighborhood, cx: number, cy: number): Neighborhood {
return neighborhood.map(([x2, y2]) => [x2 + cx, y2 + cy]);
}
export default function hexNeighborhood(cx: number, cy: number, radius: number): Neighborhood {
if (neighborhoodCache[radius]) {
return offset(neighborhoodCache[radius]!, cx, cy);
} else {
const open: [x: number, y: number, hop: number][] = [[0, 0, 0]];
const closed: Set<string> = new Set();
const result: Neighborhood = [];
while (open.length > 0) {
const [x, y, hop] = open.shift()!;
const key = `${x}:${y}`;
if (closed.has(key)) continue;
closed.add(key);
if (hop === radius) {
result.push([x, y]);
}
if (hop < radius) {
for (const [nx, ny] of adjacentCells(x, y)) {
open.push([nx, ny, hop + 1]);
}
}
}
neighborhoodCache[radius] = result;
return offset(result, cx, cy);
}
}
Loading…
Cancel
Save