Path will now be shown on hover, moved some components
This commit is contained in:
parent
1a5505fe3f
commit
ee00611c33
@ -1,9 +1,9 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {Character, Dummy, PacMan} from "../game/character";
|
||||
import {Character, PacMan} from "../game/character";
|
||||
import findPossiblePositions from "../game/possibleMovesAlgorithm";
|
||||
import {TileType} from "../game/tileType";
|
||||
import {testMap} from "../game/map";
|
||||
import {Direction} from "../game/direction";
|
||||
import {GameTile} from "./gameTile";
|
||||
|
||||
interface BoardProps extends ComponentProps {
|
||||
characters: Character[],
|
||||
@ -21,15 +21,20 @@ const Board: Component<BoardProps> = (
|
||||
|
||||
const [tileSize, setTileSize] = useState(2);
|
||||
const [selectedCharacter, setSelectedCharacter] = useState<Character>();
|
||||
// TODO show the paths to the positions when hovering over a possible position (type Path = CharacterPosition[])
|
||||
const [possiblePositions, setPossiblePositions] = useState<Path[]>([]); // TODO reset when other client moves a character
|
||||
const [hoveredPosition, setHoveredPosition] = useState<Path>();
|
||||
|
||||
function handleSelectCharacter(character: Character): void {
|
||||
setSelectedCharacter(character);
|
||||
}
|
||||
|
||||
function handleShowPath(path: Path): void {
|
||||
setHoveredPosition(path);
|
||||
}
|
||||
|
||||
function handleMoveCharacter(path: Path): void {
|
||||
if (selectedCharacter) {
|
||||
setHoveredPosition(undefined);
|
||||
selectedCharacter.follow(path);
|
||||
onMove?.(selectedCharacter);
|
||||
setSelectedCharacter(undefined);
|
||||
@ -73,28 +78,18 @@ const Board: Component<BoardProps> = (
|
||||
<div key={rowIndex} className={"flex"}>
|
||||
{
|
||||
row.map((tile, colIndex) =>
|
||||
<Tile className={`${possiblePositions.find(p => p.end.x === colIndex && p.end.y === rowIndex) ?
|
||||
"border-4 border-white" : ""}`}
|
||||
characterClass={`${selectedCharacter?.isAt({x: colIndex, y: rowIndex}) ? "animate-bounce" : ""}`}
|
||||
key={colIndex + rowIndex * colIndex}
|
||||
type={tile}
|
||||
size={tileSize}
|
||||
character={characters.find(c => c.isAt({x: colIndex, y: rowIndex}))}
|
||||
onClick={possiblePositions
|
||||
.filter(p => p.end.x === colIndex && p.end.y === rowIndex)
|
||||
.map(p => () => handleMoveCharacter(p))[0]}>
|
||||
<>
|
||||
{characters.find(c => c.isAt({x: colIndex, y: rowIndex})) &&
|
||||
<div className={"flex-center w-full h-full"}>
|
||||
<CharacterComponent
|
||||
character={characters.find(c => c.isAt({x: colIndex, y: rowIndex}))!}
|
||||
onClick={handleSelectCharacter}
|
||||
className={`${selectedCharacter?.isAt({x: colIndex, y: rowIndex}) ? "animate-bounce" : ""}`}/>
|
||||
</div>
|
||||
}
|
||||
<AddDummy path={possiblePositions.find(p => p.end.x === colIndex && p.end.y === rowIndex)}/>
|
||||
</>
|
||||
</Tile>
|
||||
<GameTile
|
||||
key={colIndex + rowIndex * colIndex}
|
||||
type={tile}
|
||||
size={tileSize}
|
||||
possiblePath={possiblePositions.find(p => p.end.x === colIndex && p.end.y === rowIndex)}
|
||||
character={characters.find(c => c.isAt({x: colIndex, y: rowIndex}))}
|
||||
isSelected={selectedCharacter?.isAt({x: colIndex, y: rowIndex})}
|
||||
showPath={hoveredPosition?.path?.find(pos => pos.x === colIndex && pos.y === rowIndex) !== undefined}
|
||||
handleMoveCharacter={handleMoveCharacter}
|
||||
handleSelectCharacter={handleSelectCharacter}
|
||||
handleStartShowPath={handleShowPath}
|
||||
handleStopShowPath={() => setHoveredPosition(undefined)}/>
|
||||
)
|
||||
}
|
||||
</div>)
|
||||
@ -104,99 +99,3 @@ const Board: Component<BoardProps> = (
|
||||
};
|
||||
|
||||
export default Board;
|
||||
|
||||
interface AddDummyProps extends ComponentProps {
|
||||
path?: Path;
|
||||
}
|
||||
|
||||
const AddDummy: Component<AddDummyProps> = ({path}) => (
|
||||
<>
|
||||
{path &&
|
||||
<div className={"flex-center w-full h-full"}>
|
||||
<CharacterComponent character={new Dummy(path)}/>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
|
||||
interface TileProps extends ChildProps {
|
||||
size: number,
|
||||
type?: TileType,
|
||||
onClick?: () => void,
|
||||
character?: Character,
|
||||
onCharacterClick?: (character: Character) => void,
|
||||
characterClass?: string,
|
||||
}
|
||||
|
||||
const Tile: Component<TileProps> = (
|
||||
{
|
||||
size,
|
||||
type = TileType.empty,
|
||||
onClick,
|
||||
className,
|
||||
children
|
||||
}) => {
|
||||
|
||||
function setColor(): string {
|
||||
switch (type) {
|
||||
case TileType.empty:
|
||||
return "bg-black";
|
||||
case TileType.wall:
|
||||
return "bg-blue-500";
|
||||
case TileType.pellet:
|
||||
return "bg-yellow-500";
|
||||
case TileType.powerPellet:
|
||||
return "bg-orange-500";
|
||||
case TileType.ghostSpawn:
|
||||
return "bg-red-500";
|
||||
case TileType.pacmanSpawn:
|
||||
return "bg-green-500";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${setColor()} hover:border relative max-w-[75px] max-h-[75px] ${className}`}
|
||||
style={{width: `${size}px`, height: `${size}px`}}
|
||||
onClick={onClick}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface CharacterComponentProps extends ComponentProps {
|
||||
character?: Character,
|
||||
onClick?: (character: Character) => void,
|
||||
}
|
||||
|
||||
const CharacterComponent: Component<CharacterComponentProps> = (
|
||||
{
|
||||
character,
|
||||
onClick,
|
||||
className
|
||||
}) => {
|
||||
|
||||
function getSide() {
|
||||
switch (character?.position.direction) {
|
||||
case Direction.up:
|
||||
return "right-1/4 top-0";
|
||||
case Direction.down:
|
||||
return "right-1/4 bottom-0";
|
||||
case Direction.left:
|
||||
return "left-0 top-1/4";
|
||||
case Direction.right:
|
||||
return "right-0 top-1/4";
|
||||
}
|
||||
}
|
||||
|
||||
if (character === undefined) return null;
|
||||
|
||||
return (
|
||||
<div className={`rounded-full w-4/5 h-4/5 cursor-pointer hover:border border-black relative ${className}`}
|
||||
style={{backgroundColor: `${character.color}`}}
|
||||
onClick={() => onClick?.(character)}>
|
||||
<div>
|
||||
<div className={`absolute ${getSide()} w-1/2 h-1/2 rounded-full bg-black`}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
163
pac-man-board-game/ClientApp/src/components/gameTile.tsx
Normal file
163
pac-man-board-game/ClientApp/src/components/gameTile.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
import React from "react";
|
||||
import {TileType} from "../game/tileType";
|
||||
import {Character, Dummy} from "../game/character";
|
||||
import {Direction} from "../game/direction";
|
||||
|
||||
interface TileWithCharacterProps extends ComponentProps {
|
||||
possiblePath?: Path,
|
||||
character?: Character,
|
||||
size: number,
|
||||
type?: TileType,
|
||||
handleMoveCharacter?: (path: Path) => void,
|
||||
handleSelectCharacter?: (character: Character) => void,
|
||||
handleStartShowPath?: (path: Path) => void,
|
||||
handleStopShowPath?: () => void,
|
||||
isSelected?: boolean,
|
||||
showPath?: boolean
|
||||
}
|
||||
|
||||
export const GameTile: Component<TileWithCharacterProps> = (
|
||||
{
|
||||
possiblePath,
|
||||
character,
|
||||
size,
|
||||
type,
|
||||
handleMoveCharacter,
|
||||
handleSelectCharacter,
|
||||
handleStartShowPath,
|
||||
handleStopShowPath,
|
||||
isSelected = false,
|
||||
showPath = false
|
||||
}) => {
|
||||
return (
|
||||
<Tile className={`${possiblePath?.end ? "border-4 border-white" : ""}`}
|
||||
type={type}
|
||||
size={size}
|
||||
onClick={possiblePath ? () => handleMoveCharacter?.(possiblePath) : undefined}
|
||||
onMouseEnter={possiblePath ? () => handleStartShowPath?.(possiblePath) : undefined}
|
||||
onMouseLeave={handleStopShowPath}>
|
||||
<>
|
||||
{character &&
|
||||
<div className={"flex-center w-full h-full"}>
|
||||
<CharacterComponent
|
||||
character={character}
|
||||
onClick={handleSelectCharacter}
|
||||
className={`${isSelected ? "animate-bounce" : ""}`}/>
|
||||
</div>
|
||||
}
|
||||
{showPath &&
|
||||
<PathSymbol/>
|
||||
}
|
||||
<AddDummy path={possiblePath}/>
|
||||
</>
|
||||
</Tile>
|
||||
);
|
||||
};
|
||||
|
||||
const PathSymbol: Component = () => ( // TODO sometimes shows up when it shouldn't
|
||||
<div className={"flex-center w-full h-full"}>
|
||||
<div className={"w-1/2 h-1/2 rounded-full bg-white"}/>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface TileProps extends ChildProps {
|
||||
size: number,
|
||||
type?: TileType,
|
||||
onClick?: () => void,
|
||||
onMouseEnter?: () => void,
|
||||
onMouseLeave?: () => void,
|
||||
character?: Character,
|
||||
onCharacterClick?: (character: Character) => void,
|
||||
characterClass?: string,
|
||||
}
|
||||
|
||||
const Tile: Component<TileProps> = (
|
||||
{
|
||||
size,
|
||||
type = TileType.empty,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
className,
|
||||
children
|
||||
}) => {
|
||||
|
||||
function setColor(): string {
|
||||
switch (type) {
|
||||
case TileType.empty:
|
||||
return "bg-black";
|
||||
case TileType.wall:
|
||||
return "bg-blue-500";
|
||||
case TileType.pellet:
|
||||
return "bg-yellow-500";
|
||||
case TileType.powerPellet:
|
||||
return "bg-orange-500";
|
||||
case TileType.ghostSpawn:
|
||||
return "bg-red-500";
|
||||
case TileType.pacmanSpawn:
|
||||
return "bg-green-500";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${setColor()} hover:border relative max-w-[75px] max-h-[75px] ${className}`}
|
||||
style={{width: `${size}px`, height: `${size}px`}}
|
||||
onClick={onClick}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface AddDummyProps extends ComponentProps {
|
||||
path?: Path;
|
||||
}
|
||||
|
||||
const AddDummy: Component<AddDummyProps> = ({path}) => (
|
||||
<>
|
||||
{path &&
|
||||
<div className={"flex-center w-full h-full"}>
|
||||
<CharacterComponent character={new Dummy(path)}/>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
|
||||
interface CharacterComponentProps extends ComponentProps {
|
||||
character?: Character,
|
||||
onClick?: (character: Character) => void,
|
||||
}
|
||||
|
||||
const CharacterComponent: Component<CharacterComponentProps> = (
|
||||
{
|
||||
character,
|
||||
onClick,
|
||||
className
|
||||
}) => {
|
||||
|
||||
function getSide() {
|
||||
switch (character?.position.direction) {
|
||||
case Direction.up:
|
||||
return "right-1/4 top-0";
|
||||
case Direction.down:
|
||||
return "right-1/4 bottom-0";
|
||||
case Direction.left:
|
||||
return "left-0 top-1/4";
|
||||
case Direction.right:
|
||||
return "right-0 top-1/4";
|
||||
}
|
||||
}
|
||||
|
||||
if (character === undefined) return null;
|
||||
|
||||
return (
|
||||
<div className={`rounded-full w-4/5 h-4/5 cursor-pointer hover:border border-black relative ${className}`}
|
||||
style={{backgroundColor: `${character.color}`}}
|
||||
onClick={() => onClick?.(character)}>
|
||||
<div>
|
||||
<div className={`absolute ${getSide()} w-1/2 h-1/2 rounded-full bg-black`}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -125,6 +125,12 @@ test("Pac-Man rolls six from position [1,5] (down), should return 17", () => {
|
||||
expect(result.length).toBe(17);
|
||||
});
|
||||
|
||||
test("Pac-Man rolls six from position [7,1] (right), path to [9,5] should be five tiles long", () => {
|
||||
pacMan.follow({end: {x: 7, y: 1}, direction: Direction.right});
|
||||
const result = possibleMovesAlgorithm(testMap, pacMan, 6);
|
||||
expect(result[0].path?.length).toBe(5);
|
||||
});
|
||||
|
||||
function arrayEquals<T extends any[]>(result: T, expected: T, message?: string): void {
|
||||
for (const item of expected) {
|
||||
expect(result, message).toContainEqual(item);
|
||||
|
Loading…
x
Reference in New Issue
Block a user