mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-27 10:03:48 +01:00
Added zoom/pan to the myrian map
This commit is contained in:
parent
d2667fd0fb
commit
ecf9b5c389
@ -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
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");
|
||||||
|
Loading…
Reference in New Issue
Block a user