diff --git a/.vscode/settings.json b/.vscode/settings.json index 28724cb..2d1499f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,7 +11,7 @@ "[typescriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, - "cSpell.words": ["nanostores"], + "cSpell.words": ["nanostores", "Preinitialized"], "editor.codeActionsOnSave": { "source.fixAll.oxc": "explicit" }, diff --git a/src/lobo/deck.ts b/src/lobo/deck.ts index 69e1597..00ad137 100644 --- a/src/lobo/deck.ts +++ b/src/lobo/deck.ts @@ -4,7 +4,7 @@ import { count, range } from '#utils/generators.ts' import type { GameValue } from './card' -type Difficulty = 'hard' | 'ordinary' | 'easy' +export type Difficulty = 'hard' | 'ordinary' | 'easy' export function createLoboDeck(difficulty: Difficulty = 'ordinary'): Deck { const deck: Deck = { cards: [] } diff --git a/src/lobo/library.ts b/src/lobo/library.ts new file mode 100644 index 0000000..471bff3 --- /dev/null +++ b/src/lobo/library.ts @@ -0,0 +1,11 @@ +import { Library } from '#core/library.ts' + +import type { GameValue } from './card' +import { createLoboDeck, type Difficulty } from './deck' + +export type GameLibrary = Library + +export function createLoboLibrary(difficulty: Difficulty = 'ordinary'): GameLibrary { + const loboDeck = createLoboDeck(difficulty) + return new Library(loboDeck.cards) +} diff --git a/src/store/actions.ts b/src/store/actions.ts new file mode 100644 index 0000000..4b0ce81 --- /dev/null +++ b/src/store/actions.ts @@ -0,0 +1,128 @@ +import type { PreinitializedWritableAtom, ReadableAtom } from 'nanostores' + +import { getAttackResult } from '#lobo/attack.ts' +import { cardSum, type GameCard } from '#lobo/card.ts' +import type { Difficulty } from '#lobo/deck.ts' +import { createLoboLibrary } from '#lobo/library.ts' +import type { NonEmptyList } from '#types/list.ts' + +import { $isValidAttack, $selectedPlayerCards, $selectedWolfCards, $winner } from './derived' +import { + $playerHand, + $wolfHand, + $library, + $wolfScore, + $playerScore, + $selectedWolfCardIds, + $selectedPlayerCardIds, + $difficulty, +} from './state' + +const STARTING_HAND_SIZE = 4 + +export function startGame(difficulty: Difficulty = 'ordinary') { + $difficulty.set(difficulty) + $wolfScore.set(0) + $playerScore.set(0) + startRound() +} + +/** + * Shuffling the whole deck per round. + * I know that makes the game harder. + * I'll think about having multiple rounds between shuffling in the future. + * + * Also, go drink some water. + */ +export function startRound() { + const winner = $winner.get() + if (winner) { + console.debug(`Winner already determined: ${winner}`) + return + } + + const difficulty = $difficulty.get() + const library = createLoboLibrary(difficulty) + const wolfHand = library.draw(STARTING_HAND_SIZE)! + const playerHand = library.draw(STARTING_HAND_SIZE)! + + $library.set(library) + $selectedWolfCardIds.set([]) + $selectedPlayerCardIds.set([]) + $wolfHand.set(wolfHand) + $playerHand.set(playerHand) +} + +/** + * @internal + */ +function toggleSelectCard($ids: PreinitializedWritableAtom, id: symbol) { + const ids = $ids.get() + if (ids.includes(id)) { + $ids.set(ids.filter((i) => i !== id)) + } else { + $ids.set(ids.concat([id])) + } +} +export function toggleSelectWolfCard(id: symbol) { + toggleSelectCard($selectedWolfCardIds, id) +} +export function toggleSelectPlayerCard(id: symbol) { + toggleSelectCard($selectedPlayerCardIds, id) +} + +/** + * @internal + */ +function addCards($hand: PreinitializedWritableAtom, cards: NonEmptyList) { + $hand.set($hand.get().concat(cards)) +} +/** + * @internal + */ +function removeCards($hand: PreinitializedWritableAtom, $ids: ReadableAtom) { + const ids = $ids.get() + $hand.set($hand.get().filter((gc) => ids.includes(gc.id))) +} +export function attack() { + const library = $library.get() + if (!library || !$isValidAttack.get()) { + return + } + + const wolfCards = $selectedWolfCards.get() + const playerCards = $selectedPlayerCards.get() + + const result = getAttackResult(wolfCards, playerCards) + const newCards = library.draw(result.drawCount)! + + switch (result.drawOwner) { + case 'wolf': + addCards($wolfHand, newCards) + break + + case 'player': + addCards($playerHand, newCards) + break + } + + removeCards($wolfHand, $selectedWolfCardIds) + removeCards($playerHand, $selectedPlayerCardIds) + $selectedWolfCardIds.set([]) + $selectedPlayerCardIds.set([]) +} + +export function endRound() { + const wolfHand = $wolfHand.get() + const playerHand = $playerHand.get() + + if (wolfHand.length > 0) { + const wolfWinnings = cardSum(wolfHand) + $wolfScore.set($wolfScore.get() + wolfWinnings) + } else { + const playerWinnings = cardSum(playerHand) + $playerScore.set($playerScore.get() + playerWinnings) + } + + startRound() +} diff --git a/src/store/derived.ts b/src/store/derived.ts index e5ea6cc..b08698d 100644 --- a/src/store/derived.ts +++ b/src/store/derived.ts @@ -1,8 +1,9 @@ import { computed } from 'nanostores' -import { isValidAttack } from '#lobo/attack.ts' +import { getAttackResult, isValidAttack } from '#lobo/attack.ts' import { + $library, $playerHand, $playerScore, $selectedPlayerCardIds, @@ -19,8 +20,16 @@ export const $selectedPlayerCards = computed([$playerHand, $selectedPlayerCardId hand.filter((c) => ids.includes(c.id)), ) export const $isValidAttack = computed( - [$selectedWolfCards, $selectedPlayerCards], - (wolfCards, playerCards) => isValidAttack(wolfCards, playerCards), + [$selectedWolfCards, $selectedPlayerCards, $library], + (wolfCards, playerCards, library) => { + if (!library || !isValidAttack(wolfCards, playerCards)) { + return false + } + + // Only allow attacks if there are enough cards left to draw + const result = getAttackResult(wolfCards, playerCards) + return result.drawCount <= library.size + }, ) export const $winner = computed( diff --git a/src/store/state.ts b/src/store/state.ts index f703e83..3c2dea7 100644 --- a/src/store/state.ts +++ b/src/store/state.ts @@ -1,8 +1,11 @@ import { atom } from 'nanostores' import type { GameCard } from '#lobo/card.ts' +import type { Difficulty } from '#lobo/deck.ts' +import type { GameLibrary } from '#lobo/library.ts' export const $winningScore = atom<25 | 50 | 100>(50) +export const $difficulty = atom('ordinary') export const $wolfScore = atom(0) export const $playerScore = atom(0) @@ -12,3 +15,5 @@ export const $playerHand = atom([]) export const $selectedWolfCardIds = atom([]) export const $selectedPlayerCardIds = atom([]) + +export const $library = atom(null)