Added modal to select character to steal from, Added button component

This commit is contained in:
Martin Berg Alstad 2023-07-17 13:10:33 +02:00
parent cf195e8e43
commit 68fe8192aa
8 changed files with 147 additions and 24 deletions

View 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>
)
}

View File

@ -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>
); );
}; };

View File

@ -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>
</>
)
}

View File

@ -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;

View File

@ -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

View File

@ -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));
} }
} }

View File

@ -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;
} }

View File

@ -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 {