Added zoom/pan to the myrian map

This commit is contained in:
Your Name 2023-08-02 23:16:02 -04:00
parent d2667fd0fb
commit ecf9b5c389
4 changed files with 173 additions and 30 deletions

@ -2,6 +2,7 @@ import { SleeveMyrianWork } from "../PersonObjects/Sleeve/Work/SleeveMyrianWork"
import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { Player } from "@player"; import { Player } from "@player";
import { DefaultWorld } from "./World"; import { DefaultWorld } from "./World";
import { MyrianTile } from "@nsdefs";
export interface MyrianSleeve { export interface MyrianSleeve {
index: number; index: number;
@ -56,6 +57,13 @@ export class Myrian {
} }
} }
getTile(x: number, y: number): MyrianTile {
if (x < 0 || y < 0 || y > this.world.length || x > this.world[y].length) return { Content: "?" };
return {
Content: this.world[y][x],
};
}
/** Serialize the current object to a JSON save state. */ /** Serialize the current object to a JSON save state. */
toJSON(): IReviverValue { toJSON(): IReviverValue {
return Generic_toJSON("Myrian", this); return Generic_toJSON("Myrian", this);

78
src/Myrian/aStar.ts Normal file

@ -0,0 +1,78 @@
import { Myrian } from "./Myrian";
export const aStar = (myrian: Myrian, start: [number, number], goal: [number, number]): [number, number][] => {
if (myrian.getTile(goal[0], goal[1]).Content !== " ") return [];
const h = (n: [number, number], m: [number, number]) => Math.abs(n[0] - m[0]) + Math.abs(n[1] - m[1]);
const d = (n: [number, number], m: [number, number]) => {
const tile = myrian.getTile(m[0], m[1]);
if (tile.Content !== " ") return Infinity;
return Math.abs(n[0] - m[0]) + Math.abs(n[1] - m[1]);
};
const openSet = new Set([start]);
const cameFrom = new Map<[number, number], [number, number]>();
const gScore = new Map<[number, number], number>();
gScore.set(start, 0);
const fScore = new Map<[number, number], number>();
fScore.set(start, h(start, goal));
const bestCurrent = () => {
let best;
for (const n of openSet.values()) {
if (best !== undefined && (fScore.get(n) ?? Infinity) >= (fScore.get(best) ?? Infinity)) continue;
best = n;
}
return best ?? [-1, -1];
};
const eq = (n: [number, number], m: [number, number]) => n[0] === m[0] && n[1] === m[1];
const neighbors = (n: [number, number]): Iterable<[number, number]> => {
const diffs = [
[-1, 0],
[+1, 0],
[0, -1],
[0, +1],
];
let i = -1;
return {
[Symbol.iterator]: () => ({
next: () =>
++i === diffs.length ? { value: [-1, -1], done: true } : { value: [n[0] + diffs[i][0], n[1] + diffs[i][1]] },
}),
};
};
/**
* @param {Map<[number, number], [number, number]>} cameFrom
* @param {[number, number]} current
*/
const reconstructPath = (cameFrom: Map<[number, number], [number, number]>, current: [number, number]) => {
const totalPath = [current];
while (cameFrom.has(current)) {
current = cameFrom.get(current) ?? [-1, -1];
totalPath.unshift(current);
}
return totalPath;
};
while (openSet.size) {
const current = bestCurrent();
if (eq(current, goal)) return reconstructPath(cameFrom, current);
openSet.delete(current);
for (const neighbor of neighbors(current)) {
const tentativegScore = (gScore.get(current) ?? Infinity) + d(current, neighbor);
if (tentativegScore >= (gScore.get(neighbor) ?? Infinity)) continue;
cameFrom.set(neighbor, current);
gScore.set(neighbor, tentativegScore);
fScore.set(neighbor, tentativegScore + h(neighbor, goal));
if (!openSet.has(neighbor)) openSet.add(neighbor);
}
}
return [];
};

@ -25,10 +25,10 @@ const iterator = (i: number, offset = 0): number[] => {
interface ICellProps { interface ICellProps {
tile: string; tile: string;
tileSize: number;
} }
const Cell = ({ tile }: ICellProps): React.ReactElement => { const Cell = ({ tile, tileSize: x }: ICellProps): React.ReactElement => {
const x = 50;
const sx = { const sx = {
display: "block", display: "block",
color: "white", color: "white",
@ -59,9 +59,37 @@ interface IProps {
myrian: Myrian; myrian: Myrian;
} }
const calcTiles = (v: number) => Math.floor((50 * 11) / v);
export function MyrianRoot({ myrian }: IProps): React.ReactElement { export function MyrianRoot({ myrian }: IProps): React.ReactElement {
const [center, setCenter] = useState([myrian.world[0].length / 2, myrian.world.length / 2]); const [center, rawSetCenter] = useState<[number, number]>([14, 8]);
const [size, setSize] = useState(11); const [size, rawSetSize] = useState(60);
const tiles = calcTiles(size);
const [dragging, setDragging] = useState(false);
const [dragScreenPos, setDragScreenPos] = useState<[number, number]>([0, 0]);
const [dragCenter, setDragCenter] = useState<[number, number]>([0, 0]);
const setCenter = (v: [number, number], tiles: number) => {
rawSetCenter(() => {
v[0] = Math.max(Math.floor(tiles / 2), v[0]);
v[1] = Math.max(Math.floor(tiles / 2), v[1]);
v[0] = Math.min(myrian.world[0].length - Math.floor(tiles / 2), v[0]);
v[1] = Math.min(myrian.world.length - Math.floor(tiles / 2), v[1]);
return v;
});
};
const setSize = (px: number) => {
const newTiles = calcTiles(px);
if (newTiles > 25) {
px = calcTiles(25);
}
if (newTiles < 5) {
px = calcTiles(5);
}
rawSetSize(px);
return px;
};
const [, setRerender] = useState(false); const [, setRerender] = useState(false);
const rerender = () => setRerender((old) => !old); const rerender = () => setRerender((old) => !old);
useEffect(() => { useEffect(() => {
@ -72,37 +100,68 @@ export function MyrianRoot({ myrian }: IProps): React.ReactElement {
const sleeves = Object.fromEntries(myrian.sleeves.map((s) => [`${s.x}_${s.y}`, s])); const sleeves = Object.fromEntries(myrian.sleeves.map((s) => [`${s.x}_${s.y}`, s]));
const move = (x: number, y: number) => () => { const move = (x: number, y: number) => () => {
setCenter((c) => [Math.max(Math.floor(size / 2), c[0] + x), Math.max(Math.floor(size / 2), c[1] + y)]); setCenter([center[0] + x, center[1] + y], tiles);
}; };
const zoom = (size: number) => () => { const zoom = (size: number) => () => {
setSize((s) => s + size); // setSize((s) => s + size);
}; };
const onMouseDown = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
setDragging(true);
setDragScreenPos([event.screenX, event.screenY]);
setDragCenter(center);
};
const onStopDragging = () => setDragging(false);
const onMouseMove = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (!dragging) return;
const dx = event.screenX - dragScreenPos[0] + size / 2;
const dy = event.screenY - dragScreenPos[1] + size / 2;
setCenter([dragCenter[0] - Math.floor(dx / size), dragCenter[1] - Math.floor(dy / size)], tiles);
};
const onWheel = (event: React.WheelEvent) => {
const newSize = setSize(size * (1 + event.deltaY / 500));
const newTiles = calcTiles(newSize);
setCenter(center, newTiles);
};
return ( return (
<Container maxWidth="lg" disableGutters sx={{ mx: 0, display: "flex", flexDirection: "column" }}> <Container maxWidth="lg" disableGutters sx={{ mx: 0, display: "flex", flexDirection: "column" }}>
<Box sx={{ display: "flex" }}> <Box sx={{ display: "flex" }}>
<Box <div
sx={{ style={{ cursor: dragging ? "grabbing" : "grab" }}
display: "flex", onMouseDown={onMouseDown}
flexDirection: "column", onMouseUp={onStopDragging}
m: 0, onMouseMove={onMouseMove}
p: 0, onMouseLeave={onStopDragging}
borderColor: "white", onWheel={onWheel}
borderStyle: "solid",
borderWidth: "1px",
}}
> >
{iterator(size, center[1] - Math.floor(size / 2)).map((j) => ( <Box
<Box key={myrian.world[j].join("") + j} sx={{ display: "flex", flexDirection: "row" }}> sx={{
{iterator(size, center[0] - Math.floor(size / 2)).map((i) => ( display: "flex",
<Cell flexDirection: "column",
key={i + "" + j + "" + myrian.world[j][i]} m: 0,
tile={sleeves[`${i}_${j}`] ? "s" : myrian.world[j][i]} p: 0,
/> borderColor: "white",
))} borderStyle: "solid",
</Box> borderWidth: "1px",
))} }}
</Box> >
{iterator(tiles, center[1] - Math.floor(tiles / 2)).map((j) => (
<Box key={myrian.world[j].join("") + j} sx={{ display: "flex", flexDirection: "row" }}>
{iterator(tiles, center[0] - Math.floor(tiles / 2)).map((i) => (
<Cell
key={i + "" + j + "" + myrian.world[j][i]}
tileSize={size}
tile={sleeves[`${i}_${j}`] ? "s" : myrian.world[j][i]}
/>
))}
</Box>
))}
</Box>
</div>
</Box> </Box>
<Box sx={{ display: "flex" }}> <Box sx={{ display: "flex" }}>

@ -38,9 +38,7 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
ianGetTile: (ctx) => (_x, _y) => { ianGetTile: (ctx) => (_x, _y) => {
const x = helpers.number(ctx, "x", _x); const x = helpers.number(ctx, "x", _x);
const y = helpers.number(ctx, "y", _y); const y = helpers.number(ctx, "y", _y);
return { return myrian.getTile(x, y);
Content: myrian.world[y][x],
};
}, },
ianGetTask: (ctx) => (_sleeveId) => { ianGetTask: (ctx) => (_sleeveId) => {
throw new Error("Unimplemented"); throw new Error("Unimplemented");