JavaScript · 3945 bytes Raw Blame History
1 // Game state singleton for dougk gamification
2 // Tracks currency (captured koi) and other persistent state
3
4 const STORAGE_KEY = 'dougk-koi'
5
6 const gameState = {
7 capturedKoi: parseInt(localStorage.getItem(STORAGE_KEY) || '0'),
8
9 // Shop approach tracking - only approach Doug when there's a reason
10 shopApproach: {
11 // First emergence this session - creatures should greet player
12 firstEmergence: {
13 donny: true,
14 ollie: true
15 },
16 // Track which items player could afford before last koi capture
17 previousAffordableIds: new Set(),
18 // Flag: player can now afford something new
19 hasNewAffordableItem: false,
20 // Items catalog reference (set by initShopTracking)
21 allItems: null,
22 // Inventory reference for checking owned items
23 inventory: null
24 },
25
26 // Initialize shop tracking with items and inventory references
27 initShopTracking(allItems, inventory) {
28 this.shopApproach.allItems = allItems
29 this.shopApproach.inventory = inventory
30 // Initialize previous affordable set based on current koi
31 this.updateAffordableItems()
32 },
33
34 // Get all purchasable (not owned) items player can afford
35 getAffordableItemIds() {
36 const { allItems, inventory } = this.shopApproach
37 if (!allItems || !inventory) return new Set()
38
39 const affordable = new Set()
40 for (const item of allItems) {
41 // Must not already own it and must be able to afford it
42 if (!inventory.owns(item.id) && item.price <= this.capturedKoi) {
43 affordable.add(item.id)
44 }
45 }
46 return affordable
47 },
48
49 // Update affordable items and check if player unlocked something new
50 updateAffordableItems() {
51 const currentAffordable = this.getAffordableItemIds()
52 const { previousAffordableIds } = this.shopApproach
53
54 // Check if any new items became affordable
55 for (const id of currentAffordable) {
56 if (!previousAffordableIds.has(id)) {
57 this.shopApproach.hasNewAffordableItem = true
58 break
59 }
60 }
61
62 // Update the previous set
63 this.shopApproach.previousAffordableIds = currentAffordable
64 },
65
66 // Check if a creature should approach Doug
67 // Returns true on first emergence or if player can afford something new
68 shouldCreatureApproach(creature) {
69 const { firstEmergence, hasNewAffordableItem } = this.shopApproach
70
71 // First emergence this session
72 if (firstEmergence[creature]) {
73 return true
74 }
75
76 // Player unlocked a new affordable item
77 if (hasNewAffordableItem) {
78 return true
79 }
80
81 return false
82 },
83
84 // Mark that a creature has approached (used after shop interaction starts)
85 markApproachComplete(creature) {
86 // Clear first emergence flag for this creature
87 this.shopApproach.firstEmergence[creature] = false
88
89 // Clear the new affordable flag (both creatures share this trigger)
90 this.shopApproach.hasNewAffordableItem = false
91 },
92
93 // Called when player purchases an item - recalculate affordable items
94 onPurchase() {
95 this.updateAffordableItems()
96 },
97
98 addKoi(n = 1) {
99 this.capturedKoi += n
100 this.save()
101 this.notifyListeners()
102 // Check if new items became affordable
103 this.updateAffordableItems()
104 },
105
106 spendKoi(n) {
107 if (this.capturedKoi >= n) {
108 this.capturedKoi -= n
109 this.save()
110 this.notifyListeners()
111 // Recalculate affordable items after spending
112 this.updateAffordableItems()
113 return true
114 }
115 return false
116 },
117
118 getKoi() {
119 return this.capturedKoi
120 },
121
122 save() {
123 localStorage.setItem(STORAGE_KEY, this.capturedKoi.toString())
124 },
125
126 // Listener system for UI updates
127 listeners: [],
128
129 addListener(callback) {
130 this.listeners.push(callback)
131 },
132
133 removeListener(callback) {
134 this.listeners = this.listeners.filter(l => l !== callback)
135 },
136
137 notifyListeners() {
138 for (const listener of this.listeners) {
139 listener(this.capturedKoi)
140 }
141 }
142 }
143
144 export default gameState