diff --git a/public/assets/sign-content/dontplaygames-socialstrike.png b/public/assets/sign-content/dontplaygames-socialstrike.png new file mode 100644 index 0000000..50c37ec Binary files /dev/null and b/public/assets/sign-content/dontplaygames-socialstrike.png differ diff --git a/public/assets/sign-content/moo-deng.png b/public/assets/sign-content/moo-deng.png new file mode 100644 index 0000000..265dab5 Binary files /dev/null and b/public/assets/sign-content/moo-deng.png differ diff --git a/public/assets/sign-content/ready-to-strike_text-only-avatar.png b/public/assets/sign-content/ready-to-strike_text-only-avatar.png new file mode 100644 index 0000000..1c40c61 Binary files /dev/null and b/public/assets/sign-content/ready-to-strike_text-only-avatar.png differ diff --git a/public/assets/sign-content/scabby.jpg b/public/assets/sign-content/scabby.jpg new file mode 100644 index 0000000..ce5be39 Binary files /dev/null and b/public/assets/sign-content/scabby.jpg differ diff --git a/public/assets/sign-content/stand-with-guild-avatar.png b/public/assets/sign-content/stand-with-guild-avatar.png new file mode 100644 index 0000000..96d1151 Binary files /dev/null and b/public/assets/sign-content/stand-with-guild-avatar.png differ diff --git a/public/assets/sign-content/tech-support-avatar.png b/public/assets/sign-content/tech-support-avatar.png new file mode 100644 index 0000000..556c8ff Binary files /dev/null and b/public/assets/sign-content/tech-support-avatar.png differ diff --git a/public/assets/sign-content/tick-tock-avatar.png b/public/assets/sign-content/tick-tock-avatar.png new file mode 100644 index 0000000..b3b4588 Binary files /dev/null and b/public/assets/sign-content/tick-tock-avatar.png differ diff --git a/public/assets/sign-content/union-fist.png b/public/assets/sign-content/union-fist.png new file mode 100644 index 0000000..18ae552 Binary files /dev/null and b/public/assets/sign-content/union-fist.png differ diff --git a/src/App.tsx b/src/App.tsx index 45272a6..fadb554 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import './App.css'; import { Header } from './components/Header'; import { InfoDialog } from './components/InfoDialog'; import { PicketSign } from './components/PicketSign'; +import { GameSettings } from './components/GameSettings'; import { ResultsDialog } from './components/ResultsDialog'; import { Scoreboard } from './components/Scoreboard'; import { Game, NewGame } from './game'; @@ -10,6 +11,7 @@ import { Game, NewGame } from './game'; function App() { const [game, setGame] = useState(NewGame()); const [duration, setDuration] = useState('0m 0s'); + const [gameSettingsOpen, setGameSettingsOpen] = useState(false); const [showDialog, setShowDialog] = useState(false); const [infoDialogOpen, setInfoDialogOpen] = useState(false); @@ -58,6 +60,22 @@ function App() { > What’s this? + + {infoDialogOpen && ( + setInfoDialogOpen(false)} /> + )} + setGameSettingsOpen(!gameSettingsOpen)}> + Settings + + {gameSettingsOpen && ( + setGameSettingsOpen(false)} + onSave={(difficulty) => { + //Reset the game with the new difficulty + setGame(game.resetWithDifficulty(difficulty)) + }} + /> + )}
diff --git a/src/card.ts b/src/card.ts index c8a5357..55a206b 100644 --- a/src/card.ts +++ b/src/card.ts @@ -6,7 +6,15 @@ enum SignContent { MakerWeekGuild = 'maker-week-guild.webp', UnionMadeGithub = 'union-made-github.webp', WuerkerCartoon = 'wuerker-cartoon.webp', - ContractCrossword = 'contract-crossword.webp' + ContractCrossword = 'contract-crossword.webp', + DontPlayGames = 'dontplaygames-socialstrike.png', + MooDeng = 'moo-deng.png', + Scabby = 'scabby.jpg', + StandWithGuild = 'stand-with-guild-avatar.png', + TechSupport = 'tech-support-avatar.png', + TickTock = 'tick-tock-avatar.png', + UnionFist = 'union-fist.png', + ReadyToStrike = 'ready-to-strike_text-only-avatar.png', } export interface Card { @@ -17,12 +25,13 @@ export interface Card { count: number; } -const CARD_VALUES = Object.values(SignContent); - -export function getInitialCards(): Card[] { +export function getInitialCards(countCardsInPlay: number): Card[] { const date = new Date().toISOString(); + let cardValues = Object.values(SignContent); + shuffleArray(cardValues); + cardValues = cardValues.slice(0, countCardsInPlay); - const cards = [...CARD_VALUES, ...CARD_VALUES].map((value, index) => ({ + const cards = [...cardValues, ...cardValues].map((value, index) => ({ value, id: date + index, isFaceUp: false, @@ -34,7 +43,7 @@ export function getInitialCards(): Card[] { } // note, sort(() => 0.5 - Math.random()) and similar are biased; see -function shuffleArray(array: Card[]) { +function shuffleArray(array: unknown[]) { for (let i = array.length - 1; i >= 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; diff --git a/src/components/GameSettings/GameSettings.css b/src/components/GameSettings/GameSettings.css new file mode 100644 index 0000000..8a47839 --- /dev/null +++ b/src/components/GameSettings/GameSettings.css @@ -0,0 +1,30 @@ +.settings-dialog { + z-index: 100; + overflow-y: scroll; + border: none; + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + display: flex; + /* justify-content: center; */ + /* align-items: center; */ +} + +.settings-close-button { + position: absolute; + top: 10px; + right: 10px; + height: 1rem; + width: 1rem; + cursor: pointer; +} + +.container { + justify-content: start; + align-items: center; + display: flex; + flex-direction: column; + gap: 1rem; +} diff --git a/src/components/GameSettings/index.tsx b/src/components/GameSettings/index.tsx new file mode 100644 index 0000000..2acc88e --- /dev/null +++ b/src/components/GameSettings/index.tsx @@ -0,0 +1,46 @@ +import { useState } from 'react'; +import { Difficulty } from '../../game'; +import './GameSettings.css'; + +export const GameSettings = ({ + onClose, + onSave, +}: { + onClose: () => void; + onSave: (difficulty: Difficulty) => void; +}) => { + const [selectedDifficulty, setSelectedDifficulty] = useState(Difficulty.EASY); + + const handleChangeDifficulty = (event: React.ChangeEvent) => { + const newValue = event.target.value; + setSelectedDifficulty(newValue as Difficulty); + }; + + return ( + + + ❌ + +
+ + + + + + +
+
+ ); +}; diff --git a/src/game.ts b/src/game.ts index 472d3a7..5ab5c83 100644 --- a/src/game.ts +++ b/src/game.ts @@ -4,6 +4,7 @@ export interface Game { handleClick(card: Card): Game; resetUnmatchedCards(): Game; reset(): Game; + resetWithDifficulty(difficulty: Difficulty): Game; getDuration(): string; getScore(): number; getAttempts(): number; @@ -13,12 +14,35 @@ export interface Game { hasMatchAllCards(): boolean; } +interface DifficultyLevel { + numCards: number, + matchLength: number, +} + +export enum Difficulty { + EASY = 'easy', + MEDIUM = 'medium', + HARD = 'hard' +} + +function getDifficultySpec(difficulty: Difficulty): DifficultyLevel { + switch (difficulty) { + case Difficulty.EASY: + return { numCards: 2, matchLength: 2 }; + case Difficulty.MEDIUM: + return { numCards: 4, matchLength: 2 }; + case Difficulty.HARD: + return { numCards: 8, matchLength: 2 }; + } +} + interface GameData { start: Date | null; end: Date | null; score: number; attempts: number; cards: Card[]; + difficulty: Difficulty; } const DefaultGameData = { @@ -26,10 +50,13 @@ const DefaultGameData = { end: null, score: 0, attempts: 0, - cards: getInitialCards() -}; + cards: getInitialCards(getDifficultySpec(Difficulty.HARD).numCards), + difficulty: Difficulty.HARD +} + export function NewGame(game: GameData = DefaultGameData): Game { + function getFaceUpCards(cards = game.cards): Card[] { return cards.filter((c) => c.isFaceUp && !c.isMatched); } @@ -54,8 +81,19 @@ export function NewGame(game: GameData = DefaultGameData): Game { end: null, score: 0, attempts: 0, - cards: getInitialCards() - }); + cards: getInitialCards(getDifficultySpec(game.difficulty).numCards), + difficulty: game.difficulty + }) + }, + resetWithDifficulty(difficulty: Difficulty): Game { + return NewGame({ + start: null, + end: null, + score: 0, + attempts: 0, + cards: getInitialCards(getDifficultySpec(difficulty).numCards), + difficulty: difficulty + }) }, handleClick(card: Card): Game { if (card.isFaceUp || card.isMatched || hasFlippedTwoCards()) { @@ -92,14 +130,12 @@ export function NewGame(game: GameData = DefaultGameData): Game { resetUnmatchedCards(): Game { return NewGame({ ...game, - cards: game.cards.map((c) => - c.isMatched ? c : { ...c, isFaceUp: false } - ) + cards: game.cards.map((c) => c.isMatched ? c : ({ ...c, isFaceUp: false })), }); }, getDuration(): string { if (game.start === null) { - return '0m 0s'; + return "0m 0s"; } let end = new Date();