diff --git a/src/components/Board/Board.tsx b/src/components/Board/Board.tsx
index f6f138d..dae7f1d 100644
--- a/src/components/Board/Board.tsx
+++ b/src/components/Board/Board.tsx
@@ -12,7 +12,7 @@ import {
ChevronRightIcon,
ChevronUpIcon,
} from "@heroicons/react/24/outline";
-import { Orientation, SURFACES } from "../../constants";
+import { OrientabilityType, SURFACES } from "../../constants";
import { ChevronDoubleUpIcon } from "@heroicons/react/20/solid";
export const Board = forwardRef(({ gridArea }: { gridArea: string }, ref) => {
@@ -32,31 +32,31 @@ export const Board = forwardRef(({ gridArea }: { gridArea: string }, ref) => {
}}
>
- {surface.orientation.w !== Orientation.None ? : ""}
+ {surface.orientability.w !== OrientabilityType.None ? : ""}
- {surface.orientation.h !== Orientation.None ? : ""}
+ {surface.orientability.h !== OrientabilityType.None ? : ""}
{
{
- [Orientation.None]: "",
- [Orientation.Orientable]: ,
- [Orientation.Nonorientable]: ,
- [Orientation.ConsecutiveNonorientable]: ,
- [Orientation.ConsecutiveOrientable]: ,
- }[surface.orientation.h]
+ [OrientabilityType.None]: "",
+ [OrientabilityType.Orientable]: ,
+ [OrientabilityType.Nonorientable]: ,
+ [OrientabilityType.ConsecutiveNonorientable]: ,
+ [OrientabilityType.ConsecutiveOrientable]: ,
+ }[surface.orientability.h]
}
{
{
- [Orientation.None]: "",
- [Orientation.Orientable]: ,
- [Orientation.Nonorientable]: ,
- [Orientation.ConsecutiveNonorientable]: ,
- [Orientation.ConsecutiveOrientable]: ,
- }[surface.orientation.w]
+ [OrientabilityType.None]: "",
+ [OrientabilityType.Orientable]: ,
+ [OrientabilityType.Nonorientable]: ,
+ [OrientabilityType.ConsecutiveNonorientable]: ,
+ [OrientabilityType.ConsecutiveOrientable]: ,
+ }[surface.orientability.w]
}
void;
- rotateRight: () => void;
- reflectX: () => void;
- reflectY: () => void;
+ currentOrientation: Orientation;
+ orientationDispatch: Dispatch;
updateCurrentPentomino: (p: Pentomino) => void;
clickBoard: (x: number, y: number) => void;
hoverBoard: (x: number, y: number) => void;
@@ -54,12 +69,8 @@ const DEFAULT_GAME_STATE: GameState = {
paintedGrid: [],
currentPentomino: PENTOMINOES.None,
currentGridCoords: { x: 0, y: 0 },
- currentReflection: 0,
- currentRotation: 0,
- rotateLeft: () => {},
- rotateRight: () => {},
- reflectX: () => {},
- reflectY: () => {},
+ currentOrientation: { reflection: 0, rotation: 0 },
+ orientationDispatch: () => {},
updateCurrentPentomino: () => {},
clickBoard: () => {},
hoverBoard: () => {},
@@ -109,8 +120,10 @@ export default function GameStateProvider({ children }: { children: ReactNode })
const [currentPentomino, setCurrentPentomino] = useState(PENTOMINOES.None);
const [currentGridCoords, setCurrentGridCoords] = useState({ x: -1, y: -1 });
- const [currentReflection, setCurrentReflection] = useState(0); // 0, 1
- const [currentRotation, setCurrentRotation] = useState(0); // 0, 1, 2, 3
+ const [currentOrientation, orientationDispatch] = useReducer>(
+ orientationReducer,
+ { reflection: 0, rotation: 0 }
+ );
const [actionHistory, setActionHistory] = useState([]);
const navigate = useNavigate();
@@ -148,45 +161,16 @@ export default function GameStateProvider({ children }: { children: ReactNode })
surface,
{
pentomino: currentPentomino,
- x: currentGridCoords.x,
- y: currentGridCoords.y,
- rotation: currentRotation,
- reflection: currentReflection,
+ orientation: { ...currentOrientation },
+ coordinates: { ...currentGridCoords },
},
boardHovered
);
- }, [grid, surface, currentGridCoords, boardHovered, currentPentomino, currentRotation, currentReflection]);
-
- function rotateLeft() {
- setCurrentRotation((4 + currentRotation - 1) % 4);
- }
-
- function rotateRight() {
- setCurrentRotation((currentRotation + 1) % 4);
- }
-
- function reflectX() {
- if (currentRotation % 2 === 1) {
- setCurrentRotation((currentRotation + 2) % 4);
- }
- setCurrentReflection((currentReflection + 1) % 2);
- }
-
- function reflectY() {
- if (currentRotation % 2 === 0) {
- setCurrentRotation((currentRotation + 2) % 4);
- }
- setCurrentReflection((currentReflection + 1) % 2);
- }
-
- function resetOrientation() {
- setCurrentReflection(0);
- setCurrentRotation(0);
- }
+ }, [grid, surface, currentGridCoords, boardHovered, currentPentomino, currentOrientation]);
function updateCurrentPentomino(p: Pentomino) {
setCurrentPentomino(p);
- resetOrientation();
+ orientationDispatch({ type: OrientationActionType.replace });
}
function updateGridCoords(dim: keyof Coordinates, dir: number) {
@@ -208,10 +192,8 @@ export default function GameStateProvider({ children }: { children: ReactNode })
{
// grid not nextGrid
prevName: grid[x][y].pentomino.name,
- prevRotation: grid[x][y].rotation,
- prevReflection: grid[x][y].reflection,
- x: x,
- y: y,
+ prevOrientation: { ...grid[x][y].orientation },
+ prevCoordinates: { x, y },
},
],
},
@@ -225,10 +207,8 @@ export default function GameStateProvider({ children }: { children: ReactNode })
const newGrid = [...grid];
newGrid[newX][newY] = {
pentomino: currentPentomino,
- reflection: currentReflection,
- rotation: currentRotation,
- x: newX,
- y: newY,
+ orientation: { ...currentOrientation },
+ coordinates: { x: newX, y: newY },
};
setGrid(newGrid);
}
@@ -239,10 +219,11 @@ export default function GameStateProvider({ children }: { children: ReactNode })
if (x === givenX && y === givenY) {
return {
pentomino: PENTOMINOES.None,
- reflection: 0,
- rotation: 0,
- x: x,
- y: y,
+ orientation: {
+ reflection: 0,
+ rotation: 0,
+ },
+ coordinates: { x, y },
};
}
return c;
@@ -260,10 +241,8 @@ export default function GameStateProvider({ children }: { children: ReactNode })
) {
nextAction.pentominoes.push({
prevName: c.pentomino.name,
- prevReflection: c.reflection,
- prevRotation: c.rotation,
- x,
- y,
+ prevOrientation: { ...c.orientation },
+ prevCoordinates: { x, y },
});
}
return {
@@ -283,9 +262,11 @@ export default function GameStateProvider({ children }: { children: ReactNode })
drawPentomino(x, y);
} else {
setCurrentPentomino(cell.pentomino.pentomino);
- erasePentomino(cell.pentomino.x, cell.pentomino.y);
- setCurrentRotation(cell.pentomino.rotation);
- setCurrentReflection(cell.pentomino.reflection);
+ erasePentomino(cell.pentomino.coordinates.x, cell.pentomino.coordinates.y);
+ orientationDispatch({
+ type: OrientationActionType.replace,
+ newOrientation: cell.pentomino.orientation,
+ });
}
}
@@ -309,9 +290,8 @@ export default function GameStateProvider({ children }: { children: ReactNode })
if (lastAction === undefined) return;
const nextGrid = cloneDeep(grid);
lastAction.pentominoes.forEach((p) => {
- nextGrid[p.x][p.y].pentomino = PENTOMINOES[p.prevName];
- nextGrid[p.x][p.y].rotation = p.prevRotation;
- nextGrid[p.x][p.y].reflection = p.prevReflection;
+ nextGrid[p.prevCoordinates.x][p.prevCoordinates.y].pentomino = PENTOMINOES[p.prevName];
+ nextGrid[p.prevCoordinates.x][p.prevCoordinates.y].orientation = { ...p.prevOrientation };
});
setGrid(nextGrid);
setActionHistory(nextActionHistory);
@@ -337,10 +317,18 @@ export default function GameStateProvider({ children }: { children: ReactNode })
updateGridCoords("x", 1);
});
- useHotkey(undefined, "A", rotateLeft);
- useHotkey(undefined, "D", rotateRight);
- useHotkey(undefined, "S", reflectX);
- useHotkey(undefined, "W", reflectY);
+ useHotkey(undefined, "A", () => {
+ orientationDispatch({ type: OrientationActionType.rotate, direction: RotationDirection.Left });
+ });
+ useHotkey(undefined, "D", () => {
+ orientationDispatch({ type: OrientationActionType.rotate, direction: RotationDirection.Right });
+ });
+ useHotkey(undefined, "S", () => {
+ orientationDispatch({ type: OrientationActionType.reflect, direction: ReflectionDirection.X });
+ });
+ useHotkey(undefined, "W", () => {
+ orientationDispatch({ type: OrientationActionType.reflect, direction: ReflectionDirection.Y });
+ });
function updateToolbarPentomino(increment: number) {
const curIndex = ALL_PENTOMINO_NAMES.indexOf(currentPentomino.name);
@@ -351,7 +339,7 @@ export default function GameStateProvider({ children }: { children: ReactNode })
ALL_PENTOMINO_NAMES[(curIndex + increment + ALL_PENTOMINO_NAMES.length) % ALL_PENTOMINO_NAMES.length]
];
setCurrentPentomino(nextPentomino);
- resetOrientation();
+ orientationDispatch({ type: OrientationActionType.replace });
}
useHotkey(undefined, "E", () => {
@@ -376,12 +364,8 @@ export default function GameStateProvider({ children }: { children: ReactNode })
paintedGrid,
currentPentomino,
currentGridCoords,
- currentReflection,
- currentRotation,
- rotateLeft,
- rotateRight,
- reflectX,
- reflectY,
+ currentOrientation,
+ orientationDispatch,
updateCurrentPentomino,
clickBoard,
hoverBoard,
diff --git a/src/components/GameStateProvider/color.test.ts b/src/components/GameStateProvider/color.test.ts
index 9184c1e..3cf272d 100644
--- a/src/components/GameStateProvider/color.test.ts
+++ b/src/components/GameStateProvider/color.test.ts
@@ -14,7 +14,7 @@ test("we have the right half num colors", () => {
});
test("pentomino colors are encoded properly", () => {
- expect(encodeOrientation(0, 0, 6)).toBe("0");
+ expect(encodeOrientation({ rotation: 0, reflection: 0 }, 6)).toBe("0");
});
test("a color is encoded properly", () => {
diff --git a/src/components/GameStateProvider/currentPentominoReducer.ts b/src/components/GameStateProvider/currentPentominoReducer.ts
new file mode 100644
index 0000000..ba009f4
--- /dev/null
+++ b/src/components/GameStateProvider/currentPentominoReducer.ts
@@ -0,0 +1,45 @@
+import { produce } from "immer";
+import { Orientation } from "../../constants";
+import { Reducer } from "react";
+
+export enum RotationDirection {
+ Left,
+ Right,
+}
+
+export enum ReflectionDirection {
+ X,
+ Y,
+}
+
+export enum OrientationActionType {
+ rotate = "ROTATE",
+ reflect = "REFLECT",
+ replace = "REPLACE",
+}
+
+export interface OrientationAction {
+ type: OrientationActionType;
+ direction?: RotationDirection | ReflectionDirection;
+ newOrientation?: Orientation;
+}
+
+export const orientationReducer: Reducer = (
+ currentOrientation: Orientation,
+ action: OrientationAction
+) => {
+ switch (action.type) {
+ case OrientationActionType.rotate:
+ return produce(currentOrientation, (o: Orientation) => {
+ const direction = action.direction === RotationDirection.Left ? -1 : 1;
+ o.rotation = (4 + o.rotation + direction) % 4;
+ });
+ case OrientationActionType.reflect:
+ return produce(currentOrientation, (o: Orientation) => {
+ o.reflection = (o.reflection + 1) % 2;
+ if (o.rotation % 2 === 1) o.rotation = (o.rotation + 2) % 4;
+ });
+ case OrientationActionType.replace:
+ return action.newOrientation ? { ...action.newOrientation } : { rotation: 0, reflection: 0 };
+ }
+};
diff --git a/src/components/GameStateProvider/orientation.test.ts b/src/components/GameStateProvider/orientation.test.ts
index 98575c3..4002681 100644
--- a/src/components/GameStateProvider/orientation.test.ts
+++ b/src/components/GameStateProvider/orientation.test.ts
@@ -2,64 +2,57 @@ import { expect, test } from "vitest";
import { decodeOrientation, encodeOrientation } from "./urlConfig";
test("encode an orientation with color 0", () => {
- expect(encodeOrientation(0, 0, 0)).toBe("0");
- expect(encodeOrientation(1, 0, 0)).toBe("1");
- expect(encodeOrientation(1, 1, 0)).toBe("5");
+ expect(encodeOrientation({ rotation: 0, reflection: 0 }, 0)).toBe("0");
+ expect(encodeOrientation({ rotation: 1, reflection: 0 }, 0)).toBe("1");
+ expect(encodeOrientation({ rotation: 1, reflection: 1 }, 0)).toBe("5");
});
test("encode an orientation in uppercase range", () => {
- expect(encodeOrientation(0, 0, 1)).toBe("A");
- expect(encodeOrientation(1, 0, 1)).toBe("B");
- expect(encodeOrientation(0, 0, 2)).toBe("I");
+ expect(encodeOrientation({ rotation: 0, reflection: 0 }, 1)).toBe("A");
+ expect(encodeOrientation({ rotation: 1, reflection: 0 }, 1)).toBe("B");
+ expect(encodeOrientation({ rotation: 0, reflection: 0 }, 2)).toBe("I");
- expect(encodeOrientation(0, 0, 4)).toBe("Y");
- expect(encodeOrientation(1, 0, 4)).toBe("Z");
+ expect(encodeOrientation({ rotation: 0, reflection: 0 }, 4)).toBe("Y");
+ expect(encodeOrientation({ rotation: 1, reflection: 0 }, 4)).toBe("Z");
});
test("encode an orientation in lowercase range", () => {
- expect(encodeOrientation(2, 0, 4)).toBe("a");
+ expect(encodeOrientation({ rotation: 2, reflection: 0 }, 4)).toBe("a");
});
test("decode an orientation with numerical color", () => {
expect(decodeOrientation("0")).toStrictEqual({
- rotation: 0,
- reflection: 0,
+ orientation: { rotation: 0, reflection: 0 },
color: 0,
});
expect(decodeOrientation("5")).toStrictEqual({
- rotation: 1,
- reflection: 1,
+ orientation: { rotation: 1, reflection: 1 },
color: 0,
});
expect(decodeOrientation("1")).toStrictEqual({
- rotation: 1,
- reflection: 0,
+ orientation: { rotation: 1, reflection: 0 },
color: 0,
});
});
test("decode an orientation with uppercase color", () => {
expect(decodeOrientation("A")).toStrictEqual({
- rotation: 0,
- reflection: 0,
+ orientation: { rotation: 0, reflection: 0 },
color: 1,
});
expect(decodeOrientation("B")).toStrictEqual({
- rotation: 1,
- reflection: 0,
+ orientation: { rotation: 1, reflection: 0 },
color: 1,
});
expect(decodeOrientation("I")).toStrictEqual({
- rotation: 0,
- reflection: 0,
+ orientation: { rotation: 0, reflection: 0 },
color: 2,
});
});
test("decode an orientation with lowercase color", () => {
expect(decodeOrientation("a")).toStrictEqual({
- rotation: 2,
- reflection: 0,
+ orientation: { rotation: 2, reflection: 0 },
color: 4,
});
});
diff --git a/src/components/GameStateProvider/paintGrid.test.ts b/src/components/GameStateProvider/paintGrid.test.ts
index ed53dce..804bfcd 100644
--- a/src/components/GameStateProvider/paintGrid.test.ts
+++ b/src/components/GameStateProvider/paintGrid.test.ts
@@ -52,11 +52,12 @@ test("no incorrect conflicts where 2 pieces touch", () => {
paintCell(
paintedGrid,
{
- x: 0,
- y: 0,
+ coordinates: { x: 0, y: 0 },
pentomino: PENTOMINOES.F,
- rotation: 0,
- reflection: 0,
+ orientation: {
+ rotation: 0,
+ reflection: 0,
+ },
},
SURFACES.Rectangle,
grid,
@@ -65,11 +66,12 @@ test("no incorrect conflicts where 2 pieces touch", () => {
paintCell(
paintedGrid,
{
- x: 4,
- y: 1,
+ coordinates: { x: 4, y: 1 },
pentomino: PENTOMINOES.V,
- rotation: 0,
- reflection: 0,
+ orientation: {
+ rotation: 0,
+ reflection: 0,
+ },
},
SURFACES.Rectangle,
grid,
@@ -78,17 +80,21 @@ test("no incorrect conflicts where 2 pieces touch", () => {
paintCell(
paintedGrid,
{
- x: 2,
- y: 1,
+ coordinates: { x: 2, y: 1 },
pentomino: PENTOMINOES.I,
- rotation: 0,
- reflection: 0,
+ orientation: {
+ rotation: 0,
+ reflection: 0,
+ },
},
SURFACES.Rectangle,
grid,
false
);
- expect(paintedGrid[0][1].conflict).toBe(false);
+ expect(paintedGrid[0][1].conflict).toStrictEqual({
+ tileName: "F",
+ type: ConflictType.Overflow,
+ });
expect(paintedGrid[0][0].conflict).toStrictEqual({
tileName: "F",
type: ConflictType.Overflow,
diff --git a/src/components/GameStateProvider/paintGrid.ts b/src/components/GameStateProvider/paintGrid.ts
index 12c6ff9..2c64be3 100644
--- a/src/components/GameStateProvider/paintGrid.ts
+++ b/src/components/GameStateProvider/paintGrid.ts
@@ -2,7 +2,7 @@ import { ConflictType, PlacedPentomino } from "./../../constants";
import { Borders, PaintedCell } from "../../constants";
import { range } from "lodash";
import { PENTOMINOES } from "../../pentominoes";
-import { EMPTY_PENTOMINO, Orientation, SURFACES, Surface } from "../../constants";
+import { EMPTY_PENTOMINO, OrientabilityType, SURFACES, Surface } from "../../constants";
interface NewCoordinates {
newX: number;
@@ -26,7 +26,8 @@ export function getPaintedBoard(
if (currentPlacedPentomino === undefined) return paintedGrid;
if (
boardHovered &&
- paintedGrid[currentPlacedPentomino.x][currentPlacedPentomino.y].pentomino.pentomino.name === PENTOMINOES.None.name
+ paintedGrid[currentPlacedPentomino.coordinates.x][currentPlacedPentomino.coordinates.y].pentomino.pentomino.name ===
+ PENTOMINOES.None.name
) {
paintCell(paintedGrid, currentPlacedPentomino, surface, grid, true);
}
@@ -59,12 +60,12 @@ export const paintCell = (
grid: PlacedPentomino[][],
hovered: boolean
) => {
- const orientation = p.pentomino.orientations[p.reflection][p.rotation];
+ const orientation = p.pentomino.shapes[p.orientation.reflection][p.orientation.rotation];
orientation.shape.forEach((pr, px) =>
pr.forEach((val, py) => {
if (val === 0) return; // the pentomino isn't taking up this square of its grid, return
- const rawX = p.x + px - orientation.center.x;
- const rawY = p.y + py - orientation.center.y;
+ const rawX = p.coordinates.x + px - orientation.center.x;
+ const rawY = p.coordinates.y + py - orientation.center.y;
const height = grid.length;
const width = grid[0].length;
const { newX, newY } = getCoordinatesToPaint(surface, height, width, rawX, rawY);
@@ -80,8 +81,8 @@ export const paintCell = (
cellToPaint.conflict.type = ConflictType.Intersection;
}
cellToPaint.pentomino = p;
- const flipX = outOfBounds(rawY, width) && surface.orientation.h === Orientation.Nonorientable;
- const flipY = outOfBounds(rawX, height) && surface.orientation.w === Orientation.Nonorientable;
+ const flipX = outOfBounds(rawY, width) && surface.orientability.h === OrientabilityType.Nonorientable;
+ const flipY = outOfBounds(rawX, height) && surface.orientability.w === OrientabilityType.Nonorientable;
const transposeX = outOfBounds(rawX, width) && surface.consecutive;
const transposeY = outOfBounds(rawY, height) && surface.consecutive;
diff --git a/src/components/GameStateProvider/urlConfig.ts b/src/components/GameStateProvider/urlConfig.ts
index d776480..26d9ef0 100644
--- a/src/components/GameStateProvider/urlConfig.ts
+++ b/src/components/GameStateProvider/urlConfig.ts
@@ -3,6 +3,7 @@ import {
DEFAULT_COLORS,
letterToSurface,
MAX_NUM_COLORS,
+ Orientation,
PENTOMINO_NAMES,
PlacedPentomino,
randomPentominoColors,
@@ -39,9 +40,8 @@ export interface StringifiedUrlConfig {
surface: Surface;
}
-interface Orientation {
- rotation: number;
- reflection: number;
+interface OrientationCharacter {
+ orientation: Orientation;
color: number; // index of the color in an array
}
@@ -85,8 +85,8 @@ export function encodePentomino(p: string, color: number) {
return p.toLowerCase();
}
-export function encodeOrientation(rotation: number, reflection: number, color: number): string {
- const r = reflection === 0 ? rotation : NUM_ROTATIONS + rotation;
+export function encodeOrientation(orientation: Orientation, color: number): string {
+ const r = orientation.reflection === 0 ? orientation.rotation : NUM_ROTATIONS + orientation.rotation;
if (color >= HALF_NUM_COLORS) color = color - HALF_NUM_COLORS;
if (color === 0 || color === undefined) return r.toString();
@@ -196,7 +196,7 @@ export function serializeUrl({ grid, colors, surface }: UrlConfig): string {
} else if (p.pentomino.name !== PENTOMINOES.None.name)
placedPentominoes.push({
p: encodePentomino(p.pentomino.name, colors[p.pentomino.name]),
- r: encodeOrientation(p.rotation, p.reflection, colors[p.pentomino.name]),
+ r: encodeOrientation(p.orientation, colors[p.pentomino.name]),
c: `${encodeNumber(x)}${encodeNumber(y)}`,
});
})
@@ -383,12 +383,14 @@ export function decodeUrl(s: string): StringifiedUrlConfig {
return config;
}
-export function decodeOrientation(r: string): Orientation {
+export function decodeOrientation(r: string): OrientationCharacter {
const asNumber = toNumber(r);
if (!isNaN(asNumber)) {
return {
- rotation: asNumber % NUM_ROTATIONS,
- reflection: asNumber >= NUM_ROTATIONS ? 1 : 0,
+ orientation: {
+ rotation: asNumber % NUM_ROTATIONS,
+ reflection: asNumber >= NUM_ROTATIONS ? 1 : 0,
+ },
color: 0,
};
} else {
@@ -399,8 +401,10 @@ export function decodeOrientation(r: string): Orientation {
: charCode - UPPERCASE_START_INDEX;
const o = charValue % NUM_SPATIAL_ORIENTATIONS;
return {
- rotation: o % 4,
- reflection: o >= 4 ? 1 : 0,
+ orientation: {
+ rotation: o % 4,
+ reflection: o >= 4 ? 1 : 0,
+ },
// color 0 is a digit not a letter so add 1
color: Math.floor(charValue / NUM_SPATIAL_ORIENTATIONS) + 1,
};
@@ -465,10 +469,8 @@ export function deserializeUrl(s: string, defaultRandomColors: boolean): UrlConf
const { x, y } = decodeCoordinates(p.c, p.p === p.p.toUpperCase());
ret.grid[x][y] = {
pentomino: PENTOMINOES[p.p.toUpperCase()],
- rotation: r.rotation,
- reflection: r.reflection,
- x: x,
- y: y,
+ orientation: { ...r.orientation },
+ coordinates: { x, y },
};
if (p.p.toUpperCase() !== PENTOMINOES.R.name) ret.colors[p.p.toUpperCase()] = decodeColor(r.color, p.p, legacy);
});
diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx
index 6bc23be..d9e6dd0 100644
--- a/src/components/Header/Header.tsx
+++ b/src/components/Header/Header.tsx
@@ -7,14 +7,8 @@ import { ALL_PENTOMINO_NAMES } from "../../constants";
import { AppStateContext } from "../AppStateProvider/AppStateProvider";
export const Header = ({ ...rest }) => {
- const {
- currentPentomino,
- updateCurrentPentomino,
- currentRotation,
- currentReflection,
- pentominoColors,
- showKeyboardIndicators,
- } = useContext(GameStateContext);
+ const { currentPentomino, updateCurrentPentomino, currentOrientation, pentominoColors, showKeyboardIndicators } =
+ useContext(GameStateContext);
const { appPreferences } = useContext(AppStateContext);
@@ -62,8 +56,7 @@ export const Header = ({ ...rest }) => {
diff --git a/src/components/PentominoDisplay/PentominoDisplay.tsx b/src/components/PentominoDisplay/PentominoDisplay.tsx
index 3bc0eb1..acdbe0b 100644
--- a/src/components/PentominoDisplay/PentominoDisplay.tsx
+++ b/src/components/PentominoDisplay/PentominoDisplay.tsx
@@ -1,5 +1,5 @@
import clsx from "clsx";
-import { PENTOMINO_DIMENSIONS, PENTOMINO_SIZES } from "../../constants";
+import { Orientation, PENTOMINO_DIMENSIONS, PENTOMINO_SIZES } from "../../constants";
import { Coordinates, Pentomino, PENTOMINOES } from "../../pentominoes";
import { useContext } from "react";
import { GameStateContext } from "../GameStateProvider/GameStateProvider";
@@ -9,8 +9,7 @@ import { AppStateContext } from "../AppStateProvider/AppStateProvider";
export const PentominoDisplay = ({
pentomino,
color,
- rotation = 0,
- reflection = 0,
+ orientation = { rotation: 0, reflection: 0 },
checkGrid = true,
size = 5,
showCenter = true,
@@ -19,8 +18,7 @@ export const PentominoDisplay = ({
}: {
pentomino: Pentomino;
color?: string;
- rotation?: number;
- reflection?: number;
+ orientation?: Orientation;
checkGrid?: boolean;
size?: number;
showCenter?: boolean;
@@ -29,9 +27,9 @@ export const PentominoDisplay = ({
const { grid } = useContext(GameStateContext);
const { appPreferences } = useContext(AppStateContext);
if (color === undefined) color = appPreferences.displayColors[0];
- const p = pentomino.orientations[reflection][rotation];
+ const p = pentomino.shapes[orientation.reflection][orientation.rotation];
function bgColor(cell: number, coordinates: Coordinates) {
- if (pentomino === PENTOMINOES.R) return { class: "bg-gray-600", style: "" };
+ if (pentomino.name === PENTOMINOES.R.name) return { class: "bg-gray-600", style: "" };
let found = false;
if (checkGrid) {
grid.forEach((row) =>
diff --git a/src/components/TopToolbar/TopToolbar.tsx b/src/components/TopToolbar/TopToolbar.tsx
index a449486..9430ce0 100644
--- a/src/components/TopToolbar/TopToolbar.tsx
+++ b/src/components/TopToolbar/TopToolbar.tsx
@@ -4,28 +4,57 @@ import { GameStateContext } from "../GameStateProvider/GameStateProvider";
import { ToolbarButton } from "../Button/Button";
import { DoubleEndedArrow } from "./DoubleEndedArrow";
import { ReloadIcon } from "@radix-ui/react-icons";
+import {
+ OrientationActionType,
+ ReflectionDirection,
+ RotationDirection,
+} from "../GameStateProvider/currentPentominoReducer";
const TopToolbar = ({ ...rest }) => {
- const { rotateLeft, rotateRight, reflectX, reflectY } = useContext(GameStateContext);
+ const { orientationDispatch } = useContext(GameStateContext);
return (
-
+ {
+ orientationDispatch({ type: OrientationActionType.rotate, direction: RotationDirection.Left });
+ }}
+ aria-label="Rotate Left"
+ className="flex flex-row items-center gap-1"
+ >
Rotate Left
-
+ {
+ orientationDispatch({ type: OrientationActionType.rotate, direction: RotationDirection.Right });
+ }}
+ aria-label="Rotate Right"
+ className="flex flex-row items-center gap-1"
+ >
Rotate Right
-
+ {
+ orientationDispatch({ type: OrientationActionType.reflect, direction: ReflectionDirection.X });
+ }}
+ aria-label="Reflect X"
+ className="flex flex-row items-center"
+ >
Reflect X
-
+ {
+ orientationDispatch({ type: OrientationActionType.reflect, direction: ReflectionDirection.Y });
+ }}
+ aria-label="Reflect Y"
+ className="flex flex-row items-center gap-1"
+ >
Reflect Y
diff --git a/src/components/Wordmark/Wordmark.tsx b/src/components/Wordmark/Wordmark.tsx
index a9c2b34..a332279 100644
--- a/src/components/Wordmark/Wordmark.tsx
+++ b/src/components/Wordmark/Wordmark.tsx
@@ -75,7 +75,7 @@ export const Wordmark = ({ gridArea }: { gridArea: string }) => {
(DEFAULT_COLORS[p] = 0));
-export enum Orientation {
+export enum OrientabilityType {
Orientable,
Nonorientable,
ConsecutiveOrientable,
@@ -95,16 +99,16 @@ export enum Orientation {
None,
}
-interface SurfaceOrientation {
- h: Orientation;
- w: Orientation;
+interface Orientability {
+ h: OrientabilityType;
+ w: OrientabilityType;
}
export interface Surface {
name: string;
consecutive: boolean;
key: string;
- orientation: SurfaceOrientation;
+ orientability: Orientability;
}
interface Surfaces {
@@ -116,43 +120,43 @@ export const SURFACES: Surfaces = {
name: "Rectangle",
consecutive: false,
key: "R",
- orientation: { w: Orientation.None, h: Orientation.None },
+ orientability: { w: OrientabilityType.None, h: OrientabilityType.None },
},
Cylinder: {
name: "Cylinder",
consecutive: false,
key: "C",
- orientation: { w: Orientation.None, h: Orientation.Orientable },
+ orientability: { w: OrientabilityType.None, h: OrientabilityType.Orientable },
},
Sphere: {
name: "Sphere",
consecutive: true,
key: "S",
- orientation: { w: Orientation.ConsecutiveOrientable, h: Orientation.ConsecutiveOrientable },
+ orientability: { w: OrientabilityType.ConsecutiveOrientable, h: OrientabilityType.ConsecutiveOrientable },
},
Torus: {
name: "Torus",
consecutive: false,
key: "T",
- orientation: { w: Orientation.Orientable, h: Orientation.Orientable },
+ orientability: { w: OrientabilityType.Orientable, h: OrientabilityType.Orientable },
},
Mobius: {
name: "Mobius",
consecutive: false,
key: "M",
- orientation: { w: Orientation.None, h: Orientation.Nonorientable },
+ orientability: { w: OrientabilityType.None, h: OrientabilityType.Nonorientable },
},
ProjectivePlane: {
name: "ProjectivePlane",
consecutive: false,
key: "P",
- orientation: { w: Orientation.Nonorientable, h: Orientation.Nonorientable },
+ orientability: { w: OrientabilityType.Nonorientable, h: OrientabilityType.Nonorientable },
},
KleinBottle: {
name: "KleinBottle",
consecutive: false,
key: "K",
- orientation: { w: Orientation.Orientable, h: Orientation.Nonorientable },
+ orientability: { w: OrientabilityType.Orientable, h: OrientabilityType.Nonorientable },
},
};
@@ -219,10 +223,8 @@ export const randomPentominoColors = (numVisibleColors: number): Colors => {
interface ActionPentomino {
prevName: string;
- prevRotation: number;
- prevReflection: number;
- x: number;
- y: number;
+ prevOrientation: Orientation;
+ prevCoordinates: Coordinates;
}
export interface Action {
diff --git a/src/pentominoes.ts b/src/pentominoes.ts
index e22fc5a..50d9d0b 100644
--- a/src/pentominoes.ts
+++ b/src/pentominoes.ts
@@ -6,18 +6,18 @@ export interface Coordinates {
y: number;
}
-interface Orientations {
- [key: number]: Orientation[];
-}
-
-export interface Orientation {
+export interface Shape {
center: Coordinates;
shape: number[][];
}
+interface Shapes {
+ [key: number]: Shape[];
+}
+
export interface Pentomino {
name: string;
- orientations: Orientations;
+ shapes: Shapes;
}
interface Pentominoes {
@@ -140,12 +140,12 @@ pentominoPrimitives.map((p) => {
const reflectedShape = reflectX(p.shape);
const expandedPentomino: Pentomino = {
name: p.name,
- orientations: {
+ shapes: {
0: [{ center: center(p.shape), shape: p.shape }],
1: [{ center: center(reflectedShape), shape: reflectedShape }],
},
};
- const orientations = expandedPentomino.orientations;
+ const orientations = expandedPentomino.shapes;
range(0, 3).forEach(() => {
// rotate 3 times
range(0, 2).forEach((i) => {