Enable placing pieces in the game board
parent
835ce2c62a
commit
1ab3db8f67
|
|
@ -1,10 +1,16 @@
|
|||
import { CellType } from "../constants/CellType";
|
||||
import { Direction } from "../constants/Direction";
|
||||
import { PieceId, pieceMap } from "../constants/Pieces";
|
||||
import { ExternalNode } from "./ExternalNode";
|
||||
import { PlacedPiece } from "./PlacedPiece";
|
||||
|
||||
export class Cell {
|
||||
public readonly externalNodes: Map<Direction, ExternalNode>;
|
||||
public readonly cellType: CellType;
|
||||
public placedPiece?: {
|
||||
piece: PlacedPiece;
|
||||
id: PieceId;
|
||||
};
|
||||
|
||||
constructor(cellType: CellType) {
|
||||
this.externalNodes = new Map([
|
||||
|
|
@ -21,4 +27,12 @@ export class Cell {
|
|||
if (!node) throw Error(`Could not find node at ${direction}`);
|
||||
return node!;
|
||||
}
|
||||
|
||||
public placePiece(pieceId: PieceId) {
|
||||
if (this.placedPiece !== undefined) return;
|
||||
this.placedPiece = {
|
||||
piece: pieceMap[pieceId].toPlacedPiece(this),
|
||||
id: pieceId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { TrackType } from "../constants/TrackType";
|
|||
import { Cell } from "./Cell";
|
||||
import { InternalNode } from "./InternalNode";
|
||||
import { PlacedPiece } from "./PlacedPiece";
|
||||
import { Node } from "./Node";
|
||||
import { InternalNodeType } from "../constants/InternalNodeType";
|
||||
|
||||
export interface PieceProps {
|
||||
|
|
@ -74,8 +73,8 @@ export class Piece {
|
|||
nodes: {
|
||||
firstNode: cell.getNodeAt(track.joinedPoints.firstPoint),
|
||||
secondNode:
|
||||
track.joinedPoints.secondPoint instanceof Node
|
||||
? (track.joinedPoints.secondPoint as Node)
|
||||
track.joinedPoints.secondPoint instanceof InternalNode
|
||||
? (track.joinedPoints.secondPoint as InternalNode)
|
||||
: cell.getNodeAt(track.joinedPoints.secondPoint as Direction),
|
||||
},
|
||||
type: track.type,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,32 @@
|
|||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import GameBoard from "./components/GameBoard";
|
||||
import DiceSet from "./components/DiceSet";
|
||||
import "./GamePage.scss";
|
||||
import { PieceId } from "interface";
|
||||
|
||||
const GamePage = () => {
|
||||
const getRandomPieceId = () => {
|
||||
const randomId = Math.floor(Math.random() * Object.keys(PieceId).length);
|
||||
return Object.values(PieceId)[randomId];
|
||||
};
|
||||
|
||||
const [dice, setDice] = useState(
|
||||
[1, 2, 3, 4].map(() => {
|
||||
return {
|
||||
pieceId: getRandomPieceId(),
|
||||
isSelected: false,
|
||||
isDisabled: false,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1>Game Page Title</h1>
|
||||
<div className="game-panel">
|
||||
<GameBoard />
|
||||
<GameBoard dice={dice} setDice={setDice} />
|
||||
<div className="rigth-panel">
|
||||
<DiceSet />
|
||||
<DiceSet dice={dice} setDice={setDice} />
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
.cell {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 2px solid black;
|
||||
border-radius: 10%;
|
||||
|
||||
.piece {
|
||||
border-radius: 10%;
|
||||
position: relative;
|
||||
|
||||
&:nth-child(2) {
|
||||
top: -20px;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
top: -40px;
|
||||
}
|
||||
}
|
||||
|
||||
&.house {
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
&.university {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
&.factory {
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
.exit:has(.exit-road) {
|
||||
border: solid black;
|
||||
border-width: 0 1px;
|
||||
|
||||
.exit-road {
|
||||
margin: auto;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border: dashed black;
|
||||
border-width: 0 1px;
|
||||
transform: translate(calc(50% - 1px));
|
||||
}
|
||||
}
|
||||
|
||||
.exit:has(.exit-road) {
|
||||
border: solid black;
|
||||
border-width: 0 1px;
|
||||
|
||||
.exit-road {
|
||||
margin: auto;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border: dashed black;
|
||||
border-width: 0 1px;
|
||||
transform: translate(calc(50% - 1px));
|
||||
}
|
||||
}
|
||||
|
||||
.exit:has(.exit-rail) {
|
||||
border-width: 0 1px;
|
||||
|
||||
.exit-rail {
|
||||
margin: auto;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border: solid black;
|
||||
border-width: 0 1px;
|
||||
transform: translate(calc(50% - 1px));
|
||||
}
|
||||
}
|
||||
|
||||
.exit:has(.exit-ambivalent) {
|
||||
border-width: 0 1px;
|
||||
border-style: dotted;
|
||||
|
||||
.exit-ambivalent {
|
||||
margin: auto;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border: dotted black;
|
||||
border-width: 0 1px 0 0;
|
||||
transform: translate(calc(50%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.exit {
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
&:has(.exit-north) {
|
||||
left: 50%;
|
||||
transform: translate(calc(-50% - 1px), -100%);
|
||||
}
|
||||
|
||||
&:has(.exit-south) {
|
||||
left: 50%;
|
||||
top: 100%;
|
||||
transform: translate(calc(-50% - 1px), 0%);
|
||||
}
|
||||
|
||||
&:has(.exit-east) {
|
||||
left: 100%;
|
||||
top: 50%;
|
||||
transform: rotate(90deg) translate(-50%, 0);
|
||||
}
|
||||
|
||||
&:has(.exit-west) {
|
||||
left: 0%;
|
||||
top: 50%;
|
||||
transform: rotate(90deg) translate(-50%, 100%);
|
||||
}
|
||||
|
||||
&:has(.exit-north), &:has(.exit-south) {
|
||||
+ .exit:has(.exit-east) {
|
||||
left: 100%;
|
||||
transform: rotate(90deg) translate(-150%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
&:has(.exit-north), &:has(.exit-south) {
|
||||
+ .exit:has(.exit-west) {
|
||||
top: 50%;
|
||||
transform: rotate(90deg) translate(-150%, 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import { Cell, directions, Exit, PieceId } from "interface";
|
||||
import "./BoardCell.scss";
|
||||
|
||||
export interface BoardCellProps {
|
||||
cell: Cell;
|
||||
dice: {
|
||||
pieceId: PieceId;
|
||||
isSelected: boolean;
|
||||
isDisabled: boolean;
|
||||
}[];
|
||||
setDice: React.Dispatch<
|
||||
React.SetStateAction<
|
||||
{
|
||||
pieceId: PieceId;
|
||||
isSelected: boolean;
|
||||
isDisabled: boolean;
|
||||
}[]
|
||||
>
|
||||
>;
|
||||
refreshBoardRender: () => void;
|
||||
}
|
||||
|
||||
const BoardCell = (props: BoardCellProps) => {
|
||||
const { cell, dice, setDice, refreshBoardRender } = props;
|
||||
const handleBoardCellClick = () => {
|
||||
const selectedDie = dice.find((die) => die.isSelected);
|
||||
if (!selectedDie) return;
|
||||
cell.placePiece(selectedDie.pieceId);
|
||||
setDice(
|
||||
dice.map((die) => {
|
||||
return die !== selectedDie
|
||||
? die
|
||||
: {
|
||||
pieceId: selectedDie.pieceId,
|
||||
isSelected: false,
|
||||
isDisabled: true,
|
||||
};
|
||||
}),
|
||||
);
|
||||
refreshBoardRender();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={"cell " + cell.cellType.toLowerCase()}
|
||||
onClick={handleBoardCellClick}
|
||||
>
|
||||
{directions.map((direction) => {
|
||||
const traversedNode = cell.getNodeAt(direction).traverseBorder();
|
||||
const isExit = traversedNode instanceof Exit;
|
||||
if (!isExit) return;
|
||||
const className =
|
||||
`exit-${direction.toLowerCase()}` +
|
||||
` exit-${(traversedNode as Exit).type.toLowerCase()}`;
|
||||
return (
|
||||
<div className="exit">
|
||||
<div className={className} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{cell.placedPiece && (
|
||||
<img className="piece" src={`pieces/${cell.placedPiece.id}.jpeg`}></img>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoardCell;
|
||||
|
|
@ -3,15 +3,4 @@
|
|||
grid-template-columns: repeat(2, auto);
|
||||
gap: 20px;
|
||||
padding: 50px;
|
||||
|
||||
.dice {
|
||||
height: 150px;
|
||||
width: 150px;
|
||||
border: 2px solid black;
|
||||
border-radius: 10%;
|
||||
|
||||
img{
|
||||
border-radius: 10%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,45 @@
|
|||
import { PieceId } from "interface";
|
||||
import "./DiceSet.scss";
|
||||
import Die from "./Die";
|
||||
|
||||
const DiceSet = () => {
|
||||
const getRandomPieceId = () => {
|
||||
const randomId = Math.floor(Math.random() * Object.keys(PieceId).length);
|
||||
return Object.values(PieceId)[randomId];
|
||||
export interface DiceSetProps {
|
||||
dice: {
|
||||
pieceId: PieceId;
|
||||
isSelected: boolean;
|
||||
isDisabled: boolean;
|
||||
}[];
|
||||
setDice: React.Dispatch<
|
||||
React.SetStateAction<
|
||||
{
|
||||
pieceId: PieceId;
|
||||
isSelected: boolean;
|
||||
isDisabled: boolean;
|
||||
}[]
|
||||
>
|
||||
>;
|
||||
}
|
||||
const DiceSet = (props: DiceSetProps) => {
|
||||
const { dice, setDice } = props;
|
||||
const handleDieClick = (die: {
|
||||
pieceId: PieceId;
|
||||
isSelected: boolean;
|
||||
isDisabled: boolean;
|
||||
}) => {
|
||||
if (die.isDisabled) return;
|
||||
const newDiceState = dice.map((oldDie) => {
|
||||
const isSelected = die === oldDie;
|
||||
return {
|
||||
...oldDie,
|
||||
isSelected: isSelected,
|
||||
};
|
||||
});
|
||||
setDice(newDiceState);
|
||||
};
|
||||
const displayedPieceIds = [1, 2, 3, 4].map(getRandomPieceId);
|
||||
|
||||
return (
|
||||
<div className="dice-set">
|
||||
{displayedPieceIds.map((pieceId) => (
|
||||
<div className="dice">
|
||||
<img src={`pieces/${pieceId}.jpeg`}></img>
|
||||
</div>
|
||||
{dice.map((die) => (
|
||||
<Die die={die} handleDieClick={handleDieClick} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
.dice {
|
||||
height: 150px;
|
||||
width: 150px;
|
||||
border: 2px solid black;
|
||||
border-radius: 10%;
|
||||
|
||||
img{
|
||||
border-radius: 10%;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
box-shadow: 0 0 20px green;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
box-shadow: 0 0 20px grey;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { PieceId } from "interface";
|
||||
import "./Die.scss";
|
||||
|
||||
interface DieProps {
|
||||
die: { pieceId: PieceId; isSelected: boolean; isDisabled: boolean };
|
||||
handleDieClick: (die: {
|
||||
pieceId: PieceId;
|
||||
isSelected: boolean;
|
||||
isDisabled: boolean;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
const Die = (props: DieProps) => {
|
||||
const { die, handleDieClick } = props;
|
||||
const { pieceId, isSelected, isDisabled } = die;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`dice${isSelected ? " selected" : ""}${isDisabled ? " disabled" : ""}`}
|
||||
onClick={() => handleDieClick(die)}
|
||||
>
|
||||
<img src={`pieces/${pieceId}.jpeg`}></img>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Die;
|
||||
|
|
@ -7,121 +7,4 @@
|
|||
grid-template-columns: repeat(7, auto);
|
||||
gap: auto;
|
||||
padding: 50px;
|
||||
|
||||
.cell {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 2px solid black;
|
||||
border-radius: 10%;
|
||||
|
||||
&.house {
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
&.university {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
&.factory {
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
.exit:has(.exit-road) {
|
||||
border: solid black;
|
||||
border-width: 0 1px;
|
||||
|
||||
.exit-road {
|
||||
margin: auto;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border: dashed black;
|
||||
border-width: 0 1px;
|
||||
transform: translate(calc(50% - 1px));
|
||||
}
|
||||
}
|
||||
|
||||
.exit:has(.exit-road) {
|
||||
border: solid black;
|
||||
border-width: 0 1px;
|
||||
|
||||
.exit-road {
|
||||
margin: auto;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border: dashed black;
|
||||
border-width: 0 1px;
|
||||
transform: translate(calc(50% - 1px));
|
||||
}
|
||||
}
|
||||
|
||||
.exit:has(.exit-rail) {
|
||||
border-width: 0 1px;
|
||||
|
||||
.exit-rail {
|
||||
margin: auto;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border: solid black;
|
||||
border-width: 0 1px;
|
||||
transform: translate(calc(50% - 1px));
|
||||
}
|
||||
}
|
||||
|
||||
.exit:has(.exit-ambivalent) {
|
||||
border-width: 0 1px;
|
||||
border-style: dotted;
|
||||
|
||||
.exit-ambivalent {
|
||||
margin: auto;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border: dotted black;
|
||||
border-width: 0 1px 0 0;
|
||||
transform: translate(calc(50%));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.exit {
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
&:has(.exit-north) {
|
||||
left: 50%;
|
||||
transform: translate(calc(-50% - 1px), -100%);
|
||||
}
|
||||
|
||||
&:has(.exit-south) {
|
||||
left: 50%;
|
||||
top: 100%;
|
||||
transform: translate(calc(-50% - 1px), 0%);
|
||||
}
|
||||
|
||||
&:has(.exit-east) {
|
||||
left: 100%;
|
||||
top: 50%;
|
||||
transform: rotate(90deg) translate(-50%, 0);
|
||||
}
|
||||
|
||||
&:has(.exit-west) {
|
||||
left: 0%;
|
||||
top: 50%;
|
||||
transform: rotate(90deg) translate(-50%, 100%);
|
||||
}
|
||||
|
||||
&:has(.exit-north), &:has(.exit-south) {
|
||||
+ .exit:has(.exit-east) {
|
||||
left: 100%;
|
||||
transform: rotate(90deg) translate(-150%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
&:has(.exit-north), &:has(.exit-south) {
|
||||
+ .exit:has(.exit-west) {
|
||||
top: 50%;
|
||||
transform: rotate(90deg) translate(-150%, 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +1,42 @@
|
|||
import { buildBoard, Cell, directions, Exit } from "interface";
|
||||
import { buildBoard, PieceId } from "interface";
|
||||
import "./GameBoard.scss";
|
||||
import { useState } from "react";
|
||||
import BoardCell from "./BoardCell";
|
||||
|
||||
const GameBoard = () => {
|
||||
const board: Cell[][] = buildBoard();
|
||||
export interface GameBoardProps {
|
||||
dice: {
|
||||
pieceId: PieceId;
|
||||
isSelected: boolean;
|
||||
isDisabled: boolean;
|
||||
}[];
|
||||
setDice: React.Dispatch<
|
||||
React.SetStateAction<
|
||||
{
|
||||
pieceId: PieceId;
|
||||
isSelected: boolean;
|
||||
isDisabled: boolean;
|
||||
}[]
|
||||
>
|
||||
>;
|
||||
}
|
||||
|
||||
const GameBoard = (props: GameBoardProps) => {
|
||||
const [board, setBoard] = useState(buildBoard());
|
||||
const [id, setId] = useState(1);
|
||||
const refreshBoardRender = () => {
|
||||
setBoard(board);
|
||||
setId(id + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="game-board">
|
||||
<div className="game-board" id={id.toString()}>
|
||||
{board.flatMap((row) =>
|
||||
row.map((cell) => (
|
||||
<div className={"cell " + cell.cellType.toLowerCase()}>
|
||||
{directions.map((direction) => {
|
||||
const traversedNode = cell.getNodeAt(direction).traverseBorder();
|
||||
const isExit = traversedNode instanceof Exit;
|
||||
if (!isExit) return;
|
||||
const className =
|
||||
`exit-${direction.toLowerCase()}` +
|
||||
` exit-${(traversedNode as Exit).type.toLowerCase()}`;
|
||||
return (
|
||||
<div className="exit">
|
||||
<div className={className} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<BoardCell
|
||||
{...props}
|
||||
cell={cell}
|
||||
refreshBoardRender={refreshBoardRender}
|
||||
/>
|
||||
)),
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue