summaryrefslogtreecommitdiff
path: root/client/src/Mancala
diff options
context:
space:
mode:
authorMike Vink <mike1994vink@gmail.com>2021-06-24 23:11:39 +0200
committerMike Vink <mike1994vink@gmail.com>2021-06-24 23:11:39 +0200
commit976ed2b105e80b257ebb724b3061400f37460041 (patch)
treebd04ac5d0a7978d6d8a4a5e2d862bf574314f2f8 /client/src/Mancala
parent3906fcf3d702f13da79c797c91a2dd32d874af49 (diff)
mega commit
Diffstat (limited to 'client/src/Mancala')
-rw-r--r--client/src/Mancala/Mancala.tsx13
-rw-r--r--client/src/Mancala/Pit.css46
-rw-r--r--client/src/Mancala/Pit.tsx65
-rw-r--r--client/src/Mancala/Play.css45
-rw-r--r--client/src/Mancala/Play.tsx207
-rw-r--r--client/src/Mancala/StartGame.tsx45
6 files changed, 395 insertions, 26 deletions
diff --git a/client/src/Mancala/Mancala.tsx b/client/src/Mancala/Mancala.tsx
index 1c712c8..509410b 100644
--- a/client/src/Mancala/Mancala.tsx
+++ b/client/src/Mancala/Mancala.tsx
@@ -9,7 +9,7 @@ import "./Mancala.css";
* users to enter two player names and start a new game.
* If there's an active game this component holds the game state. This game state can be passed as a prop
* to child components as needed.
- *
+ *
* Child components can modify the game state by calling the setGameState (which they recieve as prop.)
*/
export function Mancala() {
@@ -20,9 +20,16 @@ export function Mancala() {
// To check if code you added also follows the rules of hooks, run "npm run lint" in the command line
const [ gameState, setGameState ] = useState<GameState | undefined>(undefined);
+ if (localStorage.getItem("state") !== null) {
+ var state = localStorage.getItem("state");
+ const gameState = JSON.parse(state as string);
+ return <Play gameState={gameState} setGameState={setGameState} />
+ }
+
if (!gameState) {
- return <StartGame setGameState={setGameState} />
+ return <StartGame gameState={gameState} setGameState={setGameState} />
}
+
return <Play gameState={gameState} setGameState={setGameState} />
-} \ No newline at end of file
+}
diff --git a/client/src/Mancala/Pit.css b/client/src/Mancala/Pit.css
new file mode 100644
index 0000000..8cd3f63
--- /dev/null
+++ b/client/src/Mancala/Pit.css
@@ -0,0 +1,46 @@
+.Pit {
+ display: inline-flex;
+
+ justify-content: center;
+ align-items: center;
+
+ width: 16%;
+}
+
+.Recession {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background: var(--recess-color);
+}
+
+.Pit .Recession {
+ border-radius: 50%;
+ height: 50%;
+ width: 100%;
+}
+
+.Kalaha {
+ display: inline-flex;
+
+ justify-content: center;
+ align-items: center;
+
+ width: 10%;
+}
+
+.Kalaha .Recession {
+ border-radius: 30%;
+ height: 50%;
+ width: 100%;
+}
+
+.Stone {
+ position: absolute;
+ background: gray;
+ border-radius: 50%;
+ border: 2px solid black;
+ height: 16px;
+ width: 16px;
+ min-height: 5px;
+}
diff --git a/client/src/Mancala/Pit.tsx b/client/src/Mancala/Pit.tsx
new file mode 100644
index 0000000..300f3f4
--- /dev/null
+++ b/client/src/Mancala/Pit.tsx
@@ -0,0 +1,65 @@
+import React, { useState } from "react";
+import type { GameState } from "../gameState";
+import "./Pit.css";
+
+type Flatten<T> = T extends any[] ? T[number] : T;
+
+type Player = Flatten<GameState["players"]>;
+
+type Pit = Flatten<Player["pits"]>;
+
+type PitProps = {
+ player: Player;
+ index: number;
+ pit: Pit;
+ onClick?: (index: number, player: Player, pit: Pit) => (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => Promise<void>
+ displayStones: (pit: Pit, spread: "small" | "big") => JSX.Element[]
+}
+
+type SmallBowl = Pit & {kind: "small"};
+type Kalaha = Pit & {kind: "big"};
+
+
+
+export function Pit({player, index, pit, onClick, displayStones}: PitProps) {
+ const smallBowl = pit as SmallBowl;
+ smallBowl.kind = "small";
+ if (onClick) {
+ return (
+ <div className="Pit">
+ {pit.nrOfStones > 10 ? "" + pit.nrOfStones : ""}
+ <div className="Recession" onClick={onClick(index, player, pit)}>
+ <div className="stones" id={"pit" + pit.index}>
+ {displayStones(pit, smallBowl.kind)}
+ </div>
+ </div>
+ </div>
+ );
+ } else {
+ return (
+ <div className="Pit">
+ <div className="Recession">
+ <div className="stones">
+ {pit.nrOfStones > 10 ? pit.nrOfStones : ""}
+ {displayStones(pit, smallBowl.kind)}
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+export function Kalaha({player, pit, displayStones}: PitProps) {
+ const kalaha = pit as Kalaha;
+ kalaha.kind = "big";
+ return (
+ <div className="Kalaha">
+ {pit.nrOfStones > 10 ? pit.nrOfStones : ""}
+ <div className="Recession">
+ <div className="stones" id={"pit" + pit.index}>
+ {displayStones(pit, kalaha.kind)}
+ </div>
+ </div>
+ </div>
+ );
+}
diff --git a/client/src/Mancala/Play.css b/client/src/Mancala/Play.css
index 8f73945..6aa0d8f 100644
--- a/client/src/Mancala/Play.css
+++ b/client/src/Mancala/Play.css
@@ -1 +1,44 @@
-/** Add some styles */ \ No newline at end of file
+/** Add some styles */
+#board {
+ display: flex;
+ background: var(--board-color);
+ padding: 20px;
+
+ border-radius: 10%;
+
+ justify-content: center;
+ flex-flow: row;
+ flex-direction: row;
+
+ width: 800px;
+ height: 400px;
+}
+
+#pits {
+ position: relative;
+ display: inline-flex;
+
+
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: center;
+
+ width: 80%;
+}
+
+.playArea {
+ position: relative;
+}
+
+.playerStatus {
+ position: relative;
+ display: flex;
+
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: center;
+
+ height: 100px;
+
+}
diff --git a/client/src/Mancala/Play.tsx b/client/src/Mancala/Play.tsx
index b72bce1..d2d7f65 100644
--- a/client/src/Mancala/Play.tsx
+++ b/client/src/Mancala/Play.tsx
@@ -1,17 +1,216 @@
-import React from "react";
+import React, { useState } from "react";
import type { GameState } from "../gameState";
import "./Play.css";
+import { Pit, Kalaha } from "./Pit"
+
+type Flatten<T> = T extends any[] ? T[number] : T;
+
+type Player = Flatten<GameState["players"]>;
+
+type Pit = Flatten<Player["pits"]>;
type PlayProps = {
gameState: GameState;
setGameState(newGameState: GameState): void;
}
+function xyTranslate(pit: Pit, index: number) {
+ var angle = index/pit.nrOfStones * 2 * Math.PI;
+ var d = (index % 2 + 1) * 13;
+ var ofX = -9;
+ var ofY = -9;
+ if (pit.index === 6 || pit.index === 13) {
+ var xrandom = Math.random();
+ var yrandom = Math.random();
+ return 'translate('+ ((xrandom > 0.5 ? Math.random() : -Math.random()) * 18 - 10) + 'px, ' + (yrandom > 0.5 ? Math.random() : -Math.random()) * 60 + 'px)';
+ } else {
+ return 'translate('+ (Math.cos(angle) * (d) + ofX) + 'px, ' + (Math.sin(angle) * (d) + ofY) + 'px)';
+ }
+}
+
export function Play({ gameState, setGameState }: PlayProps) {
+
+ function playPit(index: number, player: Player, pit: Pit) {
+ const pitTotal = gameState.players[0].pits.length;
+ const allPits = gameState.players[0].pits.concat(gameState.players[1].pits);
+ const stonesToPass = pit.state.stoneElements as JSX.Element[];
+ return async function event(event: React.MouseEvent<HTMLDivElement>) {
+ if (!player.hasTurn) return;
+ console.log("updating server");
+ console.log(pit.nrOfStones);
+ try {
+ const response = await fetch('mancala/api/play', {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({player: player.type === "player1" ? 0 : 1, index: index})
+ });
+
+ if (response.ok) {
+ const newGameState = await response.json();
+ localStorage.setItem("state", JSON.stringify(newGameState));
+ setGameState({gameState, ...newGameState});
+ } else {
+ console.error(response.statusText);
+ }
+ } catch (error) {
+ console.error(error.toString());
+ }
+ console.log(pit.nrOfStones);
+
+ }
+ }
+
+ // function animate(newGameState: GameState) {
+ // var pits = newGameState.players[0].pits.concat(newGameState.players[1].pits);
+ // var type;
+ // for (var pit of pits) {
+ // type = pit.index === 6 || pit.index === 13 ? "big" : "small" as "big" | "small";
+ // displayStones(pit, type);
+ // setTimeout(() => {}, 1000);
+ // }
+ // };
+
+ function stoneStyler(pit: Pit, index: number, spread: "small" | "big", ) {
+ const colors = ['gray', 'purple', 'blue', 'green'];
+ var stoneStyle;
+ if (spread === "small") {
+ stoneStyle = {
+ transform: xyTranslate(pit, index),
+ background: colors[index % colors.length],
+ };
+ } else {
+ stoneStyle = {
+ transform: xyTranslate(pit, index),
+ background: colors[index % colors.length],
+ };
+ }
+ return stoneStyle;
+ }
+
+ function displayStones(pit: Pit, spread: "small" | "big") {
+ var jsx;
+ var kind = pit.index === 6 || pit.index === 13 ? "big" : "small" as "small"|"big";
+ if (pit.state.stoneElements === undefined) {
+ pit.state.stoneElements = [];
+ for (let i = 0; i < pit.nrOfStones; i++) {
+ jsx = ( <div style={stoneStyler(pit, i, kind)} key={i * (pit.index + 1)} className="Stone"/> );
+ pit.state.stoneElements.reverse().push(jsx);
+ }
+ } else if (pit.nrOfStones < pit.state.stoneElements.length) {
+ // while (pit.nrOfStones < pit.state.stoneElements.length) {
+ // pit.state.stoneElements.pop();
+ // }
+ pit.state.stoneElements = [];
+ for (let i = 0; i < pit.nrOfStones; i++) {
+ jsx = ( <div style={stoneStyler(pit, i, kind)} key={i * (pit.index + 1)} className="Stone"/> );
+ pit.state.stoneElements.reverse().push(jsx);
+ }
+ } else if (pit.nrOfStones > pit.state.stoneElements.length) {
+ pit.state.stoneElements = [];
+ for (let i = 0; i < pit.nrOfStones; i++) {
+ jsx = ( <div style={stoneStyler(pit, i, kind)} key={i * (pit.index + 1)} className="Stone"/> );
+ pit.state.stoneElements.reverse().push(jsx);
+ }
+ }
+ return pit.state.stoneElements;
+ }
+
+ const playerPits = gameState.players.map(
+ player => {
+ return player.pits.slice(0, -1).map(
+ (pit, index) => {
+ const [ pitState, setPitState ] = useState<Pit["state"]>({stoneElements: undefined});
+ pit.state = pitState;
+ pit.setPitState = setPitState;
+ return (
+ <Pit
+ player={player}
+ index={player.type === "player1" ? index : player.pits.length + index}
+ key={player.type === "player1" ? index : player.pits.length + index}
+ pit={pit}
+ onClick={playPit}
+ displayStones={displayStones}
+ />
+ )
+ });
+ });
+
+ const playerKalahas = gameState.players.map(
+ (player, index) => {
+ const [ kalahaState, setKalahaState ] = useState<Pit["state"]>({stoneElements: undefined});
+ const pit = player.pits[player.pits.length - 1];
+ pit.state = kalahaState;
+ pit.setPitState = setKalahaState;
+ return (
+ <Kalaha
+ player={player}
+ index={player.type === "player1" ? player.pits.length - 1 : 2 * (player.pits.length) - 1}
+ key={index}
+ pit={pit}
+ displayStones={displayStones}
+ />
+ )
+ }
+ );
+
+ var player2Pits = [];
+ var player1Pits = [];
+
+ for (var i = 0; i < playerPits[0].length; i++) {
+ player1Pits.push(playerPits[0][i]);
+ player2Pits.push(playerPits[1][i]);
+ }
+
+ const revenge = (
+ <button onClick={async () => {
+ localStorage.clear();
+ try {
+ const response = await fetch('mancala/api/start', {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ nameplayer1: gameState.players[0].name, nameplayer2: gameState.players[1].name })
+ });
+
+ if (response.ok) {
+ const gameState = await response.json();
+ setGameState(gameState);
+ } else {
+ console.error(response.statusText);
+ }
+ } catch (error) {
+ console.error(error.toString());
+ }
+
+ }}>
+ game has ended... play again?
+ </button>
+ );
+
return (
- <div>
- <p>{gameState.players[0].name} vs {gameState.players[1].name}</p>
- To do...
+ <div className="playArea">
+ <div className="playerStatus" id="statusPlayer1">
+ {gameState.players[1].name} has {gameState.players[1].pits.reduce((sum, current) => {return (sum + current.nrOfStones)}, 0)} stones.
+ {gameState.players[1].hasTurn ? " Also has the current turn" : ""}
+ </div>
+ <div id="board">
+ {playerKalahas[1]}
+ <div id="pits">
+ {player2Pits.reverse()}
+ {player1Pits}
+ </div>
+ {playerKalahas[0]}
+ </div>
+ <div className="playerStatus" id="statusPlayer1">
+ {gameState.players[0].name} has {gameState.players[0].pits.reduce((sum, current) => {return (sum + current.nrOfStones)}, 0)} stones.
+ {gameState.players[0].hasTurn ? "Also has the current turn" : ""}
+ </div>
+ {gameState.gameStatus.endOfGame ? revenge : ""}
</div>
)
}
diff --git a/client/src/Mancala/StartGame.tsx b/client/src/Mancala/StartGame.tsx
index 9d5be2c..4cf8d94 100644
--- a/client/src/Mancala/StartGame.tsx
+++ b/client/src/Mancala/StartGame.tsx
@@ -1,15 +1,17 @@
import React, { useState } from "react";
import type { GameState } from "../gameState";
import "./StartGame.css";
+import { Play } from "./Play"
type StartGameProps = {
+ gameState: GameState | undefined;
setGameState(newGameState: GameState): void;
}
/**
* Allows the players to enter their name. A name is required for both players. They can't have the same names.
*/
-export function StartGame({ setGameState }: StartGameProps) {
+export function StartGame({gameState, setGameState }: StartGameProps) {
const [errorMessage, setErrorMessage] = useState("");
const [playerOne, setPlayerOne] = useState("");
@@ -52,23 +54,30 @@ export function StartGame({ setGameState }: StartGameProps) {
}
}
- return (
- <form onSubmit={(e) => tryStartGame(e)}>
- <input value={playerOne}
- placeholder="Player 1 name"
- onChange={(e) => setPlayerOne(e.target.value)}
- />
+ if (localStorage.getItem("state") !== null) {
+ var state = localStorage.getItem("state");
+ const gameState = JSON.parse(state as string);
+ setGameState(gameState);
+ return <Play gameState={gameState} setGameState={setGameState} />
+ } else {
+ return (
+ <form onSubmit={(e) => tryStartGame(e)}>
+ <input value={playerOne}
+ placeholder="Player 1 name"
+ onChange={(e) => setPlayerOne(e.target.value)}
+ />
- <input value={playerTwo}
- placeholder="Player 2 name"
- onChange={(e) => setPlayerTwo(e.target.value)}
- />
+ <input value={playerTwo}
+ placeholder="Player 2 name"
+ onChange={(e) => setPlayerTwo(e.target.value)}
+ />
- <p className="errorMessage">{errorMessage}</p>
+ <p className="errorMessage">{errorMessage}</p>
- <button className="startGameButton" type="submit">
- Play Mancala!
- </button>
- </form>
- )
-} \ No newline at end of file
+ <button className="startGameButton" type="submit">
+ Play Mancala!
+ </button>
+ </form>
+ )
+ }
+}