Skip to content

Commit

Permalink
feat: update
Browse files Browse the repository at this point in the history
  • Loading branch information
ngolapnguyen committed Nov 27, 2022
1 parent 05c88b7 commit e538688
Show file tree
Hide file tree
Showing 15 changed files with 463 additions and 69 deletions.
1 change: 1 addition & 0 deletions assets/lottie/win.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"v":"5.6.6","ip":0,"op":160,"fr":60,"w":434,"h":118,"layers":[{"ind":1945,"nm":"surface11316","ao":0,"ip":0,"op":264,"st":0,"ty":4,"ks":{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[133.33,133.33]},"sk":{"k":0},"sa":{"k":0}},"shapes":[{"ty":"gr","hd":false,"nm":"surface11316","it":[{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[2.01,-0.89],[1.44,-1.7],[0.85,-2.71],[0,-3.4],[-0.86,-2.76],[-1.53,-1.77],[-1.96,-0.87],[-2.39,0],[-2.53,1.51],[-1.53,3.21],[0,4.77],[1.4,3.18],[2.58,1.61],[3.33,0]],"o":[[-2.36,0],[-2,0.89],[-1.45,1.7],[-0.86,2.71],[0,3.43],[0.85,2.75],[1.53,1.77],[1.97,0.87],[3.06,0],[2.54,-1.52],[1.53,-3.21],[0,-4.4],[-1.4,-3.18],[-2.58,-1.62],[0,0]],"v":[[456.99,215.73],[450.44,217.06],[445.28,220.94],[441.82,227.56],[440.54,236.72],[441.82,246.01],[445.4,252.79],[450.64,256.74],[457.16,258.05],[465.55,255.79],[471.64,248.69],[473.92,236.72],[471.82,225.35],[465.85,218.16],[456.99,215.73]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[-0.91,-0.42],[-0.67,-0.68],[-0.7,-1.43],[-0.66,-0.32],[0,0],[0,0],[-1.18,1.38],[-2,0],[-1.2,-1.39],[0,-2.58],[0,0],[5.09,0],[0.98,0.35],[0.86,0.78],[0.61,0.85],[0.77,1.11],[0,0],[0,0],[1.26,-1.39],[2,0],[1.22,1.39],[0,2.57],[0,0],[-0.45,1.08],[-1.39,0.9],[-1.6,0]],"o":[[1.25,0],[0.91,0.41],[0.67,0.68],[0.69,1.43],[0,0],[0,0],[0,-2.6],[1.18,-1.38],[2.06,0],[1.2,1.38],[0,0],[0,5.99],[-1.24,0],[-0.98,-0.35],[-0.86,-0.78],[-0.61,-0.85],[0,0],[0,0],[0,2.57],[-1.26,1.39],[-2.04,0],[-1.22,-1.4],[0,0],[0,-2.14],[0.65,-1.6],[1.39,-0.9],[0,0]],"v":[[706.35,204.08],[709.59,204.71],[711.96,206.35],[714.01,209.53],[716.04,212.16],[738.36,248],[738.36,212.11],[740.14,206.15],[744.9,204.08],[749.8,206.16],[751.6,212.11],[751.6,260.7],[743.97,269.7],[740.63,269.17],[737.85,267.47],[735.64,265.03],[733.58,262.08],[711.64,226.34],[711.64,261.68],[709.76,267.62],[704.87,269.7],[699.97,267.61],[698.15,261.67],[698.15,214.01],[698.82,209.17],[701.88,205.43],[706.35,204.08]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[-1.33,-1.44],[0,-2.76],[0,0],[1.33,-1.44],[2.16,0],[1.32,1.45],[0,2.77],[0,0],[-1.31,1.44],[-2.08,0]],"o":[[2.14,0],[1.34,1.43],[0,0],[0,2.78],[-1.33,1.44],[-2.06,0],[-1.32,-1.44],[0,0],[0,-2.76],[1.31,-1.44],[0,0]],"v":[[677.54,204.08],[682.75,206.24],[684.76,212.53],[684.76,261.21],[682.77,267.54],[677.54,269.7],[672.47,267.53],[670.49,261.21],[670.49,212.53],[672.45,206.24],[677.54,204.08]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[-0.89,-1.65],[-0.57,-2.7],[0,0],[0,0],[-0.56,1.42],[-1.34,1.09],[-2.28,0],[-1.3,-1.15],[-0.48,-1.3],[-0.76,-2.79],[0,0],[0,0],[-0.38,1.16],[-0.99,0.98],[-1.85,0],[-1.32,-1.26],[0,-1.86],[0.64,-2.6],[0,0],[0.49,-1.39],[1.26,-1.11],[2.26,0],[1.29,1.06],[0.48,1.29],[0.76,2.67],[0,0],[0,0],[0.5,-1.32],[1.29,-1.09],[2.14,0],[1.12,0.65],[0.67,1.14],[0.42,1.65],[0.3,0.93],[0,0],[0,1.37],[-1.3,1.27],[-1.89,0]],"o":[[2.64,0],[0.89,1.65],[0,0],[0,0],[0.69,-2.64],[0.56,-1.42],[1.35,-1.1],[2.34,0],[1.3,1.14],[0.48,1.29],[0,0],[0,0],[0.37,-1.76],[0.39,-1.16],[0.99,-0.97],[1.88,0],[1.32,1.25],[0,1.22],[0,0],[-0.7,2.81],[-0.49,1.39],[-1.26,1.11],[-2.12,0],[-1.29,-1.07],[-0.49,-1.3],[0,0],[0,0],[-0.69,2.47],[-0.5,1.32],[-1.29,1.09],[-1.71,0],[-1.12,-0.65],[-0.67,-1.15],[-0.42,-1.65],[0,0],[-0.62,-2.45],[0,-1.84],[1.3,-1.27],[0,0]],"v":[[588.41,204.08],[593.7,206.56],[595.89,213.08],[603.24,248.69],[611.38,215.59],[613.26,209.51],[616.12,205.73],[621.56,204.08],[627.03,205.8],[629.7,209.46],[631.57,215.59],[639.78,248.69],[647.13,213.14],[648.26,208.76],[650.32,205.55],[654.59,204.08],[659.38,205.96],[661.35,210.64],[660.4,216.37],[650.12,257.97],[648.32,264.28],[645.7,268.03],[640.44,269.7],[635.32,268.1],[632.67,264.56],[630.81,258.61],[621.57,221.88],[612.18,258.77],[610.39,264.46],[607.7,268.07],[602.56,269.7],[598.3,268.72],[595.61,266.04],[593.98,261.84],[592.91,257.97],[582.63,216.37],[581.69,210.64],[583.64,205.98],[588.41,204.08]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[-1.31,-1.45],[0,-2.72],[0,0],[-0.73,-2.17],[-1.8,-1.17],[-3.37,0],[-1.88,2.41],[0,5.08],[0,0],[-1.28,1.43],[-2.14,0],[-1.33,-1.44],[0,-2.76],[0,0],[0.97,-3.35],[3.4,-2.72],[3.1,-0.99],[4.08,0],[3.53,1.05],[2.27,2.25],[1.04,3.46],[0,4.77],[0,0],[-1.3,1.44],[-2.1,0]],"o":[[2.2,0],[1.31,1.45],[0,0],[0,3.39],[0.72,2.18],[1.8,1.17],[4.61,0],[1.88,-2.41],[0,0],[0,-2.77],[1.28,-1.43],[2.14,0],[1.34,1.43],[0,0],[0,4.9],[-0.97,3.35],[-1.66,2],[-3.09,0.99],[-4.86,0],[-3.53,-1.06],[-2.27,-2.25],[-1.04,-3.46],[0,0],[0,-2.74],[1.3,-1.44],[0,0]],"v":[[506.11,204.08],[511.37,206.26],[513.33,212.53],[513.33,242.93],[514.42,251.28],[518.2,256.3],[525.96,258.05],[535.69,254.43],[538.51,243.19],[538.51,212.53],[540.43,206.23],[545.56,204.08],[550.77,206.24],[552.77,212.53],[552.77,242.25],[551.31,254.62],[544.75,263.72],[537.61,268.21],[526.85,269.7],[514.27,268.11],[505.58,263.16],[500.62,254.59],[499.06,242.24],[499.06,212.53],[501.01,206.25],[506.11,204.08]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[-4.75,-2.69],[-2.43,-4.93],[0,-6.58],[1.32,-4.01],[2.67,-2.96],[3.88,-1.57],[4.98,0],[3.92,1.6],[2.65,2.94],[1.34,4.09],[0,4.73],[-1.39,4.05],[-2.67,2.88],[-3.82,1.52],[-4.82,0]],"o":[[6.55,0],[4.75,2.69],[2.43,4.93],[0,4.85],[-1.33,4.01],[-2.67,2.96],[-3.88,1.56],[-4.97,0],[-3.92,-1.6],[-2.64,-2.93],[-1.34,-4.09],[0,-4.85],[1.4,-4.06],[2.67,-2.88],[3.82,-1.52],[0,0]],"v":[[456.99,204.08],[473.95,208.11],[484.72,219.54],[488.36,236.81],[486.37,250.1],[480.37,260.56],[470.55,267.35],[457.25,269.7],[443.92,267.3],[434.08,260.49],[428.1,249.96],[426.1,236.72],[428.19,223.36],[434.29,212.96],[444.03,206.36],[456.99,204.08]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[-0.99,-1.08],[-1.8,-2.94],[0,0],[0,0],[-0.5,0.82],[-0.61,0.73],[-0.83,0.43],[-1.14,0],[-1.23,-1.2],[0,-1.57],[0.57,-1.17],[1.57,-2.23],[0,0],[0,0],[1.34,-1.44],[2.09,0],[1.31,1.42],[0,2.82],[0,0],[0,0],[0.57,1.24],[0,1.02],[-1.28,1.2],[-1.85,0]],"o":[[1.98,0],[0.98,1.08],[0,0],[0,0],[0.7,-1.15],[0.5,-0.82],[0.6,-0.72],[0.83,-0.43],[1.81,0],[1.23,1.2],[0,1.22],[-0.57,1.18],[0,0],[0,0],[0,2.77],[-1.35,1.44],[-2.12,0],[-1.3,-1.42],[0,0],[0,0],[-1.27,-1.98],[-0.58,-1.24],[0,-1.68],[1.29,-1.2],[0,0]],"v":[[376.69,204.08],[381.15,205.71],[385.33,211.74],[396.12,230.08],[407.04,211.74],[408.84,208.79],[410.5,206.47],[412.66,204.73],[415.61,204.08],[420.18,205.89],[422.04,210.04],[421.18,213.64],[417.96,218.74],[403.04,241.84],[403.04,261.21],[401.02,267.53],[395.86,269.7],[390.72,267.57],[388.77,261.21],[388.77,241.88],[373.69,218.42],[370.93,213.59],[370.06,210.21],[371.99,205.89],[376.69,204.08]],"c":true}}},{"ty":"st","lc":1,"lj":2,"ml":10,"o":{"k":100},"w":{"k":2},"c":{"k":[0.93,0.49,0.19,1]},"hd":false},{"ty":"fl","o":{"a":1,"k":[{"i":{"x":[0.67],"y":[1]},"o":{"x":[0.33],"y":[0]},"t":59,"s":[0]},{"t":109,"s":[100]}],"ix":5},"c":{"k":[0.97,0.8,0.68,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[-259.5,-132.75]},"a":{"k":[0,0]},"s":{"k":[75,75]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.27],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":0,"s":[0]},{"t":66,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"hd":false}]}],"meta":{"g":"LF SVG to Lottie"}}
16 changes: 14 additions & 2 deletions components/LeftCorner.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import Image from "next/image";
import { useGameContext } from "../contexts/game";
import useCountdown from "../hooks/useCountdown";

const RoundCountdown = () => {
const { gameState } = useGameContext();
const { seconds } = useCountdown(new Date(gameState!.round_expire_at));

return <>{seconds}s</>;
};

export const LeftCorner = () => {
const { currentPlayer, isLoadingGameState } = useGameContext();
const { gameState, currentPlayer, isLoadingGameState } = useGameContext();

return (
<div className="left-corner">
<div className="points">Points: {currentPlayer?.points || 0}</div>
<div>Game ID: {gameState?.id}</div>
<div>Game Status: {gameState?.status}</div>
<div className="rounds">
<span>Next round in: 30s</span>
<span>
Next round in: {gameState?.round_expire_at ? <RoundCountdown /> : "-"}
</span>
{isLoadingGameState && (
<Image
src="/assets/images/spinner.svg"
Expand Down
35 changes: 31 additions & 4 deletions components/Logs.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
import { useState } from "react";
import { useMemo, useState } from "react";
import { useGameContext } from "../contexts/game";

export const Logs = () => {
const { gameState, currentPlayer } = useGameContext();
const [isLogsExpanded, setIsLogsExpanded] = useState(false);

const goalLocation = useMemo(() => {
let goalCol = -1;
let goalRow = -1;

if (gameState) {
gameState.map.forEach((row, rowIndex) => {
row.forEach((col, colIndex) => {
if (col === "X") {
goalCol = colIndex + 1;
goalRow = rowIndex + 1;
}
});
});
}

return {
col: goalCol,
row: goalRow,
};
}, [gameState]);

return (
<div className={["logs", isLogsExpanded ? "expanded" : ""].join(" ")}>
<div className="header">Logs</div>
<div className="content">
{gameState &&
currentPlayer &&
{gameState && currentPlayer && gameState.history.length > 0 ? (
gameState.history.map((roundHistory, index) => {
const playerHistory = roundHistory[currentPlayer.id];

Expand All @@ -25,9 +45,16 @@ export const Logs = () => {
You got {playerHistory.item.value} points!
</div>
)}
{currentPlayer.location.row === goalLocation.row &&
currentPlayer.location.col === goalLocation.col && (
<div className="success">You reached the goal!</div>
)}
</div>
);
})}
})
) : (
<div className="log-item">No logs.</div>
)}
</div>
<button
type="button"
Expand Down
156 changes: 121 additions & 35 deletions components/Map.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,42 @@
import Lottie from "lottie-react";
import Image from "next/image";
import { useMemo } from "react";
import { useEffect, useMemo, useRef } from "react";
import arrowAnimation from "../assets/lottie/arrow.json";
import explosionAnimation from "../assets/lottie/explosion.json";
import { MOVE_OFFSET } from "../constants/move";
import { useGameContext } from "../contexts/game";
import { GameStatus } from "../types/game";
import { dist } from "../utils/points";
import { WinScreen } from "./WinScreen";

const ISLAND_SIZE = 16;
const PLAYGROUND_SIZE = 12;
const GRID_ITEM_SIZE = 48;

export const Map = () => {
const { gameState, currentPlayer, nextMove } = useGameContext();
const playerFacingDirections = useRef<Record<string, string>>({});

const goalLocation = useMemo(() => {
let goalCol = -1;
let goalRow = -1;

if (gameState) {
gameState.map.forEach((row, rowIndex) => {
row.forEach((col, colIndex) => {
if (col === "X") {
goalCol = colIndex + 1;
goalRow = rowIndex + 1;
}
});
});
}

return {
col: goalCol,
row: goalRow,
};
}, [gameState]);

const nextMoveLocation = useMemo(() => {
if (!currentPlayer || !nextMove) {
Expand All @@ -24,6 +49,50 @@ export const Map = () => {
};
}, [currentPlayer, nextMove]);

const winner = useMemo(() => {
if (gameState?.status === GameStatus.COMPLETED) {
let winner = gameState.players[0];

gameState.players.forEach((player) => {
// Update winner if this player has higher points than the last ones
if (player.points > winner.points) {
winner = player;
}

// If points are equal, check which one is nearer to the goal
if (
// player.points === winner.points &&
dist(
[goalLocation.col!, goalLocation.row!],
[player.location.col, player.location.row]
) <
dist(
[goalLocation.col!, goalLocation.row!],
[winner.location.col, winner.location.row]
)
) {
winner = player;
}
});

return winner;
}
}, [gameState, goalLocation]);

useEffect(() => {
if (gameState?.id && gameState?.status === GameStatus.NEW) {
playerFacingDirections.current = gameState.players.reduce(
(result, current, index) => {
return {
...result,
[current.id]: index % 2 === 1 ? "face-left" : "face-right",
};
},
{}
);
}
}, [gameState?.id]); // eslint-disable-line

return (
<>
<div className="island">
Expand All @@ -43,38 +112,55 @@ export const Map = () => {
const isNext =
currentPlayer &&
nextMoveLocation &&
colIndex === nextMoveLocation.col &&
rowIndex === nextMoveLocation.row;
colIndex === nextMoveLocation.col - 1 &&
rowIndex === nextMoveLocation.row - 1;

const cellValue = gameState?.map[rowIndex][colIndex];
const isGoal = cellValue === "X";

return (
<div
className={["col", isNext ? "next" : ""].join(" ")}
key={colIndex}
>
{isGoal ? (
<Image
width={64}
height={64}
src="/assets/images/portal.webp"
alt="goal"
className="goal"
/>
) : (
cellValue
)}
<span className="cell-value">{cellValue}</span>
</div>
);
})}
</div>
))}
</div>
{goalLocation && (
<div
className="goal"
style={{
left: (goalLocation.col - 1) * GRID_ITEM_SIZE,
top: (goalLocation.row - 1) * GRID_ITEM_SIZE,
}}
>
<Image
width={64}
height={64}
src="/assets/images/portal.webp"
alt="goal"
className="goal-image"
/>
</div>
)}
{nextMoveLocation && (
<div
className="arrow"
style={{
left: (nextMoveLocation.col - 1) * GRID_ITEM_SIZE,
top: (nextMoveLocation.row - 1) * GRID_ITEM_SIZE,
}}
>
<Lottie animationData={arrowAnimation} />
</div>
)}
{gameState &&
// Find the players hit by bomb in prev round
Object.keys(gameState.prev_round).map((playerId) => {
const wasHit = gameState.prev_round[playerId].got_boom;
const wasHit = gameState.prev_round[playerId]?.got_boom;
const playerLocation = gameState.players.find(
(player) => player.id === playerId
)?.location;
Expand All @@ -99,42 +185,42 @@ export const Map = () => {
<div className="players">
{gameState?.players.map((player, index) => {
// Was player hit by bomb in prev round?
const wasHit = gameState.prev_round[player.id].got_boom;
const wasHit = gameState.prev_round[player.id]?.got_boom;

// Decide which direction the avatar is facing
let direction = "";
if (gameState.prev_round[player.id].action_result === "left") {
direction = "face-left";
if (gameState.prev_round[player.id]?.action_result === "left") {
playerFacingDirections.current[player.id] = "face-left";
} else if (
gameState.prev_round[player.id]?.action_result === "right"
) {
playerFacingDirections.current[player.id] = "face-right";
}

return (
<div
className={["player", wasHit ? "hit" : "idle", direction].join(
" "
)}
className={[
"player",
wasHit ? "hit" : "idle",
playerFacingDirections.current[player.id],
].join(" ")}
key={player.id}
style={{
left: (player.location.col - 1) * GRID_ITEM_SIZE,
top: (player.location.row - 1) * GRID_ITEM_SIZE,
}}
>
<div className="nametag">Player {index}</div>
<div className="nametag">
{player.id === currentPlayer?.id ? "You" : `Player ${index}`}
</div>
{winner?.id === player.id && (
<div className="winner-tag">🎉Winner🎉</div>
)}
</div>
);
})}
</div>
{nextMoveLocation && (
<div
className="arrow"
style={{
left: nextMoveLocation.col * GRID_ITEM_SIZE,
top: nextMoveLocation.row * GRID_ITEM_SIZE,
}}
>
<Lottie animationData={arrowAnimation} />
</div>
)}
</div>
{winner && winner.id === currentPlayer?.id && <WinScreen />}
</>
);
};
38 changes: 32 additions & 6 deletions components/MovementControls.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,52 @@
import { useGameContext } from "../contexts/game";
import { client } from "../libs/apis";

export const MovementControls = () => {
const { setNextMove } = useGameContext();
const { gameState, nextMove, currentPlayer, setNextMove } = useGameContext();

const onMove = async (move: any) => {
if (!gameState || !currentPlayer) {
return;
}

const onMove = (move: any) => {
if (window.confirm(`Are you sure you want to move ${move}?`)) {
setNextMove(move);
await client.submitStep(gameState?.id, currentPlayer?.token, move);
}
};

return (
<div className="movement-controls">
<button type="button" className="up" onClick={() => onMove("up")}>
<button
type="button"
className="up"
onClick={() => onMove("up")}
disabled={Boolean(nextMove)}
>
Up
</button>
<button type="button" className="down" onClick={() => onMove("down")}>
<button
type="button"
className="down"
onClick={() => onMove("down")}
disabled={Boolean(nextMove)}
>
Down
</button>
<button type="button" className="left" onClick={() => onMove("left")}>
<button
type="button"
className="left"
onClick={() => onMove("left")}
disabled={Boolean(nextMove)}
>
Left
</button>
<button type="button" className="right" onClick={() => onMove("right")}>
<button
type="button"
className="right"
onClick={() => onMove("right")}
disabled={Boolean(nextMove)}
>
Right
</button>
</div>
Expand Down
Loading

0 comments on commit e538688

Please sign in to comment.