Added modal to select character to steal from, Added button component
This commit is contained in:
parent
cf195e8e43
commit
68fe8192aa
26
pac-man-board-game/ClientApp/src/components/Button.tsx
Normal file
26
pac-man-board-game/ClientApp/src/components/Button.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const Button: Component<ButtonProps> = (
|
||||||
|
{
|
||||||
|
className,
|
||||||
|
onClick,
|
||||||
|
style,
|
||||||
|
title,
|
||||||
|
id,
|
||||||
|
disabled = false,
|
||||||
|
children,
|
||||||
|
type = "button",
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
id={id}
|
||||||
|
className={`button-default ${className}`}
|
||||||
|
style={style}
|
||||||
|
disabled={disabled}
|
||||||
|
type={type}
|
||||||
|
title={title}
|
||||||
|
onClick={onClick}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {useAtom} from "jotai";
|
import {useAtom, useAtomValue} from "jotai";
|
||||||
import {selectedDiceAtom} from "../utils/state";
|
import {selectedDiceAtom, thisPlayerAtom} from "../utils/state";
|
||||||
|
import {Button} from "./Button";
|
||||||
|
|
||||||
interface AllDiceProps extends ComponentProps {
|
interface AllDiceProps extends ComponentProps {
|
||||||
values?: number[],
|
values?: number[],
|
||||||
@ -41,6 +42,8 @@ export const Dice: Component<DiceProps> = (
|
|||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
|
const thisPlayer = useAtomValue(thisPlayerAtom);
|
||||||
|
|
||||||
function handleClick() {
|
function handleClick() {
|
||||||
if (onClick && value) {
|
if (onClick && value) {
|
||||||
onClick(value);
|
onClick(value);
|
||||||
@ -48,8 +51,10 @@ export const Dice: Component<DiceProps> = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={`text-2xl bg-gray-400 px-4 m-1 ${className}`} onClick={handleClick}>
|
<Button className={`text-2xl bg-gray-400 px-4 m-1 ${className}`}
|
||||||
|
disabled={!thisPlayer?.isTurn()}
|
||||||
|
onClick={handleClick}>
|
||||||
{value?.toString()}
|
{value?.toString()}
|
||||||
</button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
import React, {useEffect, useState} from "react";
|
import React, {Fragment, useEffect, useState} from "react";
|
||||||
import {Character} from "../game/character";
|
import {Character} from "../game/character";
|
||||||
import findPossiblePositions from "../game/possibleMovesAlgorithm";
|
import findPossiblePositions from "../game/possibleMovesAlgorithm";
|
||||||
import {GameTile} from "./gameTile";
|
import {GameTile} from "./gameTile";
|
||||||
import {TileType} from "../game/tileType";
|
import {TileType} from "../game/tileType";
|
||||||
import {useAtomValue} from "jotai";
|
import {atom, PrimitiveAtom, useAtom, useAtomValue, useSetAtom} from "jotai";
|
||||||
import {allCharactersAtom, currentPlayerAtom, selectedDiceAtom} from "../utils/state";
|
import {allCharactersAtom, currentPlayerAtom, playersAtom, selectedDiceAtom} from "../utils/state";
|
||||||
import Pellet from "../game/pellet";
|
import Pellet from "../game/pellet";
|
||||||
|
import {Dialog, Transition} from "@headlessui/react";
|
||||||
|
|
||||||
interface BoardProps extends ComponentProps {
|
interface BoardProps extends ComponentProps {
|
||||||
onMove?: Action<Position[]>,
|
onMove?: Action<Position[]>,
|
||||||
map: GameMap
|
map: GameMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const modalOpenAtom: PrimitiveAtom<boolean> = atom(false);
|
||||||
|
|
||||||
const Board: Component<BoardProps> = (
|
const Board: Component<BoardProps> = (
|
||||||
{
|
{
|
||||||
className,
|
className,
|
||||||
@ -25,6 +28,7 @@ const Board: Component<BoardProps> = (
|
|||||||
const [selectedCharacter, setSelectedCharacter] = useState<Character>();
|
const [selectedCharacter, setSelectedCharacter] = useState<Character>();
|
||||||
const [possiblePositions, setPossiblePositions] = useState<Path[]>([]); // TODO reset when other client moves a character
|
const [possiblePositions, setPossiblePositions] = useState<Path[]>([]); // TODO reset when other client moves a character
|
||||||
const [hoveredPosition, setHoveredPosition] = useState<Path>();
|
const [hoveredPosition, setHoveredPosition] = useState<Path>();
|
||||||
|
const setModalOpen = useSetAtom(modalOpenAtom);
|
||||||
|
|
||||||
function handleSelectCharacter(character: Character): void {
|
function handleSelectCharacter(character: Character): void {
|
||||||
if (character.isPacMan() && currentPlayer?.PacMan.Colour !== character.Colour) {
|
if (character.isPacMan() && currentPlayer?.PacMan.Colour !== character.Colour) {
|
||||||
@ -61,12 +65,8 @@ const Board: Component<BoardProps> = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO steal from other player
|
|
||||||
function stealFromPlayer(): void {
|
function stealFromPlayer(): void {
|
||||||
// TODO select player to steal from
|
setModalOpen(true);
|
||||||
// TODO modal to select player
|
|
||||||
// const victim
|
|
||||||
// currentPlayer?.stealFrom(victim)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickUpPellets(destination: Path): Position[] {
|
function pickUpPellets(destination: Path): Position[] {
|
||||||
@ -103,6 +103,7 @@ const Board: Component<BoardProps> = (
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`w-fit ${className}`}>
|
<div className={`w-fit ${className}`}>
|
||||||
|
<SelectPlayerModal/>
|
||||||
{
|
{
|
||||||
map.map((row, rowIndex) =>
|
map.map((row, rowIndex) =>
|
||||||
<div key={rowIndex} className={"flex"}>
|
<div key={rowIndex} className={"flex"}>
|
||||||
@ -128,3 +129,88 @@ const Board: Component<BoardProps> = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default Board;
|
export default Board;
|
||||||
|
|
||||||
|
const SelectPlayerModal: Component = () => {
|
||||||
|
const [isOpen, setIsOpen] = useAtom(modalOpenAtom);
|
||||||
|
const currentPlayer = useAtomValue(currentPlayerAtom);
|
||||||
|
const allPlayers = useAtomValue(playersAtom).filter(p => p !== currentPlayer);
|
||||||
|
|
||||||
|
if (currentPlayer === undefined) return null;
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
|
<Dialog as="div" className="relative z-10" onClose={close}>
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-25"/>
|
||||||
|
</Transition.Child>
|
||||||
|
|
||||||
|
<div className="fixed inset-0 overflow-y-auto">
|
||||||
|
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0 scale-95"
|
||||||
|
enterTo="opacity-100 scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100 scale-100"
|
||||||
|
leaveTo="opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Dialog.Panel
|
||||||
|
className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||||
|
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-900">
|
||||||
|
Steal from player
|
||||||
|
</Dialog.Title>
|
||||||
|
<div className="mt-2">
|
||||||
|
<Dialog.Description className="text-sm text-gray-500">
|
||||||
|
Select a player to steal up to 2 pellets from.
|
||||||
|
</Dialog.Description>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
allPlayers.map(player =>
|
||||||
|
<div key={player.Name} className={"border-b pb-1"}>
|
||||||
|
<span className={"mx-2"}>{player.Name} has {player.Box.count} pellets</span>
|
||||||
|
<button className={"text-blue-500 enabled:cursor-pointer disabled:text-gray-500"}
|
||||||
|
style={{background: "none"}}
|
||||||
|
disabled={player.Box.count === 0}
|
||||||
|
onClick={() => {
|
||||||
|
currentPlayer?.stealFrom(player);
|
||||||
|
close();
|
||||||
|
}}>
|
||||||
|
Steal
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
|
onClick={close}
|
||||||
|
>
|
||||||
|
Don't steal from anyone
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ import React, {MouseEventHandler} from "react";
|
|||||||
import {State} from "../game/player";
|
import {State} from "../game/player";
|
||||||
import {currentPlayerAtom, rollDiceButtonAtom, thisPlayerAtom} from "../utils/state";
|
import {currentPlayerAtom, rollDiceButtonAtom, thisPlayerAtom} from "../utils/state";
|
||||||
import {useAtomValue} from "jotai";
|
import {useAtomValue} from "jotai";
|
||||||
|
import {Button} from "./Button";
|
||||||
|
|
||||||
interface GameButtonProps extends ComponentProps {
|
interface GameButtonProps extends ComponentProps {
|
||||||
onReadyClick?: MouseEventHandler,
|
onReadyClick?: MouseEventHandler,
|
||||||
@ -19,12 +20,12 @@ const GameButton: Component<GameButtonProps> = (
|
|||||||
const activeRollDiceButton = useAtomValue(rollDiceButtonAtom);
|
const activeRollDiceButton = useAtomValue(rollDiceButtonAtom);
|
||||||
|
|
||||||
if (currentPlayer === undefined || currentPlayer.State === State.waitingForPlayers) {
|
if (currentPlayer === undefined || currentPlayer.State === State.waitingForPlayers) {
|
||||||
return <button onClick={onReadyClick}>Ready</button>;
|
return <Button onClick={onReadyClick}>Ready</Button>;
|
||||||
}
|
}
|
||||||
if (!thisPlayer?.isTurn()) { // TODO also show when waiting for other players
|
if (!thisPlayer?.isTurn()) { // TODO also show when waiting for other players
|
||||||
return <button disabled>Please wait</button>;
|
return <Button disabled>Please wait</Button>;
|
||||||
}
|
}
|
||||||
return <button onClick={onRollDiceClick} disabled={!activeRollDiceButton}>Roll dice</button>;
|
return <Button onClick={onRollDiceClick} disabled={!activeRollDiceButton}>Roll dice</Button>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GameButton;
|
export default GameButton;
|
||||||
|
@ -13,13 +13,13 @@ import GameButton from "./gameButton";
|
|||||||
|
|
||||||
const wsService = new WebSocketService(import.meta.env.VITE_API);
|
const wsService = new WebSocketService(import.meta.env.VITE_API);
|
||||||
|
|
||||||
// TODO ghost should be able to take pacmen
|
// TODO bug, when taking player on last dice, the currentPlayer changes and the wrong character get to steal
|
||||||
|
|
||||||
|
// TODO move stats above dice
|
||||||
// TODO don't start game until at least 2 players have joined
|
// TODO don't start game until at least 2 players have joined
|
||||||
// TODO join game lobby
|
// TODO join game lobby
|
||||||
// TODO sign up player page
|
// TODO sign up player page
|
||||||
// TODO sign in page
|
// TODO sign in page
|
||||||
// TODO steal from other players
|
|
||||||
// TODO show box with collected pellets
|
// TODO show box with collected pellets
|
||||||
// TODO layout
|
// TODO layout
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import {Character, CharacterType} from "./character";
|
|||||||
import Box from "./box";
|
import Box from "./box";
|
||||||
import {Colour} from "./colour";
|
import {Colour} from "./colour";
|
||||||
import {getDefaultStore} from "jotai";
|
import {getDefaultStore} from "jotai";
|
||||||
import {currentPlayerNameAtom} from "../utils/state";
|
import {currentPlayerNameAtom, playersAtom} from "../utils/state";
|
||||||
import Pellet from "./pellet";
|
import Pellet from "./pellet";
|
||||||
|
|
||||||
export enum State {
|
export enum State {
|
||||||
@ -44,6 +44,8 @@ export default class Player {
|
|||||||
if (pellet)
|
if (pellet)
|
||||||
this.Box.addPellet(pellet);
|
this.Box.addPellet(pellet);
|
||||||
}
|
}
|
||||||
|
const store = getDefaultStore();
|
||||||
|
store.set(playersAtom, store.get(playersAtom).map(player => player));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,7 @@ br {
|
|||||||
@apply my-2;
|
@apply my-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
button, button[type=submit], input[type=button], input[type=submit] {
|
.button-default, button[type=submit], input[type=submit] {
|
||||||
@apply bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded;
|
@apply bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded;
|
||||||
}
|
@apply disabled:bg-gray-500;
|
||||||
|
|
||||||
button[disabled] {
|
|
||||||
@apply bg-gray-500;
|
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,13 @@ interface ComponentProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ChildProps extends ComponentProps {
|
interface ChildProps extends ComponentProps {
|
||||||
children?: React.JSX.Element,
|
children?: React.JSX.Element | string,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ButtonProps extends ChildProps {
|
||||||
|
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void,
|
||||||
|
disabled?: boolean,
|
||||||
|
type?: "button" | "submit" | "reset",
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InputProps extends ComponentProps {
|
interface InputProps extends ComponentProps {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user