diff --git a/package.json b/package.json
index aabd4d8..eea55a4 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "life-trinket",
"private": true,
- "version": "1.0.1",
+ "version": "1.0.2",
"type": "commonjs",
"engines": {
"node": ">=20",
diff --git a/src/Components/Dialogs/SettingsDialog.tsx b/src/Components/Dialogs/SettingsDialog.tsx
index 0c369eb..6838adb 100644
--- a/src/Components/Dialogs/SettingsDialog.tsx
+++ b/src/Components/Dialogs/SettingsDialog.tsx
@@ -242,6 +242,23 @@ export const SettingsDialog = ({
+
+
+
+ {
+ setSettings({
+ ...settings,
+ showMatchScore: !settings.showMatchScore,
+ });
+ }}
+ />
+
+
+ Shows a score badge on each player's card to track wins across multiple games.
+
+
+
+
@@ -559,6 +592,48 @@ const PlayerMenu = ({
+
+
);
diff --git a/src/Components/Players/Players.tsx b/src/Components/Players/Players.tsx
index 262828e..4db2c26 100644
--- a/src/Components/Players/Players.tsx
+++ b/src/Components/Players/Players.tsx
@@ -31,7 +31,7 @@ const PlayersWrapper = twc.div`w-full h-full bg-black`;
export const Players = ({ gridLayout }: { gridLayout: GridLayout }) => {
const { players } = usePlayers();
- const { playing, settings, preStartCompleted } = useGlobalSettings();
+ const { playing, settings, preStartCompleted, gameScore } = useGlobalSettings();
return (
@@ -48,6 +48,11 @@ export const Players = ({ gridLayout }: { gridLayout: GridLayout }) => {
opponents={players.filter(
(opponent) => opponent.index !== player.index
)}
+ matchScore={
+ settings.showMatchScore
+ ? gameScore[player.index]
+ : undefined
+ }
/>
{settings.preStartMode === PreStartMode.RandomKing &&
diff --git a/src/Components/ScoreDisplay/ScoreDisplay.tsx b/src/Components/ScoreDisplay/ScoreDisplay.tsx
new file mode 100644
index 0000000..e42f8e4
--- /dev/null
+++ b/src/Components/ScoreDisplay/ScoreDisplay.tsx
@@ -0,0 +1,71 @@
+import { twc } from 'react-twc';
+import { Player } from '../../Types/Player';
+import { GameScore } from '../../Contexts/GlobalSettingsContext';
+
+const ScoreContainer = twc.div`
+ absolute bottom-4 left-1/2 -translate-x-1/2
+ bg-background-default/90 backdrop-blur-sm
+ rounded-lg p-4
+ shadow-lg
+ z-40
+ min-w-[200px]
+`;
+
+const Title = twc.h3`
+ text-sm font-semibold text-text-secondary
+ uppercase tracking-wide mb-3
+`;
+
+const ScoreList = twc.div`
+ flex flex-col gap-2
+`;
+
+const ScoreItem = twc.div`
+ flex items-center justify-between gap-4
+`;
+
+const PlayerInfo = twc.div`
+ flex items-center gap-2
+`;
+
+const PlayerColor = twc.div`
+ w-4 h-4 rounded-full
+`;
+
+const PlayerName = twc.span`
+ text-text-primary font-medium
+`;
+
+const Score = twc.span`
+ text-text-primary font-bold text-lg
+`;
+
+type ScoreDisplayProps = {
+ players: Player[];
+ gameScore: GameScore;
+};
+
+export const ScoreDisplay = ({ players, gameScore }: ScoreDisplayProps) => {
+ const hasAnyScore = Object.values(gameScore).some((score) => score > 0);
+
+ if (!hasAnyScore) {
+ return null;
+ }
+
+ return (
+
+ Match Score
+
+ {players.map((player) => (
+
+
+
+ {player.name || `Player ${player.index + 1}`}
+
+ {gameScore[player.index] || 0}
+
+ ))}
+
+
+ );
+};
diff --git a/src/Components/Views/Play.tsx b/src/Components/Views/Play.tsx
index 6b83c97..bd9394a 100644
--- a/src/Components/Views/Play.tsx
+++ b/src/Components/Views/Play.tsx
@@ -1,4 +1,4 @@
-import { useEffect } from 'react';
+import { useEffect, useState } from 'react';
import { twc } from 'react-twc';
import { twGridTemplateAreas } from '../../../tailwind.config';
import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
@@ -6,6 +6,7 @@ import { usePlayers } from '../../Hooks/usePlayers';
import { Orientation, PreStartMode } from '../../Types/Settings';
import { Players } from '../Players/Players';
import { PreStart } from '../PreStartGame/PreStart';
+import { GameOver } from '../GameOver/GameOver';
const MainWrapper = twc.div`w-[100dvmax] h-[100dvmin] overflow-hidden, setPlayers`;
@@ -14,9 +15,10 @@ type GridTemplateAreasKeys = keyof typeof twGridTemplateAreas;
export type GridLayout = `grid-areas-${GridTemplateAreasKeys}`;
export const Play = () => {
- const { players, setPlayers } = usePlayers();
- const { initialGameSettings, playing, settings, preStartCompleted } =
+ const { players, setPlayers, resetCurrentGame, setStartingPlayerIndex } = usePlayers();
+ const { initialGameSettings, playing, settings, preStartCompleted, gameScore, setGameScore } =
useGlobalSettings();
+ const [winner, setWinner] = useState(null);
let gridLayout: GridLayout;
switch (players.length) {
@@ -94,6 +96,57 @@ export const Play = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+ // Check for game over when only one player remains
+ useEffect(() => {
+ if (players.length < 2 || winner !== null || !settings.showMatchScore) {
+ return;
+ }
+
+ const activePlayers = players.filter((p) => !p.hasLost);
+
+ // If only one player is alive, they are the winner
+ if (activePlayers.length === 1) {
+ setWinner(activePlayers[0].index);
+ }
+ }, [players, winner, settings.showMatchScore]);
+
+ const handleStartNextGame = () => {
+ if (winner === null) return;
+
+ // Update score
+ const newScore = { ...gameScore };
+ newScore[winner] = (newScore[winner] || 0) + 1;
+ setGameScore(newScore);
+
+ // Set the loser as the starting player for next game
+ const loserIndex = players.find((p) => p.index !== winner)?.index ?? 0;
+ setStartingPlayerIndex(loserIndex);
+
+ // Reset game
+ resetCurrentGame();
+ setWinner(null);
+ };
+
+ const handleStay = () => {
+ if (winner === null) return;
+
+ // Update score
+ const newScore = { ...gameScore };
+ newScore[winner] = (newScore[winner] || 0) + 1;
+ setGameScore(newScore);
+
+ // Reset hasLost state for all players
+ setPlayers(
+ players.map((p) => ({
+ ...p,
+ hasLost: false,
+ }))
+ );
+
+ // Clear winner to allow new game over detection
+ setWinner(null);
+ };
+
return (
{players.length > 1 &&
@@ -103,6 +156,14 @@ export const Play = () => {
settings.showStartingPlayer && }
+
+ {winner !== null && (
+
+ )}
);
};
diff --git a/src/Components/Views/StartMenu/StartMenu.tsx b/src/Components/Views/StartMenu/StartMenu.tsx
index d13f6cb..b820544 100644
--- a/src/Components/Views/StartMenu/StartMenu.tsx
+++ b/src/Components/Views/StartMenu/StartMenu.tsx
@@ -64,6 +64,7 @@ const Start = () => {
setPlaying,
savedGame,
saveCurrentGame,
+ setGameScore,
} = useGlobalSettings();
const infoDialogRef = useRef(null);
@@ -213,6 +214,9 @@ const Start = () => {
setInitialGameSettings(savedGame.initialGameSettings);
setPlayers(savedGame.players);
+ if (savedGame.gameScore) {
+ setGameScore(savedGame.gameScore);
+ }
saveCurrentGame(null);
setRandomizingPlayer(false);
setShowPlay(true);
@@ -407,15 +411,31 @@ const Start = () => {
{savedGame && (
)}
diff --git a/src/Contexts/GlobalSettingsContext.tsx b/src/Contexts/GlobalSettingsContext.tsx
index 252ed9d..813db6b 100644
--- a/src/Contexts/GlobalSettingsContext.tsx
+++ b/src/Contexts/GlobalSettingsContext.tsx
@@ -12,8 +12,13 @@ type Version = {
export type SavedGame = {
initialGameSettings: InitialGameSettings;
players: Player[];
+ gameScore?: GameScore;
} | null;
+export type GameScore = {
+ [playerIndex: number]: number;
+};
+
export type GlobalSettingsContextType = {
fullscreen: {
isFullscreen: boolean;
@@ -45,6 +50,9 @@ export type GlobalSettingsContextType = {
version: Version;
savedGame: SavedGame;
saveCurrentGame: (currentGame: SavedGame) => void;
+ gameScore: GameScore;
+ setGameScore: (score: GameScore) => void;
+ resetGameScore: () => void;
};
export const GlobalSettingsContext =
diff --git a/src/Providers/GlobalSettingsProvider.tsx b/src/Providers/GlobalSettingsProvider.tsx
index 8ed1731..1c306f4 100644
--- a/src/Providers/GlobalSettingsProvider.tsx
+++ b/src/Providers/GlobalSettingsProvider.tsx
@@ -1,6 +1,7 @@
import { ReactNode, useEffect, useMemo, useState } from 'react';
import { useWakeLock } from 'react-screen-wake-lock';
import {
+ GameScore,
GlobalSettingsContext,
GlobalSettingsContextType,
SavedGame,
@@ -94,6 +95,19 @@ export const GlobalSettingsProvider = ({
);
};
+ const savedGameScore = localStorage.getItem('gameScore');
+ const [gameScore, setGameScore] = useState(
+ savedGameScore ? JSON.parse(savedGameScore) : {}
+ );
+ const setGameScoreAndLocalStorage = (score: GameScore) => {
+ setGameScore(score);
+ localStorage.setItem('gameScore', JSON.stringify(score));
+ };
+ const resetGameScore = () => {
+ setGameScore({});
+ localStorage.removeItem('gameScore');
+ };
+
// Set settings if they are not valid
useEffect(() => {
// If there are no saved settings, set default settings
@@ -171,11 +185,13 @@ export const GlobalSettingsProvider = ({
localStorage.removeItem('playing');
localStorage.removeItem('showPlay');
localStorage.removeItem('preStartComplete');
+ localStorage.removeItem('gameScore');
setPlaying(false);
setShowPlay(false);
setPreStartCompleted(false);
setSettings({ ...settings, useMonarch: false });
+ setGameScore({});
};
const goToStart = async () => {
@@ -299,6 +315,9 @@ export const GlobalSettingsProvider = ({
isLatest: isLatestVersion,
checkForNewVersion,
},
+ gameScore,
+ setGameScore: setGameScoreAndLocalStorage,
+ resetGameScore,
};
}, [
isFullscreen,
@@ -317,6 +336,7 @@ export const GlobalSettingsProvider = ({
remoteVersion,
isLatestVersion,
analytics,
+ gameScore,
]);
return (
diff --git a/src/Providers/PlayersProvider.tsx b/src/Providers/PlayersProvider.tsx
index b5048ae..03ae820 100644
--- a/src/Providers/PlayersProvider.tsx
+++ b/src/Providers/PlayersProvider.tsx
@@ -61,7 +61,11 @@ export const PlayersProvider = ({ children }: { children: ReactNode }) => {
return;
}
- const newStartingPlayerIndex = Math.floor(Math.random() * players.length);
+ // Use the saved starting player index if available, otherwise random
+ const newStartingPlayerIndex =
+ startingPlayerIndex >= 0
+ ? startingPlayerIndex
+ : Math.floor(Math.random() * players.length);
players.forEach((player: Player) => {
player.commanderDamage.map((damage) => {
diff --git a/src/Types/Settings.ts b/src/Types/Settings.ts
index 63ffb93..a5486d2 100644
--- a/src/Types/Settings.ts
+++ b/src/Types/Settings.ts
@@ -27,6 +27,7 @@ export type Settings = {
preStartMode: PreStartMode;
showAnimations: boolean;
useMonarch: boolean;
+ showMatchScore: boolean;
};
export type InitialGameSettings = {
@@ -61,6 +62,7 @@ export const settingsSchema = z.object({
preStartMode: z.nativeEnum(PreStartMode),
showAnimations: z.boolean(),
useMonarch: z.boolean().default(false),
+ showMatchScore: z.boolean().default(true),
});
export const defaultSettings: Settings = {
@@ -71,4 +73,5 @@ export const defaultSettings: Settings = {
preStartMode: PreStartMode.None,
showAnimations: true,
useMonarch: false,
+ showMatchScore: true,
};