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 {useAtom} from "jotai";
import {selectedDiceAtom} from "../utils/state";
import {useAtom, useAtomValue} from "jotai";
import {selectedDiceAtom, thisPlayerAtom} from "../utils/state";
import {Button} from "./Button";
interface AllDiceProps extends ComponentProps {
values?: number[],
@ -41,6 +42,8 @@ export const Dice: Component<DiceProps> = (
onClick,
}) => {
const thisPlayer = useAtomValue(thisPlayerAtom);
function handleClick() {
if (onClick && value) {
onClick(value);
@ -48,8 +51,10 @@ export const Dice: Component<DiceProps> = (
}
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()}
</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 findPossiblePositions from "../game/possibleMovesAlgorithm";
import {GameTile} from "./gameTile";
import {TileType} from "../game/tileType";
import {useAtomValue} from "jotai";
import {allCharactersAtom, currentPlayerAtom, selectedDiceAtom} from "../utils/state";
import {atom, PrimitiveAtom, useAtom, useAtomValue, useSetAtom} from "jotai";
import {allCharactersAtom, currentPlayerAtom, playersAtom, selectedDiceAtom} from "../utils/state";
import Pellet from "../game/pellet";
import {Dialog, Transition} from "@headlessui/react";
interface BoardProps extends ComponentProps {
onMove?: Action<Position[]>,
map: GameMap
}
const modalOpenAtom: PrimitiveAtom<boolean> = atom(false);
const Board: Component<BoardProps> = (
{
className,
@ -25,6 +28,7 @@ const Board: Component<BoardProps> = (
const [selectedCharacter, setSelectedCharacter] = useState<Character>();
const [possiblePositions, setPossiblePositions] = useState<Path[]>([]); // TODO reset when other client moves a character
const [hoveredPosition, setHoveredPosition] = useState<Path>();
const setModalOpen = useSetAtom(modalOpenAtom);
function handleSelectCharacter(character: Character): void {
if (character.isPacMan() && currentPlayer?.PacMan.Colour !== character.Colour) {
@ -61,12 +65,8 @@ const Board: Component<BoardProps> = (
}
}
// TODO steal from other player
function stealFromPlayer(): void {
// TODO select player to steal from
// TODO modal to select player
// const victim
// currentPlayer?.stealFrom(victim)
setModalOpen(true);
}
function pickUpPellets(destination: Path): Position[] {
@ -103,6 +103,7 @@ const Board: Component<BoardProps> = (
return (
<div className={`w-fit ${className}`}>
<SelectPlayerModal/>
{
map.map((row, rowIndex) =>
<div key={rowIndex} className={"flex"}>
@ -128,3 +129,88 @@ const Board: Component<BoardProps> = (
};
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 {currentPlayerAtom, rollDiceButtonAtom, thisPlayerAtom} from "../utils/state";
import {useAtomValue} from "jotai";
import {Button} from "./Button";
interface GameButtonProps extends ComponentProps {
onReadyClick?: MouseEventHandler,
@ -19,12 +20,12 @@ const GameButton: Component<GameButtonProps> = (
const activeRollDiceButton = useAtomValue(rollDiceButtonAtom);
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
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;

View File

@ -13,13 +13,13 @@ import GameButton from "./gameButton";
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 join game lobby
// TODO sign up player page
// TODO sign in page
// TODO steal from other players
// TODO show box with collected pellets
// TODO layout

View File

@ -2,7 +2,7 @@ import {Character, CharacterType} from "./character";
import Box from "./box";
import {Colour} from "./colour";
import {getDefaultStore} from "jotai";
import {currentPlayerNameAtom} from "../utils/state";
import {currentPlayerNameAtom, playersAtom} from "../utils/state";
import Pellet from "./pellet";
export enum State {
@ -44,6 +44,8 @@ export default class Player {
if (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;
}
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;
}
button[disabled] {
@apply bg-gray-500;
@apply disabled:bg-gray-500;
}

View File

@ -10,7 +10,13 @@ interface 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 {