Skip to content

Commit

Permalink
Direction enum
Browse files Browse the repository at this point in the history
  • Loading branch information
sspenst committed Jul 25, 2023
1 parent 906d31d commit 64408af
Show file tree
Hide file tree
Showing 16 changed files with 238 additions and 174 deletions.
36 changes: 18 additions & 18 deletions components/level/game.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Direction, { directionToPosition, getDirectionFromCode } from '@root/constants/direction';
import { GameContext } from '@root/contexts/gameContext';
import { CheckpointState, convertFromCheckpointState, convertToCheckpointState, isValidCheckpointState } from '@root/helpers/checkpointHelpers';
import isPro from '@root/helpers/isPro';
Expand All @@ -18,7 +19,7 @@ import BlockState from '../../models/blockState';
import Control from '../../models/control';
import Level, { EnrichedLevel } from '../../models/db/level';
import Move from '../../models/move';
import Position, { getDirectionFromCode } from '../../models/position';
import Position from '../../models/position';
import SquareState from '../../models/squareState';
import GameLayout from './gameLayout';

Expand Down Expand Up @@ -128,7 +129,7 @@ export default function Game({
onStatsSuccess,
}: GameProps) {
const [gameState, setGameState] = useState<GameState>(initGameState(level.data));
const [lastCodes, setLastCodes] = useState<string[]>([]);
const lastDirections = useRef<Direction[]>([]);
const levelContext = useContext(LevelContext);
const [localSessionRestored, setLocalSessionRestored] = useState(false);
const [madeMove, setMadeMove] = useState(false);
Expand Down Expand Up @@ -224,13 +225,13 @@ export default function Game({
fetchPlayAttempt();
}, [disablePlayAttempts, fetchPlayAttempt, gameState.actionCount, madeMove]);

const trackStats = useCallback((codes: string[], levelId: string, maxRetries: number) => {
const trackStats = useCallback((directions: Direction[], levelId: string, maxRetries: number) => {
if (disableStats) {
return;
}

// if codes array is identical to lastCodes array, don't PUT stats
if (codes.length === lastCodes.length && codes.every((code, index) => code === lastCodes[index])) {
// if directions array is identical to lastDirections array, don't PUT stats
if (directions.length === lastDirections.current.length && directions.every((direction, index) => direction === lastDirections.current[index])) {
return;
}

Expand All @@ -239,7 +240,7 @@ export default function Game({
fetch('/api/stats', {
method: 'PUT',
body: JSON.stringify({
codes: codes,
directions: directions,
levelId: levelId,
matchId: matchId,
}),
Expand All @@ -263,7 +264,7 @@ export default function Game({
onStatsSuccess();
}

setLastCodes(codes);
lastDirections.current = directions;
} else if (res.status === 500) {
throw res.text();
} else {
Expand All @@ -276,17 +277,17 @@ export default function Game({
}).catch(async err => {
const error = JSON.parse(await err)?.error;

console.error(`Error updating stats: { codes: ${codes}, levelId: ${levelId} }`, error);
console.error(`Error updating stats: { directions: ${directions}, levelId: ${levelId} }`, error);
toast.dismiss();
toast.error(`Error updating stats: ${error}`, { duration: 3000 });

if (maxRetries > 0) {
trackStats(codes, levelId, maxRetries - 1);
trackStats(directions, levelId, maxRetries - 1);
}
}).finally(() => {
NProgress.done();
});
}, [disableStats, lastCodes, matchId, mutateLevel, mutateProStatsLevel, mutateUser, onStatsSuccess]);
}, [disableStats, matchId, mutateLevel, mutateProStatsLevel, mutateUser, onStatsSuccess]);

const saveCheckpoint = useCallback((slot: number) => {
if (slot !== BEST_CHECKPOINT_INDEX) {
Expand Down Expand Up @@ -583,9 +584,8 @@ export default function Game({
// undo the block push
if (prevMove.blockId !== undefined) {
const block = getBlockById(blocks, prevMove.blockId);
const direction = getDirectionFromCode(prevMove.code);

if (!block || !direction) {
if (!block) {
return prevGameState;
}

Expand All @@ -596,7 +596,7 @@ export default function Game({
}

// move the block back to its original position
block.pos = block.pos.sub(direction);
block.pos = block.pos.sub(directionToPosition(prevMove.direction));
}

const newGameState: GameState = {
Expand All @@ -613,19 +613,19 @@ export default function Game({
return newGameState;
}

function makeMove(direction: Position) {
function makeMove(direction: Direction) {
// if the position didn't change or the new position is invalid
if (!isPlayerPositionValid(board, prevGameState.height, pos, prevGameState.width)) {
return prevGameState;
}

const blockIndex = getBlockIndexAtPosition(blocks, pos);
const move = new Move(code, prevGameState.pos);
const move = new Move(direction, prevGameState.pos);

// if there is a block at the new position
if (blockIndex !== -1) {
const block = blocks[blockIndex];
const blockPos = block.pos.add(direction);
const blockPos = block.pos.add(directionToPosition(direction));

// if the block is not allowed to move this direction or the new position is invalid
if (!block.canMoveTo(blockPos) ||
Expand Down Expand Up @@ -656,7 +656,7 @@ export default function Game({
const moveCount = prevGameState.moveCount + 1;

if (board[pos.y][pos.x].tileType === TileType.End) {
trackStats(moves.map(move => move.code), level._id.toString(), 3);
trackStats(moves.map(move => move.direction), level._id.toString(), 3);
}

const newGameState: GameState = {
Expand Down Expand Up @@ -705,7 +705,7 @@ export default function Game({
}

// calculate the target tile to move to
const pos = prevGameState.pos.add(direction);
const pos = prevGameState.pos.add(directionToPosition(direction));

// before making a move, check if undo is a better choice
function checkForFreeUndo() {
Expand Down
7 changes: 4 additions & 3 deletions components/tutorial.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* istanbul ignore file */

import { createPopper, Instance, Placement } from '@popperjs/core';
import { directionToPosition } from '@root/constants/direction';
import TileType from '@root/constants/tileType';
import classNames from 'classnames';
import { Types } from 'mongoose';
Expand All @@ -10,7 +11,7 @@ import { AppContext } from '../contexts/appContext';
import { TimerUtil } from '../helpers/getTs';
import Control from '../models/control';
import Level from '../models/db/level';
import Position, { getDirectionFromCode } from '../models/position';
import Position from '../models/position';
import DismissToast from './dismissToast';
import BasicLayout from './level/basicLayout';
import Controls from './level/controls';
Expand Down Expand Up @@ -421,9 +422,9 @@ export default function Tutorial() {
// notify the user to undo if they have gone past the exit (too far right or down)
// or if they have gone left or up at any point
if (gameState.pos.x > 4 || gameState.pos.y > 3 || gameState.moves.some(move => {
const direction = getDirectionFromCode(move.code);
const pos = directionToPosition(move.direction);

return direction?.equals(new Position(-1, 0)) || direction?.equals(new Position(0, -1));
return pos.equals(new Position(-1, 0)) || pos.equals(new Position(0, -1));
})) {
undoButton?.classList.add(styles['highlight-red']);
} else {
Expand Down
37 changes: 37 additions & 0 deletions constants/direction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Position from '@root/models/position';

enum Direction {
LEFT = 1,
UP,
RIGHT,
DOWN,
}

export function getDirectionFromCode(code: string) {
if (code === 'ArrowLeft' || code === 'KeyA') {
return Direction.LEFT;
} else if (code === 'ArrowUp' || code === 'KeyW') {
return Direction.UP;
} else if (code === 'ArrowRight' || code === 'KeyD') {
return Direction.RIGHT;
} else if (code === 'ArrowDown' || code === 'KeyS') {
return Direction.DOWN;
} else {
return undefined;
}
}

export function directionToPosition(direction: Direction) {
switch (direction) {
case Direction.LEFT:
return new Position(-1, 0);
case Direction.UP:
return new Position(0, -1);
case Direction.RIGHT:
return new Position(1, 0);
case Direction.DOWN:
return new Position(0, 1);
}
}

export default Direction;
31 changes: 25 additions & 6 deletions helpers/checkpointHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { GameState } from '@root/components/level/game';
import Direction, { directionToPosition, getDirectionFromCode } from '@root/constants/direction';
import TileType from '@root/constants/tileType';
import BlockState from '@root/models/blockState';
import Move from '@root/models/move';
import Position, { getDirectionFromCode } from '@root/models/position';
import Position from '@root/models/position';
import SquareState from '@root/models/squareState';

interface CheckpointSquareState {
Expand Down Expand Up @@ -53,6 +54,19 @@ export function isValidCheckpointState(value: unknown) {
return true;
}

function directionToCode(direction: Direction) {
switch (direction) {
case Direction.LEFT:
return 'ArrowLeft';
case Direction.UP:
return 'ArrowUp';
case Direction.RIGHT:
return 'ArrowRight';
case Direction.DOWN:
return 'ArrowDown';
}
}

export function convertToCheckpointState(gameState: GameState) {
const blocks = gameState.blocks.map(block => BlockState.clone(block));
const checkpointState: CheckpointState = {
Expand All @@ -72,17 +86,16 @@ export function convertToCheckpointState(gameState: GameState) {
moveCount: gameState.moveCount,
moves: gameState.moves.map(move => {
const checkpointMove: CheckpointMove = {
code: move.code,
code: directionToCode(move.direction),
pos: move.pos.clone(),
};

if (move.blockId !== undefined) {
const block = blocks.find(b => b.id === move.blockId);
const direction = getDirectionFromCode(move.code);

if (block && direction) {
if (block) {
checkpointMove.block = block.clone();
checkpointMove.block.pos = checkpointMove.block.pos.sub(direction);
checkpointMove.block.pos = checkpointMove.block.pos.sub(directionToPosition(move.direction));

if (block.inHole) {
checkpointMove.block.inHole = false;
Expand Down Expand Up @@ -110,8 +123,14 @@ export function convertFromCheckpointState(checkpointState: CheckpointState) {
height: checkpointState.height,
moveCount: checkpointState.moveCount,
moves: checkpointState.moves.map(checkpointMove => {
const direction = getDirectionFromCode(checkpointMove.code);

if (!direction) {
throw new Error(`Invalid checkpoint code ${checkpointMove.code}`);
}

return new Move(
checkpointMove.code,
direction,
new Position(checkpointMove.pos.x, checkpointMove.pos.y),
checkpointMove.block?.id,
);
Expand Down
24 changes: 13 additions & 11 deletions helpers/validateSolution.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import Direction, { directionToPosition } from '@root/constants/direction';
import TileType from '@root/constants/tileType';
import TileTypeHelper from '@root/helpers/tileTypeHelper';
import Level from '../models/db/level';
import Position, { getDirectionFromCode } from '../models/position';
import Position from '../models/position';

export default function validateSolution(codes: string[], level: Level) {
export default function validateSolution(directions: Direction[], level: Level) {
const data = level.data.replace(/\n/g, '').split('') as TileType[];
const endIndices = [];
const posIndex = data.indexOf(TileType.Start);
Expand All @@ -14,20 +15,21 @@ export default function validateSolution(codes: string[], level: Level) {
endIndices.push(endIndex);
}

for (let i = 0; i < codes.length; i++) {
for (let i = 0; i < directions.length; i++) {
// cannot continue moving if already on an exit
if (data[pos.y * level.width + pos.x] === TileType.End) {
return false;
}

const direction = getDirectionFromCode(codes[i]);
const direction = directions[i];

if (!direction) {
// account for bad user input from the API
if (!(direction in Direction)) {
return false;
}

// validate and update position with direction
pos = pos.add(direction);
pos = pos.add(directionToPosition(direction));

if (pos.x < 0 || pos.x >= level.width || pos.y < 0 || pos.y >= level.height) {
return false;
Expand All @@ -45,15 +47,15 @@ export default function validateSolution(codes: string[], level: Level) {
// if a block is being moved
if (TileTypeHelper.canMove(tileTypeAtPos)) {
// validate block is allowed to move in this direction
if ((direction.equals(new Position(-1, 0)) && !TileTypeHelper.canMoveLeft(tileTypeAtPos)) ||
(direction.equals(new Position(0, -1)) && !TileTypeHelper.canMoveUp(tileTypeAtPos)) ||
(direction.equals(new Position(1, 0)) && !TileTypeHelper.canMoveRight(tileTypeAtPos)) ||
(direction.equals(new Position(0, 1)) && !TileTypeHelper.canMoveDown(tileTypeAtPos))) {
if ((direction === Direction.LEFT && !TileTypeHelper.canMoveLeft(tileTypeAtPos)) ||
(direction === Direction.UP && !TileTypeHelper.canMoveUp(tileTypeAtPos)) ||
(direction === Direction.RIGHT && !TileTypeHelper.canMoveRight(tileTypeAtPos)) ||
(direction === Direction.DOWN && !TileTypeHelper.canMoveDown(tileTypeAtPos))) {
return false;
}

// validate and update block position with direction
const blockPos = pos.add(direction);
const blockPos = pos.add(directionToPosition(direction));

if (blockPos.x < 0 || blockPos.x >= level.width || blockPos.y < 0 || blockPos.y >= level.height) {
return false;
Expand Down
12 changes: 6 additions & 6 deletions models/move.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import Direction from '@root/constants/direction';
import Position from './position';

export default class Move {
// keycode of the move direction
code: string;
direction: Direction;
// position before the move
pos: Position;
// the id of the block pushed during this move
blockId?: number;

constructor(code: string, pos: Position, blockId?: number) {
this.code = code;
constructor(direction: Direction, pos: Position, blockId?: number) {
this.direction = direction;
this.pos = pos.clone();
this.blockId = blockId;
}

static clone(move: Move) {
return new Move(
move.code,
move.direction,
new Position(move.pos.x, move.pos.y),
move.blockId,
);
}

clone() {
return new Move(
this.code,
this.direction,
this.pos,
this.blockId,
);
Expand Down
14 changes: 0 additions & 14 deletions models/position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,3 @@ export default class Position {
);
}
}

export function getDirectionFromCode(code: string) {
if (code === 'ArrowLeft' || code === 'KeyA') {
return new Position(-1, 0);
} else if (code === 'ArrowUp' || code === 'KeyW') {
return new Position(0, -1);
} else if (code === 'ArrowRight' || code === 'KeyD') {
return new Position(1, 0);
} else if (code === 'ArrowDown' || code === 'KeyS') {
return new Position(0, 1);
} else {
return undefined;
}
}
Loading

0 comments on commit 64408af

Please sign in to comment.