rotation!

This commit is contained in:
Olivier Gagnon 2021-10-16 17:12:04 -04:00
parent c0420d1787
commit 092d5146b4
8 changed files with 94 additions and 50 deletions

@ -7,12 +7,14 @@ const noCharge = [FragmentType.None, FragmentType.Delete, FragmentType.Booster];
export interface IActiveFragmentParams { export interface IActiveFragmentParams {
x: number; x: number;
y: number; y: number;
rotation: number;
fragment: Fragment; fragment: Fragment;
} }
export class ActiveFragment { export class ActiveFragment {
id: number; id: number;
charge: number; charge: number;
rotation: number;
x: number; x: number;
y: number; y: number;
@ -21,13 +23,14 @@ export class ActiveFragment {
this.id = params.fragment.id; this.id = params.fragment.id;
this.x = params.x; this.x = params.x;
this.y = params.y; this.y = params.y;
this.charge = 1; this.charge = 0;
if (noCharge.includes(params.fragment.type)) this.charge = 0; this.rotation = params.rotation;
} else { } else {
this.id = -1; this.id = -1;
this.x = -1; this.x = -1;
this.y = -1; this.y = -1;
this.charge = -1; this.charge = -1;
this.rotation = -1;
} }
} }
@ -39,7 +42,8 @@ export class ActiveFragment {
const dy: number = other.y - this.y; const dy: number = other.y - this.y;
for (let j = 0; j < thisFragment.shape.length; j++) { for (let j = 0; j < thisFragment.shape.length; j++) {
for (let i = 0; i < thisFragment.shape[j].length; i++) { for (let i = 0; i < thisFragment.shape[j].length; i++) {
if (thisFragment.fullAt(i, j) && otherFragment.fullAt(i - dx, j - dy)) return true; if (thisFragment.fullAt(i, j, this.rotation) && otherFragment.fullAt(i - dx, j - dy, other.rotation))
return true;
} }
} }
@ -53,12 +57,12 @@ export class ActiveFragment {
} }
fullAt(worldX: number, worldY: number): boolean { fullAt(worldX: number, worldY: number): boolean {
return this.fragment().fullAt(worldX - this.x, worldY - this.y); return this.fragment().fullAt(worldX - this.x, worldY - this.y, this.rotation);
} }
neighboors(): number[][] { neighboors(): number[][] {
return this.fragment() return this.fragment()
.neighboors() .neighboors(this.rotation)
.map((cell) => [this.x + cell[0], this.y + cell[1]]); .map((cell) => [this.x + cell[0], this.y + cell[1]]);
} }
@ -66,7 +70,7 @@ export class ActiveFragment {
// We have to do a round trip because the constructor. // We have to do a round trip because the constructor.
const fragment = FragmentById(this.id); const fragment = FragmentById(this.id);
if (fragment === null) throw new Error("ActiveFragment id refers to unknown Fragment."); if (fragment === null) throw new Error("ActiveFragment id refers to unknown Fragment.");
const c = new ActiveFragment({ x: this.x, y: this.y, fragment: fragment }); const c = new ActiveFragment({ x: this.x, y: this.y, rotation: this.rotation, fragment: fragment });
c.charge = this.charge; c.charge = this.charge;
return c; return c;
} }

@ -17,30 +17,44 @@ export class Fragment {
this.limit = limit; this.limit = limit;
} }
fullAt(x: number, y: number): boolean { fullAt(x: number, y: number, rotation: number, debug = false): boolean {
if (y < 0) return false; if (y < 0) return false;
if (y >= this.shape.length) return false; if (y >= this.height(rotation)) return false;
if (x < 0) return false; if (x < 0) return false;
if (x >= this.shape[y].length) return false; if (x >= this.width(rotation)) return false;
// Yes it's ordered y first. // start xy, modifier xy
return this.shape[y][x]; let [sx, sy, mx, my] = [0, 0, 1, 1];
if (rotation === 1) {
[sx, sy, mx, my] = [this.width(rotation) - 1, 0, -1, 1];
} else if (rotation === 2) {
[sx, sy, mx, my] = [this.width(rotation) - 1, this.height(rotation) - 1, -1, -1];
} else if (rotation === 3) {
[sx, sy, mx, my] = [0, this.height(rotation) - 1, 1, -1];
}
let [qx, qy] = [sx + mx * x, sy + my * y];
if (rotation % 2 === 1) [qx, qy] = [qy, qx];
if (debug) {
console.log("q " + [qx, qy]);
}
return this.shape[qy][qx];
} }
width(): number { width(rotation: number): number {
// check every line for robustness. if (rotation % 2 === 0) return this.shape[0].length;
return Math.max(...this.shape.map((line) => line.length));
}
height(): number {
return this.shape.length; return this.shape.length;
} }
height(rotation: number): number {
if (rotation % 2 === 0) return this.shape.length;
return this.shape[0].length;
}
// List of direct neighboors of this fragment. // List of direct neighboors of this fragment.
neighboors(): number[][] { neighboors(rotation: number): number[][] {
const candidates: number[][] = []; const candidates: number[][] = [];
const add = (x: number, y: number): void => { const add = (x: number, y: number): void => {
if (this.fullAt(x, y)) return; if (this.fullAt(x, y, rotation)) return;
if (candidates.some((coord) => coord[0] === x && coord[1] === y)) return; if (candidates.some((coord) => coord[0] === x && coord[1] === y)) return;
candidates.push([x, y]); candidates.push([x, y]);
}; };

@ -10,8 +10,8 @@ export interface IStaneksGift {
charge(worldX: number, worldY: number, ram: number): number; charge(worldX: number, worldY: number, ram: number): number;
process(p: IPlayer, n: number): void; process(p: IPlayer, n: number): void;
effect(fragment: ActiveFragment): number; effect(fragment: ActiveFragment): number;
canPlace(x: number, y: number, fragment: Fragment): boolean; canPlace(x: number, y: number, rotation: number, fragment: Fragment): boolean;
place(x: number, y: number, fragment: Fragment): boolean; place(x: number, y: number, rotation: number, fragment: Fragment): boolean;
fragmentAt(worldX: number, worldY: number): ActiveFragment | null; fragmentAt(worldX: number, worldY: number): ActiveFragment | null;
deleteAt(worldX: number, worldY: number): boolean; deleteAt(worldX: number, worldY: number): boolean;
clear(): void; clear(): void;

@ -70,20 +70,20 @@ export class StaneksGift implements IStaneksGift {
return CalculateEffect(fragment.charge, fragment.fragment().power, boost); return CalculateEffect(fragment.charge, fragment.fragment().power, boost);
} }
canPlace(x: number, y: number, fragment: Fragment): boolean { canPlace(x: number, y: number, rotation: number, fragment: Fragment): boolean {
if (x + fragment.width() > this.width()) return false; if (x + fragment.width(0) > this.width()) return false;
if (y + fragment.height() > this.height()) return false; if (y + fragment.height(0) > this.height()) return false;
if (this.count(fragment) >= fragment.limit) return false; if (this.count(fragment) >= fragment.limit) return false;
const newFrag = new ActiveFragment({ x: x, y: y, fragment: fragment }); const newFrag = new ActiveFragment({ x: x, y: y, rotation: rotation, fragment: fragment });
for (const aFrag of this.fragments) { for (const aFrag of this.fragments) {
if (aFrag.collide(newFrag)) return false; if (aFrag.collide(newFrag)) return false;
} }
return true; return true;
} }
place(x: number, y: number, fragment: Fragment): boolean { place(x: number, y: number, rotation: number, fragment: Fragment): boolean {
if (!this.canPlace(x, y, fragment)) return false; if (!this.canPlace(x, y, rotation, fragment)) return false;
this.fragments.push(new ActiveFragment({ x: x, y: y, fragment: fragment })); this.fragments.push(new ActiveFragment({ x: x, y: y, rotation: rotation, fragment: fragment }));
return true; return true;
} }

@ -27,10 +27,14 @@ function FragmentOption(props: IOptionProps): React.ReactElement {
<Box display="flex"> <Box display="flex">
<Box sx={{ mx: 2 }}> <Box sx={{ mx: 2 }}>
<FragmentPreview <FragmentPreview
width={props.fragment.width()} width={props.fragment.width(0)}
height={props.fragment.height()} height={props.fragment.height(0)}
colorAt={(x, y) => { colorAt={(x, y) => {
return !props.fragment.fullAt(x, y) ? "" : props.fragment.type === FragmentType.Booster ? "blue" : "green"; return !props.fragment.fullAt(x, y, 0)
? ""
: props.fragment.type === FragmentType.Booster
? "blue"
: "green";
}} }}
/> />
</Box> </Box>

@ -39,11 +39,11 @@ function randomColor(fragment: ActiveFragment): string {
return `rgb(${colors[0] * 256}, ${colors[1] * 256}, ${colors[2] * 256})`; return `rgb(${colors[0] * 256}, ${colors[1] * 256}, ${colors[2] * 256})`;
} }
type GridProps = { interface IProps {
gift: IStaneksGift; gift: IStaneksGift;
}; }
export function Grid(props: GridProps): React.ReactElement { export function MainBoard(props: IProps): React.ReactElement {
function calculateGrid(gift: IStaneksGift): any { function calculateGrid(gift: IStaneksGift): any {
const newgrid = zeros([gift.width(), gift.height()]); const newgrid = zeros([gift.width(), gift.height()]);
for (let i = 0; i < gift.width(); i++) { for (let i = 0; i < gift.width(); i++) {
@ -60,16 +60,19 @@ export function Grid(props: GridProps): React.ReactElement {
const [grid, setGrid] = React.useState(calculateGrid(props.gift)); const [grid, setGrid] = React.useState(calculateGrid(props.gift));
const [ghostGrid, setGhostGrid] = React.useState(zeros([props.gift.width(), props.gift.height()])); const [ghostGrid, setGhostGrid] = React.useState(zeros([props.gift.width(), props.gift.height()]));
const [pos, setPos] = React.useState([0, 0]); const [pos, setPos] = React.useState([0, 0]);
const [rotation, setRotation] = React.useState(0);
const [selectedFragment, setSelectedFragment] = React.useState(NoneFragment); const [selectedFragment, setSelectedFragment] = React.useState(NoneFragment);
function moveGhost(worldX: number, worldY: number): void { function moveGhost(worldX: number, worldY: number): void {
if (selectedFragment.type === FragmentType.None || selectedFragment.type === FragmentType.Delete) return;
const newgrid = zeros([props.gift.width(), props.gift.height()]); const newgrid = zeros([props.gift.width(), props.gift.height()]);
for (let i = 0; i < selectedFragment.shape.length; i++) { for (let y = 0; y < selectedFragment.height(rotation); y++) {
for (let j = 0; j < selectedFragment.shape[i].length; j++) { for (let x = 0; x < selectedFragment.width(rotation); x++) {
if (!selectedFragment.shape[i][j]) continue; console.log([x, y]);
if (worldX + j > newgrid.length - 1) continue; if (!selectedFragment.fullAt(x, y, rotation, true)) continue;
if (worldY + i > newgrid[worldX + j].length - 1) continue; if (worldX + x > newgrid.length - 1) continue;
newgrid[worldX + j][worldY + i] = 1; if (worldY + y > newgrid[worldX + x].length - 1) continue;
newgrid[worldX + x][worldY + y] = 1;
} }
} }
@ -86,8 +89,8 @@ export function Grid(props: GridProps): React.ReactElement {
if (selectedFragment.type == FragmentType.Delete) { if (selectedFragment.type == FragmentType.Delete) {
deleteAt(worldX, worldY); deleteAt(worldX, worldY);
} else { } else {
if (!props.gift.canPlace(worldX, worldY, selectedFragment)) return; if (!props.gift.canPlace(worldX, worldY, rotation, selectedFragment)) return;
props.gift.place(worldX, worldY, selectedFragment); props.gift.place(worldX, worldY, rotation, selectedFragment);
} }
setGrid(calculateGrid(props.gift)); setGrid(calculateGrid(props.gift));
} }
@ -130,6 +133,25 @@ export function Grid(props: GridProps): React.ReactElement {
setGhostGrid(newgrid); setGhostGrid(newgrid);
} }
React.useEffect(() => {
function doRotate(this: Document, event: KeyboardEvent): void {
if (event.key === "q") {
setRotation((rotation - 1 + 4) % 4);
console.log((rotation - 1 + 4) % 4);
}
if (event.key === "e") {
setRotation((rotation + 1) % 4);
console.log((rotation + 1) % 4);
}
}
document.addEventListener("keydown", doRotate);
return () => document.removeEventListener("keydown", doRotate);
});
// try {
// console.log(selectedFragment);
// } catch (err) {}
return ( return (
<> <>
<Button onClick={clear}>Clear</Button> <Button onClick={clear}>Clear</Button>

@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { StaneksGiftEvents } from "../StaneksGiftEvents"; import { StaneksGiftEvents } from "../StaneksGiftEvents";
import { Grid } from "./Grid"; import { MainBoard } from "./MainBoard";
import { IStaneksGift } from "../IStaneksGift"; import { IStaneksGift } from "../IStaneksGift";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
@ -30,7 +30,7 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
Bonus time: {convertTimeMsToTimeElapsedString(CONSTANTS._idleSpeed * staneksGift.storedCycles)} Bonus time: {convertTimeMsToTimeElapsedString(CONSTANTS._idleSpeed * staneksGift.storedCycles)}
</Typography> </Typography>
)} )}
<Grid gift={staneksGift} /> <MainBoard gift={staneksGift} />
</> </>
); );
} }

@ -12,8 +12,8 @@ export interface INetscriptStanek {
fragmentDefinitions(): any; fragmentDefinitions(): any;
placedFragments(): any; placedFragments(): any;
clear(): void; clear(): void;
canPlace(worldX: number, worldY: number, fragmentId: number): boolean; canPlace(worldX: number, worldY: number, rotation: number, fragmentId: number): boolean;
place(worldX: number, worldY: number, fragmentId: number): boolean; place(worldX: number, worldY: number, rotation: number, fragmentId: number): boolean;
fragmentAt(worldX: number, worldY: number): any; fragmentAt(worldX: number, worldY: number): any;
deleteAt(worldX: number, worldY: number): boolean; deleteAt(worldX: number, worldY: number): boolean;
} }
@ -55,19 +55,19 @@ export function NetscriptStanek(
//checkStanekAPIAccess("clear"); //checkStanekAPIAccess("clear");
staneksGift.clear(); staneksGift.clear();
}, },
canPlace: function (worldX: any, worldY: any, fragmentId: any): any { canPlace: function (worldX: any, worldY: any, rotation: any, fragmentId: any): any {
helper.updateDynamicRam("canPlace", getRamCost("stanek", "canPlace")); helper.updateDynamicRam("canPlace", getRamCost("stanek", "canPlace"));
//checkStanekAPIAccess("canPlace"); //checkStanekAPIAccess("canPlace");
const fragment = FragmentById(fragmentId); const fragment = FragmentById(fragmentId);
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.canPlace", `Invalid fragment id: ${fragmentId}`); if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.canPlace", `Invalid fragment id: ${fragmentId}`);
return staneksGift.canPlace(worldX, worldY, fragment); return staneksGift.canPlace(worldX, worldY, rotation, fragment);
}, },
place: function (worldX: any, worldY: any, fragmentId: any): any { place: function (worldX: any, worldY: any, rotation: any, fragmentId: any): any {
helper.updateDynamicRam("place", getRamCost("stanek", "place")); helper.updateDynamicRam("place", getRamCost("stanek", "place"));
//checkStanekAPIAccess("place"); //checkStanekAPIAccess("place");
const fragment = FragmentById(fragmentId); const fragment = FragmentById(fragmentId);
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.place", `Invalid fragment id: ${fragmentId}`); if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.place", `Invalid fragment id: ${fragmentId}`);
return staneksGift.place(worldX, worldY, fragment); return staneksGift.place(worldX, worldY, rotation, fragment);
}, },
fragmentAt: function (worldX: any, worldY: any): any { fragmentAt: function (worldX: any, worldY: any): any {
helper.updateDynamicRam("fragmentAt", getRamCost("stanek", "fragmentAt")); helper.updateDynamicRam("fragmentAt", getRamCost("stanek", "fragmentAt"));