mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-23 16:13:49 +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 { Player } from "@player";
|
||||
import { DefaultWorld } from "./World";
|
||||
import { MyrianTile } from "@nsdefs";
|
||||
|
||||
export interface MyrianSleeve {
|
||||
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. */
|
||||
toJSON(): IReviverValue {
|
||||
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 {
|
||||
tile: string;
|
||||
tileSize: number;
|
||||
}
|
||||
|
||||
const Cell = ({ tile }: ICellProps): React.ReactElement => {
|
||||
const x = 50;
|
||||
const Cell = ({ tile, tileSize: x }: ICellProps): React.ReactElement => {
|
||||
const sx = {
|
||||
display: "block",
|
||||
color: "white",
|
||||
@ -59,9 +59,37 @@ interface IProps {
|
||||
myrian: Myrian;
|
||||
}
|
||||
|
||||
const calcTiles = (v: number) => Math.floor((50 * 11) / v);
|
||||
|
||||
export function MyrianRoot({ myrian }: IProps): React.ReactElement {
|
||||
const [center, setCenter] = useState([myrian.world[0].length / 2, myrian.world.length / 2]);
|
||||
const [size, setSize] = useState(11);
|
||||
const [center, rawSetCenter] = useState<[number, number]>([14, 8]);
|
||||
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 rerender = () => setRerender((old) => !old);
|
||||
useEffect(() => {
|
||||
@ -72,15 +100,44 @@ export function MyrianRoot({ myrian }: IProps): React.ReactElement {
|
||||
const sleeves = Object.fromEntries(myrian.sleeves.map((s) => [`${s.x}_${s.y}`, s]));
|
||||
|
||||
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) => () => {
|
||||
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 (
|
||||
<Container maxWidth="lg" disableGutters sx={{ mx: 0, display: "flex", flexDirection: "column" }}>
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<div
|
||||
style={{ cursor: dragging ? "grabbing" : "grab" }}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseUp={onStopDragging}
|
||||
onMouseMove={onMouseMove}
|
||||
onMouseLeave={onStopDragging}
|
||||
onWheel={onWheel}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@ -92,17 +149,19 @@ export function MyrianRoot({ myrian }: IProps): React.ReactElement {
|
||||
borderWidth: "1px",
|
||||
}}
|
||||
>
|
||||
{iterator(size, center[1] - Math.floor(size / 2)).map((j) => (
|
||||
{iterator(tiles, center[1] - Math.floor(tiles / 2)).map((j) => (
|
||||
<Box key={myrian.world[j].join("") + j} sx={{ display: "flex", flexDirection: "row" }}>
|
||||
{iterator(size, center[0] - Math.floor(size / 2)).map((i) => (
|
||||
{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 sx={{ display: "flex" }}>
|
||||
|
@ -38,9 +38,7 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
|
||||
ianGetTile: (ctx) => (_x, _y) => {
|
||||
const x = helpers.number(ctx, "x", _x);
|
||||
const y = helpers.number(ctx, "y", _y);
|
||||
return {
|
||||
Content: myrian.world[y][x],
|
||||
};
|
||||
return myrian.getTile(x, y);
|
||||
},
|
||||
ianGetTask: (ctx) => (_sleeveId) => {
|
||||
throw new Error("Unimplemented");
|
||||
|
Loading…
Reference in New Issue
Block a user