diff --git a/src/App.tsx b/src/App.tsx index 0c8e7e8..e80c59b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,7 +11,7 @@ import { InfoPanel } from './components/InfoPanel' import { Joker } from './components/Joker' import { Round } from './components/Round' import { Shop } from './components/Shop' -import { Blinds } from './Constants' +import { Blinds, DeckType } from './Constants' import { gameReducer, GameStateContext, initialGameState } from './GameState' import { cardSnap } from './Utilities' import { MainMenu } from './components/MainMenu' @@ -45,7 +45,13 @@ export default function App() { const currBlindType = game.blind.curr === 'small' ? Blinds[0] : game.blind.curr === 'big' ? Blinds[1] : game.blind.boss - const reward = currBlindType.reward + game.stats.hands + Math.min(Math.floor(game.stats.money / 5), 5) + let reward = currBlindType.reward + if(game.stats.deck === DeckType.Green) { + reward += 2 * game.stats.hands + game.stats.discards + } else { + reward += game.stats.hands + Math.min(Math.floor(game.stats.money / 5), 5) + } + return ( @@ -111,14 +117,28 @@ export default function App() { }}>{`Cash Out: $${reward}`}
{'. '.repeat(49)}
- {game.stats.hands > 0 && + {game.stats.deck !== DeckType.Green && game.stats.hands > 0 &&
{game.stats.hands}
{'Remaining Hands \[$1 each\]'}
{'$'.repeat(game.stats.hands)}
} - {game.stats.money > 4 && + {game.stats.deck === DeckType.Green && game.stats.hands > 0 && +
+
{game.stats.hands}
+
{'Remaining Hands \[$2 each\]'}
+
{'$'.repeat(2 * game.stats.hands)}
+
+ } + {game.stats.deck === DeckType.Green && game.stats.discards > 0 && +
+
{game.stats.discards}
+
{'Remaining Discards \[$1 each\]'}
+
{'$'.repeat(game.stats.discards)}
+
+ } + {game.stats.deck !== DeckType.Green && game.stats.money > 4 &&
{Math.min(Math.floor(game.stats.money / 5), 5)}
{'1 interest per $5 \[5 max\]'}
diff --git a/src/Constants.ts b/src/Constants.ts index 993b068..2da613e 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -1,22 +1,39 @@ -export enum DeckType { Abandoned, Anaglyph, Black, Blue, Challenge, Checkered, Erratic, Ghost, Green, Magic, Nebula, Painted, Plasma, Red, Yellow, Zodiac } +export enum DeckType { + Abandoned, + // Anaglyph, + Black, + Blue, + // Challenge, + Checkered, + Erratic, + Ghost, + Green, + // Magic, + // Nebula, + Painted, + // Plasma, + Red, + Yellow, + // Zodiac +} export const deckInfo: {[D in keyof typeof DeckType]: string} = { Abandoned: 'Start run with\n no/ {orange}Face Cards\n in your deck', - Anaglyph: '', + // Anaglyph: '', Black: '{orange}+1/ Joker slot\n {blue}-1/ hand\n every round', Blue: '{blue}+1/ hand\n every round', - Challenge: '', + // Challenge: '', Checkered: 'Start run with\n {orange}26/ {dark-purple}Spades/ and\n {orange}26/ {red}Hearts/ in deck', Erratic: 'All Ranks and\n Suits in deck\n are randomized', Ghost: '{indigo}Spectral/ cards may\n appear in the shop,\n start with a/ {indigo}Hex/ card', Green: 'At end of each Round:\n {yellow}$2/ {small black}per remaining/ {blue}Hand\n {yellow}$1/ {small black}per remaining/ {red}Discard\n Earn no/ {orange}Interest', - Magic: 'Start run with the\n {purple}Crystal Ball/ voucher\n and/ {orange}2/ copies\n of/ {purple}The Fool', - Nebula: 'Start run with the\n {aqua}Telescope/ voucher\n {red}-1/ consumable slot', + // Magic: 'Start run with the\n {purple}Crystal Ball/ voucher\n and/ {orange}2/ copies\n of/ {purple}The Fool', + // Nebula: 'Start run with the\n {aqua}Telescope/ voucher\n {red}-1/ consumable slot', Painted: '{orange}+2/ Hand Size,\n {orange}-1/ Joker Slot', - Plasma: '', + // Plasma: '', Red: '{red}+1/ discard\n every round', Yellow: 'Start with\n extra/ {yellow}$10', - Zodiac: 'Start run with\n {purple}Tarot Merchant/,\n {aqua}Planet Merchant/,\n and {orange}Overstock' + // Zodiac: 'Start run with\n {purple}Tarot Merchant/,\n {aqua}Planet Merchant/,\n and {orange}Overstock' } export enum Suit { Spades, Hearts, Clubs, Diamonds } diff --git a/src/GameState.ts b/src/GameState.ts index d2b1a94..658ec0c 100644 --- a/src/GameState.ts +++ b/src/GameState.ts @@ -2,7 +2,7 @@ import { createContext, Dispatch } from "react" import { BlindType, ConsumableInstance, Consumables, ConsumableType, DeckType, Edition, Enhancement, handLevels, HandType, handUpgrade, Rank, rankChips, Seal, Suit } from "./Constants" import { ante_base, AnteBlinds, bestHand, boss_roll, cardSnap, debuffCards, getNextBlind, newOffers, shuffle } from "./Utilities" import { CardInfo } from "./components/CardInfo" -import { Activation, JokerInstance, Jokers, JokerType } from "./components/JokerInfo" +import { Activation, JokerInstance, JokerType } from "./components/JokerInfo" import Rand from "rand-seed" export let Random: Rand = new Rand() @@ -13,12 +13,20 @@ export const levelHand = ({ hand, n = 1 }: {hand: keyof typeof handLevels, n?: n handLevels[hand].mult += handUpgrade[hand].mult * n } +export const addCard = ({ game, card, loc }: {game: GameState, card: CardInfo, loc: 'hand' | 'deck'}) => { + game.cards[loc].push({...card, + id: game.cards.nextId, + selected: false, + submitted: false, + scored: false + }) + game.cards.nextId++ +} + type GameStates = 'main-menu' | 'blind-select' | 'scoring' | 'post-scoring' | 'shop' export type GameState = { state: GameStates - seed: string - seeded: boolean stats: { handSize: number @@ -31,6 +39,8 @@ export type GameState = { consumableSize: number jokerSize: number deck: DeckType + seed: string + seeded: boolean } shop: { @@ -102,7 +112,8 @@ export type GameAction = { state?: GameStates - stat?: keyof typeof initialGameState['stats'] + stat?: 'handSize' | 'hands' | 'discards' | 'money' | 'ante' | 'round' | 'score' | 'consumableSize' | 'jokerSize' + previous?: 'played' | 'discarded' // To know during a draw which came previously amount?: number @@ -116,20 +127,20 @@ export type GameAction = { export const initialGameState: GameState = { state: 'main-menu' as GameStates, - seed: '', - seeded: false, - + stats: { handSize: 8, hands: 4, - discards: 4, - money: 9999999, + discards: 3, + money: 0, ante: 1, round: 0, score: 0, consumableSize: 2, jokerSize: 5, - deck: DeckType.Red + deck: DeckType.Red, + seed: '', + seeded: false }, shop: { @@ -187,56 +198,109 @@ export const gameReducer = (state: GameState, action: GameAction): GameState => (a.suit !== b.suit ? a.suit - b.suit : b.rank - a.rank) ) let suits = Object.keys(Suit).filter(k => isNaN(Number(k))).map(s => s as keyof typeof Suit) - let ranks = Object.keys(Rank).filter(r => isNaN(Number(r))).map(r => r as keyof typeof Rank) + let ranks = Object.keys(Rank).filter(r => isNaN(Number(r))).map(r => r as keyof typeof Rank) let next = state, name = state.blind.boss.name switch(action.type) { case 'init': + switch(action.payload?.deck!) { + case DeckType.Red: + initialGameState.stats.discards++ + break + case DeckType.Blue: + initialGameState.stats.hands++ + break + case DeckType.Yellow: + initialGameState.stats.money += 10 + break + case DeckType.Black: + initialGameState.stats.jokerSize++ + initialGameState.stats.hands-- + break + case DeckType.Ghost: + next.shop.weights.Spectral = 4 + next.cards.consumables = [{ + id: 0, + consumable: Consumables[24] + }] + break + case DeckType.Painted: + initialGameState.stats.handSize += 2 + initialGameState.stats.jokerSize-- + break + } + next = initialGameState let arr: CardInfo[] = [] switch(action.payload?.deck!) { case DeckType.Erratic: for(let i = 1; i <= 52; i++) { let rank = Rank[ranks[Math.floor(Random.next()*ranks.length)]] - arr.push( - { - id: i, - suit: Suit[suits[Math.floor(Random.next()*suits.length)]], - rank: rank, - chips: rankChips[rank], - deck: DeckType.Erratic - } - ) + arr.push({ + id: i, + suit: Suit[suits[Math.floor(Random.next()*suits.length)]], + rank: rank, + chips: rankChips[Rank[rank] as keyof typeof rankChips], + deck: DeckType.Erratic + }) } - break - default: - suits.forEach(s => { ranks.forEach(r => { - arr.push( - { + next.cards.nextId = 53 + break + case DeckType.Abandoned: + suits.forEach(s => {ranks.forEach(r => { + if(!["Jack", "Queen", "King"].includes(r)) { + arr.push({ id: arr.length + 1, suit: Suit[s], rank: Rank[r], chips: rankChips[r], deck: action.payload?.deck! - } - ) + }) + } })}) + next.cards.nextId = 41 + break + case DeckType.Checkered: + suits.filter(s => ["Spades", "Hearts"].includes(s)).forEach(s => { + for(let i = 0; i < 2; i++) { + ranks.forEach(r => { + arr.push({ + id: arr.length + 1, + suit: Suit[s], + rank: Rank[r], + chips: rankChips[r], + deck: action.payload?.deck! + }) + }) + } + }) + next.cards.nextId = 53 + break + default: + suits.forEach(s => {ranks.forEach(r => { + arr.push({ + id: arr.length + 1, + suit: Suit[s], + rank: Rank[r], + chips: rankChips[r], + deck: action.payload?.deck! + }) + })}) + next.cards.nextId = 53 } - next = initialGameState next = {...next, - seed: action.payload?.seed ?? (Math.random() + 1).toString(36).toUpperCase().slice(2), - seeded: action.payload?.seed !== undefined, stats: {...state.stats, - deck: action.payload?.deck! + deck: action.payload?.deck!, + seed: action.payload?.seed ?? (Math.random() + 1).toString(36).toUpperCase().slice(2), + seeded: action.payload?.seed !== undefined }, blind: {...state.blind, boss: boss_roll(state.stats.ante), base: ante_base(state.stats.ante) }, cards: {...state.cards, - nextId: 53, deck: arr } } - Random = new Rand(next.seed) + Random = new Rand(next.stats.seed) break case 'state': next = {...next, @@ -244,81 +308,27 @@ export const gameReducer = (state: GameState, action: GameAction): GameState => } switch(action.payload?.state) { case 'scoring': - next = {...next, - stats: {...state.stats, - round: state.stats.round + 1 - }, - cards: {...state.cards, - deck: shuffle(state.cards.deck) - } - } + next.stats.round++ + next.cards.deck = shuffle(state.cards.deck) + if(state.blind.curr === 'boss') { debuffCards(state.blind.boss, next.cards.deck, state.cards.played) - if(name === 'The Needle') { - next.stats.hands = 1 - } else if(name === 'The Water') { - next.stats.discards = 0 - } else if(name === 'Crimson Heart') { - next.jokers[Math.floor(Random.next() * next.jokers.length)].debuffed = true - } else if(name === 'Amber Acorn') { - next.jokers.forEach(j => j.flipped = true) - next = {...next, - jokers: shuffle(next.jokers) - } - } - } - state.jokers.filter(j => j.joker.activation.includes(Activation.OnBlind)).forEach(j => { - switch(j.joker.name) { - case 'Marble Joker': - next = {...next, - cards: {...next.cards, - nextId: next.cards.nextId + 1, - deck: [...next.cards.deck, { - id: next.cards.nextId, - suit: Suit[suits[Math.floor(Random.next()*suits.length)]], - rank: Rank[ranks[Math.floor(Random.next()*ranks.length)]], - chips: 50, - enhancement: Enhancement.Stone, - deck: DeckType.Red - }] - } - } - break - case 'Burglar': - next.stats.hands += 3 - next.stats.discards = 0 + switch(name) { + case 'The Needle': next.stats.hands = 1; break + case 'The Water': next.stats.discards; break + case 'The Manacle': next.stats.handSize--; break + case 'Crimson Heart': + next.jokers[Math.floor(Random.next() * next.jokers.length)].debuffed = true break - case 'Madness': - if(state.blind.curr !== 'boss') { - j.joker.counter! += 0.5 - let validJokers = state.jokers.filter(joker => joker !== j) - if(validJokers.length > 0) { - let target = validJokers[Math.floor(Random.next() * validJokers.length)].id - next = {...next, - jokers: next.jokers.filter(j => j.id !== target) - } - } - } + case 'Amber Acorn': + next.jokers.forEach(j => j.flipped = true) + next.jokers = shuffle(next.jokers) break - case 'Riff-Raff': - if(state.jokers.length < state.stats.jokerSize) { - let validJokers = Jokers.filter(j => state.jokers.every(joker => joker.joker.name === j.name)) - let joker: JokerType - let nToAdd = Math.min(2, state.stats.jokerSize - state.jokers.length) - for(let i = 0; i < nToAdd; i++) { - if(validJokers.length === 0) { validJokers.push(Jokers[0]) } - joker = validJokers[Math.floor(Random.next() * validJokers.length)] - next = {...next, - jokers: [...next.jokers, { - id: next.cards.nextId + i, - joker: joker - }] - } - validJokers.filter(j => j.name !== joker.name) - } - next.cards.nextId + nToAdd - } } + } + + state.jokers.filter(j => j.joker.activation.includes(Activation.OnBlind) && !j.debuffed).forEach(j => { + next = j.joker.activate(next, j, Activation.OnBlind) }) break case 'post-scoring': @@ -328,7 +338,46 @@ export const gameReducer = (state: GameState, action: GameAction): GameState => c.debuffed = false } ) + + state.cards.hand.filter(c => !c.selected && !c.submitted).forEach(c => { + if(!c.debuffed) { + let will_trigger = c.enhancement === Enhancement?.Gold + state.jokers.filter(j => j.joker.activation.includes(Activation.OnHeld) && !j.debuffed).forEach(j => { + switch(j.joker.name) { + } + }) + + if(will_trigger) { + const cardName = `(Held in hand) ${c.edition ? Edition[c.edition] + ' ' : ''}${c.enhancement ? Enhancement[c.enhancement] + ' ' : ''}${c.seal ? Seal[c.seal] + ' Seal ': ''}${c.enhancement === Enhancement?.Stone ? 'Card' : Rank[c.rank]} of ${Suit[c.suit]}` + next.scoreLog.push({name: cardName}) + + const mime = state.jokers.find(j => j.joker.name === 'Mime') !== undefined + + const heldInHand = () => { + if(c.enhancement === Enhancement?.Gold) { + next.stats.money += 3 + next.scoreLog.push({name: 'Gold Card', notes: '+$3'}) + next.moneyTotalLog += 3 + } + } + + heldInHand() + if(c.seal === Seal?.Red) { + next.scoreLog.push({name: 'Red Seal', notes: 'Retrigger'}) + heldInHand() + } + if(mime) { + state.jokers.filter(j => j.joker.name === 'Mime').forEach(() => { + next.scoreLog.push({name: 'Mime', notes: 'Retrigger'}) + heldInHand() + }) + } + } + } + }) + state.jokers.filter(j => j.joker.activation.includes(Activation.EndOfRound)).forEach(j => { + next = j.joker.activate(next, j, Activation.EndOfRound) switch(j.joker.name) { case 'Egg': j.joker.counter! += 3 @@ -337,6 +386,15 @@ export const gameReducer = (state: GameState, action: GameAction): GameState => // TODO: add to post log next.stats.money += deck.reduce((nines, c) => nines += (c.rank === Rank.Nine ? 1 : 0), 0) break + case 'Rocket': + if(state.blind.curr === 'boss') { + j.joker.counter! += 2 + } + next.stats.money += j.joker.counter! + break + case 'Mail-In Rebate': + j.joker.counter = Math.floor(Random.next() * 13) + break } }) next.jokers.forEach(j => {j.debuffed = false; j.flipped = false}) @@ -523,7 +581,10 @@ export const gameReducer = (state: GameState, action: GameAction): GameState => break case 'Ice Cream': j.joker.counter! = Math.max(0, j.joker.counter! - 5) - break + break + case 'DNA': + next = j.joker.activate(next, j, Activation.OnPlayed, chips, mult) + break case 'Sixth Sense': if(state.cards.firstPlay && selected.length === 1 && selected[0].rank === Rank.Six && state.cards.consumables.length < state.stats.consumableSize) { toRemove.push(selected[0]) @@ -588,6 +649,17 @@ export const gameReducer = (state: GameState, action: GameAction): GameState => nextId: next.cards.nextId + 1 }} } + break + case 'Obelisk': + const mostPlayed = Object.entries(handLevels).reduce((most, hand) => ( + most = (most[1].played < hand[1].played || most[1].chips * most[1].mult < hand[1].chips * hand[1].mult) ? hand : most + ), Object.entries(handLevels)[12]) + if(hand === mostPlayed[0]) { + j.joker.counter! = 1 + } else { + j.joker.counter! += 0.2 + } + break } if(baseball && j.joker.rarity === 'Uncommon') { mult *= 1.5 @@ -753,10 +825,22 @@ export const gameReducer = (state: GameState, action: GameAction): GameState => break case 'Vampire': if(c.enhancement) { - j.joker.counter! += 0.1 + j.joker.counter! = (((j.joker.counter! * 10) + 1) / 10) c.enhancement = undefined } break + case 'Midas Mask': + if([Rank.King, Rank.Queen, Rank.Jack].includes(c.rank)) { + c.enhancement = Enhancement.Gold + } + break + case 'Photograph': + if([Rank.King, Rank.Queen, Rank.Jack].includes(c.rank) && j.joker.counter! > 0) { + mult *= 2 + next.scoreLog.push({name: 'Photograph', mult: 2, mult_type: 'x'}) + j.joker.counter!-- + } + break case 'Triboulet': if([Rank.King, Rank.Queen].includes(c.rank)) { mult *= 2 @@ -1034,6 +1118,12 @@ export const gameReducer = (state: GameState, action: GameAction): GameState => next.scoreLog.push({name: 'Vampire', mult: j.joker.counter!, mult_type: 'x'}) } break + case 'Obelisk': + if(j.joker.counter! > 1) { + mult *= j.joker.counter! + next.scoreLog.push({name: 'Obelisk', mult: j.joker.counter!, mult_type: 'x'}) + } + break } if(baseball && j.joker.rarity === 'Uncommon') { mult *= 1.5 @@ -1052,7 +1142,16 @@ export const gameReducer = (state: GameState, action: GameAction): GameState => next.scoreLog.push({name: 'Final Score', chips: chips, mult: mult, notes: `+$${state.moneyTotalLog}`}) next.stats.score += (chips * mult) next.active.score = {chips: chips, mult: mult} + + state.jokers.filter(j => j.joker.activation.includes(Activation.AfterScoring) && !j.debuffed).forEach(j => { + switch(j.joker.name) { + case 'Photograph': + j.joker.counter = 1 + break + } + }) } + next = {...next, stats: {...next.stats, hands: state.stats.hands - 1 @@ -1088,13 +1187,20 @@ export const gameReducer = (state: GameState, action: GameAction): GameState => state.jokers.filter(j => j.joker.activation.includes(Activation.OnDiscard) && !j.debuffed).forEach(j => { switch(j.joker.name) { case 'Faceless Joker': - if(state.cards.selected.reduce((face, c) => face += ([Rank.King, Rank.Queen, Rank.Jack].includes(c.rank) ? 1 : 0), 0)) { + if(state.cards.selected.reduce((face, c) => face += ([Rank.King, Rank.Queen, Rank.Jack].includes(c.rank) ? 1 : 0), 0) >= 3) { next.stats.money += 5 } break case 'Green Joker': j.joker.counter = Math.max(0, j.joker.counter! - 1); break + case 'Mail-In Rebate': + state.cards.selected.forEach(c => { + if(c.rank === j.joker.counter! + 2) { + next.stats.money += 5 + } + }) + break } }) next = {...next, diff --git a/src/Utilities.ts b/src/Utilities.ts index 57e61f7..d052568 100644 --- a/src/Utilities.ts +++ b/src/Utilities.ts @@ -198,9 +198,11 @@ export const newOffers = (slots: number, weights: { const validJokers = Jokers.filter(j => j.rarity === rarity && !game.jokers.find(joker => joker.joker.name === j.name) && !offers.filter(o => (o as JokerInstance).joker).find(o => (o as JokerInstance).joker.name === j.name)) if(validJokers.length === 0) { validJokers.push(Jokers[0])} + let joker = validJokers[Math.floor(Random.next() * validJokers.length)] offers.push({ id: -i, - joker: validJokers[Math.floor(Random.next() * validJokers.length)], + joker: joker, + counter: joker.counter, shopMode: true }) } else if(roll < (weights.Joker + weights.Tarot) / total) { diff --git a/src/assets/jokers/Common/Mail-In_Rebate.png b/src/assets/jokers/Common/Mail-In_Rebate.png new file mode 100644 index 0000000..824c994 Binary files /dev/null and b/src/assets/jokers/Common/Mail-In_Rebate.png differ diff --git a/src/assets/jokers/Common/Photograph.png b/src/assets/jokers/Common/Photograph.png new file mode 100644 index 0000000..39c5708 Binary files /dev/null and b/src/assets/jokers/Common/Photograph.png differ diff --git a/src/assets/jokers/Common/Reserved_Parking.png b/src/assets/jokers/Common/Reserved_Parking.png new file mode 100644 index 0000000..af73a7b Binary files /dev/null and b/src/assets/jokers/Common/Reserved_Parking.png differ diff --git a/src/assets/jokers/Rare/Obelisk.png b/src/assets/jokers/Rare/Obelisk.png new file mode 100644 index 0000000..f5af989 Binary files /dev/null and b/src/assets/jokers/Rare/Obelisk.png differ diff --git a/src/assets/jokers/Uncommon/Gift_Card.png b/src/assets/jokers/Uncommon/Gift_Card.png new file mode 100644 index 0000000..65f6c4c Binary files /dev/null and b/src/assets/jokers/Uncommon/Gift_Card.png differ diff --git a/src/assets/jokers/Uncommon/Midas_Mask.png b/src/assets/jokers/Uncommon/Midas_Mask.png new file mode 100644 index 0000000..ee4c43e Binary files /dev/null and b/src/assets/jokers/Uncommon/Midas_Mask.png differ diff --git a/src/assets/jokers/Uncommon/Rocket.png b/src/assets/jokers/Uncommon/Rocket.png new file mode 100644 index 0000000..8df83f0 Binary files /dev/null and b/src/assets/jokers/Uncommon/Rocket.png differ diff --git a/src/assets/jokers/Uncommon/Turtle_Bean.png b/src/assets/jokers/Uncommon/Turtle_Bean.png new file mode 100644 index 0000000..6caedd3 Binary files /dev/null and b/src/assets/jokers/Uncommon/Turtle_Bean.png differ diff --git a/src/components/Blind.tsx b/src/components/Blind.tsx index 4aa5eec..645c913 100644 --- a/src/components/Blind.tsx +++ b/src/components/Blind.tsx @@ -1,4 +1,4 @@ -import { useContext } from 'react' +import { useContext, useRef } from 'react' import stake_icon from '../assets/white_stake.webp' import { Blinds, BlindType, handLevels, HandType } from '../Constants' import './Blind.css' @@ -12,6 +12,8 @@ type BlindProps = { export const Blind = ({ type, blind }: BlindProps) => { const { state: game, dispatch } = useContext(GameStateContext) + const gameRef = useRef(game); + gameRef.current = game const req = game.blind.base * blind.mult let req_display = req.toLocaleString() if(req_display.length > 11) { req_display = req.toExponential()} @@ -28,6 +30,16 @@ export const Blind = ({ type, blind }: BlindProps) => { most = (most[1].played < hand[1].played || most[1].chips * most[1].mult < hand[1].chips * hand[1].mult) ? hand : most ), Object.entries(handLevels)[12]) + const onBlindSelect = () => { + if(isCurrent && type === 'select'){ + console.log(game.stats, gameRef.current.stats) + dispatch({type: 'state', payload: {state: 'scoring'}}) + console.log(game.stats, gameRef.current.stats) + dispatch({type: 'draw', payload: {amount: gameRef.current.stats.handSize - gameRef.current.cards.hand.length}}) + console.log(game.stats, gameRef.current.stats) + } + } + return ( <> {type === 'sidebar' && <> @@ -61,17 +73,7 @@ export const Blind = ({ type, blind }: BlindProps) => {
-
{ - if(isCurrent && type === 'select'){ - dispatch({type: 'state', payload: {state: 'scoring'}}) - let draw = game.stats.handSize - if(game.blind.curr === 'boss' && game.blind.boss.name === 'The Manacle') { - dispatch({type: 'stat', payload: {stat: 'handSize'}}) - draw-- - } - dispatch({type: 'draw', payload: {amount: draw}}) - } - }}>{ +
{ isCurrent ? (type === 'select' ? 'Select' : 'Current') : ((game.blind.curr === 'boss' && Blinds.indexOf(blind) < 2) || (game.blind.curr === 'big' && Blinds.indexOf(blind) < 1)) ? 'Defeated' : 'Upcoming' }
diff --git a/src/components/Hand.tsx b/src/components/Hand.tsx index 6371a02..1e02e10 100644 --- a/src/components/Hand.tsx +++ b/src/components/Hand.tsx @@ -24,7 +24,6 @@ export default function Hand() {
0}`} onClick={() => { if(gameRef.current.cards.selected.length > 0) { - let selected = gameRef.current.cards.selected let len = Math.max(gameRef.current.stats.handSize - (gameRef.current.cards.hand.length - gameRef.current.cards.selected.length), 0) dispatch({type: 'submit'}) if(game.blind.curr === 'boss' && game.blind.boss.name === 'The Hook') { @@ -34,14 +33,6 @@ export default function Hand() { dispatch({type: 'updateCards', payload: {cardLocation: 'hidden', update: [...game.cards.hidden, ...discard]}}) len += discard.length } - if(game.jokers.find(j => j.joker.name === 'DNA') !== undefined && gameRef.current.cards.hidden.length === 0 && selected.length === 1) { - dispatch({type: 'addCard', payload: {cardLocation: 'hand', card: { - ...selected[0], - selected: false, - submitted: false - }}}) - len-- - } setTimeout(() => { const lastHand = game.active.name @@ -60,6 +51,8 @@ export default function Hand() { for(let i = 0; i < Math.min(game.stats.consumableSize - game.cards.consumables.length, planets); i++) { dispatch({type: 'addCard', payload: {card: Consumables.find(c => c.hand === lastHand)}}); } + } else if(gameRef.current.stats.hands === 0) { + // Game Over } else { dispatch({type: 'draw', payload: {amount: len, previous: 'played'}}) } diff --git a/src/components/Joker.tsx b/src/components/Joker.tsx index 7b15be0..b5963a3 100644 --- a/src/components/Joker.tsx +++ b/src/components/Joker.tsx @@ -32,6 +32,7 @@ export const Joker = ({id, joker, edition, selected = false, ...props}: JokerIns case 'Madness': case 'Square Joker': case 'Vampire': + case 'Obelisk': description = description.replace('_', joker.counter! + '') break case 'Abstract Joker': @@ -51,6 +52,8 @@ export const Joker = ({id, joker, edition, selected = false, ...props}: JokerIns let nines = fullDeck.reduce((nines, c) => nines += (c.rank === Rank.Nine ? 1 : 0), 0) description = description.replace('_', nines + '') break + case 'Mail-In Rebate': + description = description.replace('_', Rank[joker.counter!]) } const descriptionElement = description.split('\n').map((line, i) => diff --git a/src/components/JokerInfo.ts b/src/components/JokerInfo.ts index 77f0c0d..ac8440f 100644 --- a/src/components/JokerInfo.ts +++ b/src/components/JokerInfo.ts @@ -1,9 +1,11 @@ -import { Edition, Sticker } from "../Constants" +import { DeckType, Edition, Enhancement, Rank, Sticker, Suit } from "../Constants" +import { addCard, GameState, initialGameState, Random } from "../GameState" export type JokerInstance = { id: number joker: JokerType + counter?: number edition?: Edition sticker?: Sticker selected?: boolean @@ -12,7 +14,7 @@ export type JokerInstance = { shopMode?: boolean } -export enum Activation { OnPlayed, OnScored, OnHeld, Independent, OnOther, OnDiscard, EndOfRound, Passive, OnBlind } +export enum Activation { OnPlayed, OnScored, OnHeld, Independent, OnOther, OnDiscard, EndOfRound, Passive, OnBlind, AfterScoring } export type JokerType = { name: string @@ -20,6 +22,7 @@ export type JokerType = { cost: number rarity: 'Common' | 'Uncommon' | 'Rare' | 'Legendary' activation: Activation[] + activate: (game: GameState, joker: JokerInstance, act: Activation, mult?: number, chips?: number, ) => GameState counter?: number copyable?: boolean perishable?: boolean @@ -32,7 +35,12 @@ export const Jokers: JokerType[] = [ description: '{red}+4/Mult', cost: 2, rarity: 'Common', - activation: [Activation.Independent] + activation: [Activation.Independent], + activate: (game, _, __, mult) => { + mult! += 4 + game.scoreLog.push({name: 'Joker', mult: 4, mult_type: '+'}) + return game + } }, // { // name: 'Greedy Joker', @@ -164,13 +172,27 @@ export const Jokers: JokerType[] = [ // cost: 5, // rarity: 'Common', // activation: [Activation.Independent] - // }, { - // name: 'Marble Joker', - // description: 'Adds one /{orange}Stone/ card\n to the deck when\n{orange}Blind/ is selected', - // cost: 6, - // rarity: 'Uncommon', - // activation: [Activation.OnBlind] - // }, { + // }, + { + name: 'Marble Joker', + description: 'Adds one /{orange}Stone/ card\n to the deck when\n{orange}Blind/ is selected', + cost: 6, + rarity: 'Uncommon', + activation: [Activation.OnBlind], + activate: (game, _, __) => { + addCard({game, card: { + id: game.cards.nextId, + suit: Math.floor(Random.next() * Object.keys(Suit).length), + rank: Math.floor(Random.next() * Object.keys(Rank).length), + chips: 50, + enhancement: Enhancement.Stone, + deck: DeckType.Red + }, loc: 'deck'}) + console.log(game) + return game + } + }, + // { // name: 'Loyalty Card', // description: '{red-invert}X4/ Mult every/{orange}6\n hands played\n{grey}_ remaining', // cost: 5, @@ -279,13 +301,20 @@ export const Jokers: JokerType[] = [ // rarity: 'Common', // activation: [Activation.EndOfRound], // counter: 0 - // }, { - // name: 'Burglar', - // description: 'When/ {orange}Blind/ is selected,\n gain/ {blue}+3/ Hands and\n {orange}lose all discards', - // cost: 6, - // rarity: 'Uncommon', - // activation: [Activation.OnBlind] - // }, { + // }, + { + name: 'Burglar', + description: 'When/ {orange}Blind/ is selected,\n gain/ {blue}+3/ Hands and\n {orange}lose all discards', + cost: 6, + rarity: 'Uncommon', + activation: [Activation.OnBlind], + activate: (game, _, __) => { + game.stats.hands += 3 + game.stats.discards = 0 + return game + } + }, + // { // name: 'Blackboard', // description: '{red-invert}X3/ Mult if all cards held\n in hand are/ {dark-purple}Spades/ or/ {blue} Clubs', // cost: 6, @@ -311,8 +340,18 @@ export const Jokers: JokerType[] = [ description: 'If/ {orange}first hand/ of round\n has only/ {orange}1/ card, add a\n permanent copy to deck\n and draw it to/ {orange}hand', cost: 8, rarity: 'Rare', - activation: [] // Activated in hand on submit - }, + activation: [Activation.OnPlayed, Activation.EndOfRound], + activate: (game, joker, act) => { + if(act === Activation.OnPlayed && joker.counter! > 0 && game.cards.selected.length === 1) { + addCard({game, card: game.cards.selected[0], loc: 'hand'}) + joker.counter!-- + } else if(act === Activation.EndOfRound) { + joker.counter = 1 + } + return game + }, + counter: 1 + }, // { // name: 'Splash', // description: 'Every/ {orange}played card/ counts\n in scoring', @@ -384,71 +423,170 @@ export const Jokers: JokerType[] = [ cost: 7, rarity: 'Uncommon', activation: [Activation.OnBlind, Activation.Independent], + activate: (game, j, _) => { + if(game.blind.curr !== 'boss') { + j.counter! += 0.5 + let validJokers = game.jokers.filter(joker => joker !== j) + if(validJokers.length > 0) { + let target = validJokers[Math.floor(Random.next() * validJokers.length)].id + game.jokers = game.jokers.filter(j => j.id !== target) + } + } + return game + }, counter: 1 - }, { - name: 'Square Joker', - description: 'This Joker gains/ {blue}+4/ Chips if\n played hand has exactly/ {orange}4/ cards\n{grey}(Currently/ {blue}_/{grey}Chips)', - cost: 4, - rarity: 'Common', - activation: [Activation.OnPlayed, Activation.Independent], - counter: 0 - }, { - name: 'Seance', - description: 'If/ {orange}poker hand/ is a\n {orange}Straight Flush/{nospace}, create a\n random/ {blue}Spectral/ card\n {grey small}(Must have room)', - cost: 6, - rarity: 'Uncommon', - activation: [Activation.OnPlayed] - }, { + }, + // { + // name: 'Square Joker', + // description: 'This Joker gains/ {blue}+4/ Chips if\n played hand has exactly/ {orange}4/ cards\n{grey}(Currently/ {blue}_/{grey}Chips)', + // cost: 4, + // rarity: 'Common', + // activation: [Activation.OnPlayed, Activation.Independent], + // counter: 0 + // }, { + // name: 'Seance', + // description: 'If/ {orange}poker hand/ is a\n {orange}Straight Flush/{nospace}, create a\n random/ {blue}Spectral/ card\n {grey small}(Must have room)', + // cost: 6, + // rarity: 'Uncommon', + // activation: [Activation.OnPlayed] + // }, + { name: 'Riff-Raff', description: 'When/ {orange}Blind/ is selected,\n create/ {orange}2/ {blue}Common/ {orange}Jokers\n {small grey}(Must have room)', cost: 5, rarity: 'Common', - activation: [Activation.OnBlind] - }, { - name: 'Vampire', - description: 'This Joker gains/ {red}+/ {red-invert nospace}X0.1\n per scoring/ {orange}Enhanced\n {orange}card/ played, removes\n card/ {orange}Enhancement\n {grey}(Currently/ {red-invert}X_/ {grey}Mult)', - cost: 7, - rarity: 'Uncommon', - activation: [Activation.OnScored, Activation.Independent], - counter: 1 - }, { - name: 'Shortcut', - description: 'Allows/ {orange}Straights/ to be\n made with gaps of/ {orange}1 rank\n {grey}(ex:/ {orange}10 8 6 5 3/{grey nospace})', - cost: 7, - rarity: 'Uncommon', - activation: [Activation.Passive] - } // Hologram - , { - name: 'Vagabond', - description: 'Create a/ {purple}Tarot/ card\n if hand is played with\n {yellow}$4/ or less\n {small grey}(Must have room)', - cost: 8, - rarity: 'Rare', - activation: [Activation.OnPlayed] - }, { - name: 'Baron', - description: 'Each/ {orange}King/ held in hand\n gives/ {red-invert}X1.5/ Mult', - cost: 8, - rarity: 'Rare', - activation: [Activation.OnHeld] - }, { - name: 'Cloud 9', - description: 'Earn/ {yellow}$1/ for each/ {orange}9\n in your/ {orange}full deck/ at\n end of round\n {grey}(Currently/ {yellow}$_/ {grey nospace})', - cost: 7, - rarity: 'Uncommon', - activation: [Activation.EndOfRound] - } - // ,{ - // name: 'Baseball Card', - // description: '{green}Uncommon/Jokers each\ngive/{red-invert}X1.5/Mult', + activation: [Activation.OnBlind], + activate: (game, _, __) => { + if(game.jokers.length < game.stats.jokerSize) { + let validJokers = Jokers.filter(j => game.jokers.every(joker => joker.joker.name === j.name)) + let joker: JokerType + let nToAdd = Math.min(2, game.stats.jokerSize - game.jokers.length) + for(let i = 0; i < nToAdd; i++) { + if(validJokers.length === 0) { validJokers.push(Jokers[0]) } + joker = validJokers[Math.floor(Random.next() * validJokers.length)] + game = {...game, + jokers: [...game.jokers, { + id: game.cards.nextId + i, + joker: joker + }] + } + validJokers.filter(j => j.name !== joker.name) + } + game.cards.nextId + nToAdd + } + return game + } + }, + // { + // name: 'Vampire', + // description: 'This Joker gains/ {red}+/ {red-invert nospace}X0.1\n per scoring/ {orange}Enhanced\n {orange}card/ played, removes\n card/ {orange}Enhancement\n {grey}(Currently/ {red-invert}X_/ {grey}Mult)', + // cost: 7, + // rarity: 'Uncommon', + // activation: [Activation.OnScored, Activation.Independent], + // counter: 1 + // }, { + // name: 'Shortcut', + // description: 'Allows/ {orange}Straights/ to be\n made with gaps of/ {orange}1 rank\n {grey}(ex:/ {orange}10 8 6 5 3/{grey nospace})', + // cost: 7, + // rarity: 'Uncommon', + // activation: [Activation.Passive] + // } // Hologram + // , { + // name: 'Vagabond', + // description: 'Create a/ {purple}Tarot/ card\n if hand is played with\n {yellow}$4/ or less\n {small grey}(Must have room)', + // cost: 8, + // rarity: 'Rare', + // activation: [Activation.OnPlayed] + // }, { + // name: 'Baron', + // description: 'Each/ {orange}King/ held in hand\n gives/ {red-invert}X1.5/ Mult', + // cost: 8, + // rarity: 'Rare', + // activation: [Activation.OnHeld] + // }, { + // name: 'Cloud 9', + // description: 'Earn/ {yellow}$1/ for each/ {orange}9\n in your/ {orange}full deck/ at\n end of round\n {grey}(Currently/ {yellow}$_/ {grey nospace})', + // cost: 7, + // rarity: 'Uncommon', + // activation: [Activation.EndOfRound] + // }, { + // name: 'Rocket', + // description: 'Earn/ {yellow}$1/ at end of round.\n Payout increases by/ {yellow}$2/ when\n {orange}Boss Blind/ is defeated', + // cost: 6, + // rarity: 'Uncommon', + // activation: [Activation.EndOfRound], + // counter: 1 + // }, { + // name: 'Obelisk', + // description: 'This joker gains/ {red}+/{red-invert nospace}X0.2/ Mult\n per/ {orange}consecutive/ hand played without\n playing your most played\n {orange}poker hand/ {grey}(Currently/ {red-invert}X_/ {grey}Mult)', // cost: 8, // rarity: 'Rare', - // activation: [] + // activation: [Activation.OnPlayed, Activation.Independent], + // counter: 1 + // }, { + // name: 'Midas Mask', + // description: 'All played/ {orange}face\n cards become/ {orange}Gold\n cards when scored', + // cost: 7, + // rarity: 'Uncommon', + // activation: [Activation.OnScored] + // } // Luchador + // , { + // name: 'Photograph', + // description: 'First played/ {orange}face card\n gives/ {red-invert}X2/ Mult when scored', + // cost: 5, + // rarity: 'Common', + // activation: [Activation.OnScored, Activation.AfterScoring], + // counter: 1 + // }, { + // name: 'Gift Card', + // description: 'Add/ {yellow}$1/ of/ {orange} sell value\n to every/ {orange}Joker/ and\n {orange}Consumable/ card at end of round', + // cost: 6, + // rarity: 'Uncommon', + // activation: [Activation.EndOfRound] + // }, + { + name: 'Turtle Bean', + description: '{orange}+5/ hand size, reduces\n by/ {red}1/ each round', + cost: 6, + rarity: 'Uncommon', + activation: [Activation.OnBlind, Activation.EndOfRound], + activate: (game, j, act) => { + if(act === Activation.OnBlind && j.counter! > 0) { + game.stats.handSize += j.counter! + } else if (act === Activation.EndOfRound) { + game.stats.handSize = initialGameState.stats.handSize + j.counter!-- + } + return game + }, + counter: 5 + }, // Erosion + // { + // name: 'Reserved Parking', + // description: 'Each/ {orange}face/ card held in\n hand has a/ {green} 1 in 2\n chance to give/ {yellow}$1', + // cost: 6, + // rarity: 'Common', + // activation: [Activation.OnHeld] + // }, { + // name: 'Mail-In Rebate', + // description: 'Earn/ {yellow}$5/ for each\n discarded/ {orange}_/{nospace},\n rank changes every round', + // cost: 4, + // rarity: 'Common', + // activation: [Activation.OnDiscard, Activation.EndOfRound], + // counter: 0 + // } + // // ,{ + // // name: 'Baseball Card', + // // description: '{green}Uncommon/Jokers each\ngive/{red-invert}X1.5/Mult', + // // cost: 8, + // // rarity: 'Rare', + // // activation: [] + // // } + // , { + // name: 'Triboulet', + // description: 'Played Kings and\nQueens each give/{red-invert}X2/Mult\nwhen scored', + // cost: 20, + // rarity: 'Legendary', + // activation: [Activation.OnScored] // } - , { - name: 'Triboulet', - description: 'Played Kings and\nQueens each give/{red-invert}X2/Mult\nwhen scored', - cost: 20, - rarity: 'Legendary', - activation: [Activation.OnScored] - } ] \ No newline at end of file diff --git a/src/components/MainMenu.css b/src/components/MainMenu.css index 66c71a6..5a49f75 100644 --- a/src/components/MainMenu.css +++ b/src/components/MainMenu.css @@ -4,6 +4,11 @@ align-items: center; } +h1 { + font-size: 256px; + line-height: 32px; +} + #logo { width: 666px; } diff --git a/src/components/MainMenu.tsx b/src/components/MainMenu.tsx index 98b3030..cec9ede 100644 --- a/src/components/MainMenu.tsx +++ b/src/components/MainMenu.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import logo from '../assets/logo.png' +// import logo from '../assets/logo.png' // import ace from '../assets/cards/SA.webp' // import cardBack from '../assets/cards/modifiers/enhancements/Base.png' import './MainMenu.css' @@ -10,7 +10,9 @@ export const MainMenu = () => { return (