awacke1 commited on
Commit
1f6a578
Β·
verified Β·
1 Parent(s): e34e138

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +104 -270
game.js CHANGED
@@ -1,33 +1,18 @@
1
- // --- Game State (Modify Existing) ---
 
2
  let gameState = {
3
  currentPageId: 1,
4
- // πŸ‘‡ Encapsulate character data
5
  character: {
6
- name: "Hero",
7
- race: "Human",
8
- alignment: "Neutral Good",
9
- class: "Fighter",
10
- level: 1,
11
- xp: 0,
12
- xpToNextLevel: 100, // Experience needed for level 2
13
- statPointsPerLevel: 1, // How many points earned on level up (optional)
14
- availableStatPoints: 0, // Points available to spend
15
- stats: {
16
- strength: 7,
17
- intelligence: 5,
18
- wisdom: 5, // Corrected spelling from before
19
- dexterity: 6,
20
- constitution: 6, // Added constitution
21
- charisma: 5, // Added charisma
22
- hp: 30,
23
- maxHp: 30
24
- },
25
- inventory: [] // Will mirror items collected in game
26
  }
27
- // Note: We removed the top-level 'stats' and 'inventory' as they are now inside character
28
  };
29
 
30
- // --- DOM Element Getters (Add New) ---
 
 
31
  const charNameInput = document.getElementById('char-name');
32
  const charRaceSpan = document.getElementById('char-race');
33
  const charAlignmentSpan = document.getElementById('char-alignment');
@@ -38,26 +23,23 @@ const charXPNextSpan = document.getElementById('char-xp-next');
38
  const charHPSpan = document.getElementById('char-hp');
39
  const charMaxHPSpan = document.getElementById('char-max-hp');
40
  const charInventoryList = document.getElementById('char-inventory-list');
41
- const statSpans = { // Map stat names to their display elements
42
- strength: document.getElementById('stat-strength'),
43
- intelligence: document.getElementById('stat-intelligence'),
44
- wisdom: document.getElementById('stat-wisdom'),
45
- dexterity: document.getElementById('stat-dexterity'),
46
- constitution: document.getElementById('stat-constitution'),
47
- charisma: document.getElementById('stat-charisma'),
48
  };
49
  const statIncreaseButtons = document.querySelectorAll('.stat-increase');
50
  const levelUpButton = document.getElementById('levelup-btn');
51
  const saveCharButton = document.getElementById('save-char-btn');
52
  const exportCharButton = document.getElementById('export-char-btn');
53
  const statIncreaseCostSpan = document.getElementById('stat-increase-cost');
 
54
 
55
- // --- NEW Character Sheet Functions ---
56
 
57
- /**
58
- * Renders the entire character sheet based on gameState.character
59
- */
60
  function renderCharacterSheet() {
 
 
61
  const char = gameState.character;
62
 
63
  charNameInput.value = char.name;
@@ -68,20 +50,17 @@ function renderCharacterSheet() {
68
  charXPSpan.textContent = char.xp;
69
  charXPNextSpan.textContent = char.xpToNextLevel;
70
 
71
- // Update HP (ensure it doesn't exceed maxHP)
72
  char.stats.hp = Math.min(char.stats.hp, char.stats.maxHp);
73
  charHPSpan.textContent = char.stats.hp;
74
  charMaxHPSpan.textContent = char.stats.maxHp;
75
 
76
- // Update core stats display
77
  for (const stat in statSpans) {
78
  if (statSpans.hasOwnProperty(stat) && char.stats.hasOwnProperty(stat)) {
79
  statSpans[stat].textContent = char.stats[stat];
80
  }
81
  }
82
 
83
- // Update inventory list (up to 15 slots)
84
- charInventoryList.innerHTML = ''; // Clear previous list
85
  const maxSlots = 15;
86
  for (let i = 0; i < maxSlots; i++) {
87
  const li = document.createElement('li');
@@ -94,221 +73,148 @@ function renderCharacterSheet() {
94
  itemSpan.textContent = item;
95
  li.appendChild(itemSpan);
96
  } else {
97
- // Add placeholder for empty slot
98
  const emptySlotSpan = document.createElement('span');
99
  emptySlotSpan.classList.add('item-slot');
 
100
  li.appendChild(emptySlotSpan);
101
  }
102
  charInventoryList.appendChild(li);
103
  }
104
 
105
- // Update level up / stat increase buttons state
106
- updateLevelUpAvailability();
107
-
108
- // Display cost to increase stat (example: level * 10)
109
- statIncreaseCostSpan.textContent = calculateStatIncreaseCost();
110
  }
111
 
112
- /**
113
- * Calculates the XP cost to increase a stat (example logic)
114
- */
115
  function calculateStatIncreaseCost() {
116
- // Cost could depend on current stat value or level
117
- return (gameState.character.level * 10) + 5; // Example: 15 XP at level 1, 25 at level 2 etc.
118
  }
119
 
120
- /**
121
- * Enables/disables level up and stat increase buttons based on XP/Points
122
- */
123
  function updateLevelUpAvailability() {
124
  const char = gameState.character;
125
  const canLevelUp = char.xp >= char.xpToNextLevel;
126
  levelUpButton.disabled = !canLevelUp;
127
 
128
- const canIncreaseStat = char.availableStatPoints > 0 || (char.xp >= calculateStatIncreaseCost()); // Can spend points OR XP
 
 
 
129
  statIncreaseButtons.forEach(button => {
130
- // Enable if points available OR if enough XP (and not leveling up)
131
- button.disabled = !(char.availableStatPoints > 0 || (char.xp >= calculateStatIncreaseCost()));
132
- // Optionally disable if level up is pending to force level up first?
133
  // button.disabled = button.disabled || canLevelUp;
134
  });
135
 
136
- // Enable spending stat points ONLY if available > 0
137
- if (char.availableStatPoints > 0) {
138
- statIncreaseCostSpan.parentElement.innerHTML = `<small>Available points: ${char.availableStatPoints} / Cost per point: 1</small>`;
139
- statIncreaseButtons.forEach(button => button.disabled = false);
140
- } else {
141
- statIncreaseCostSpan.parentElement.innerHTML = `<small>Cost to increase stat: <span id="stat-increase-cost">${calculateStatIncreaseCost()}</span> XP</small>`;
142
- // Disable based on XP check done above
143
- }
144
  }
145
 
146
- /**
147
- * Handles leveling up the character
148
- */
149
  function handleLevelUp() {
150
- const char = gameState.character;
 
151
  if (char.xp >= char.xpToNextLevel) {
152
  char.level++;
153
- char.xp -= char.xpToNextLevel; // Subtract cost
154
- char.xpToNextLevel = Math.floor(char.xpToNextLevel * 1.6); // Increase next level cost (adjust multiplier)
155
- char.availableStatPoints += char.statPointsPerLevel; // Grant stat points
156
 
157
- // Increase max HP based on Constitution (example: + half CON modifier)
158
  const conModifier = Math.floor((char.stats.constitution - 10) / 2);
159
- const hpGain = Math.max(1, Math.floor(Math.random() * 6) + 1 + conModifier); // Roll d6 + CON mod (like D&D)
160
  char.stats.maxHp += hpGain;
161
- char.stats.hp = char.stats.maxHp; // Full heal on level up
162
 
163
- console.log(`πŸŽ‰ Leveled Up to ${char.level}! Gained ${char.statPointsPerLevel} stat point(s) and ${hpGain} HP.`);
164
- renderCharacterSheet(); // Update display
165
  } else {
166
  console.warn("Not enough XP to level up yet.");
167
  }
168
  }
169
 
170
- /**
171
- * Handles increasing a specific stat
172
- */
173
  function handleStatIncrease(statName) {
 
174
  const char = gameState.character;
175
  const cost = calculateStatIncreaseCost();
176
 
177
- // Priority 1: Spend available stat points
178
  if (char.availableStatPoints > 0) {
179
  char.stats[statName]++;
180
  char.availableStatPoints--;
181
- console.log(`πŸ“ˆ Increased ${statName} using a point. ${char.availableStatPoints} points remaining.`);
182
-
183
- // Update derived stats if needed (e.g., CON affects maxHP)
184
- if (statName === 'constitution') {
185
- const oldModifier = Math.floor((char.stats.constitution - 1 - 10) / 2);
186
- const newModifier = Math.floor((char.stats.constitution - 10) / 2);
187
- const hpBonusPerLevel = Math.max(0, newModifier - oldModifier) * char.level; // Gain HP retroactively? Or just going forward? Simpler: just add difference.
188
- if(hpBonusPerLevel > 0) {
189
- console.log(`Increased max HP by ${hpBonusPerLevel} due to CON increase.`);
190
- char.stats.maxHp += hpBonusPerLevel;
191
- char.stats.hp += hpBonusPerLevel; // Also increase current HP
192
- }
193
- }
194
-
195
  renderCharacterSheet();
196
- return; // Exit after spending a point
197
  }
198
 
199
- // Priority 2: Spend XP if no points are available
200
  if (char.xp >= cost) {
201
  char.stats[statName]++;
202
  char.xp -= cost;
203
- console.log(`πŸ’ͺ Increased ${statName} for ${cost} XP.`);
204
-
205
- // Update derived stats (same as above)
206
- if (statName === 'constitution') {
207
- const oldModifier = Math.floor((char.stats.constitution - 1 - 10) / 2);
208
- const newModifier = Math.floor((char.stats.constitution - 10) / 2);
209
- const hpBonusPerLevel = Math.max(0, newModifier - oldModifier) * char.level;
210
- if(hpBonusPerLevel > 0) {
211
- console.log(`Increased max HP by ${hpBonusPerLevel} due to CON increase.`);
212
- char.stats.maxHp += hpBonusPerLevel;
213
- char.stats.hp += hpBonusPerLevel;
214
- }
215
- }
216
-
217
  renderCharacterSheet();
218
  } else {
219
- console.warn(`Not enough XP (${char.xp}/${cost}) or stat points to increase ${statName}.`);
220
  }
221
  }
222
 
223
 
224
- /**
225
- * Saves character data to localStorage
226
- */
227
  function saveCharacter() {
228
  try {
229
  localStorage.setItem('textAdventureCharacter', JSON.stringify(gameState.character));
230
- console.log('πŸ’Ύ Character saved locally.');
231
- // Optional: Add brief visual confirmation
232
- saveCharButton.textContent = 'πŸ’Ύ Saved!';
233
- setTimeout(() => { saveCharButton.innerHTML = 'πŸ’Ύ<span class="btn-label">Save</span>'; }, 1500);
 
 
 
 
234
  } catch (e) {
235
- console.error('Error saving character to localStorage:', e);
236
- alert('Failed to save character. Local storage might be full or disabled.');
237
  }
238
  }
239
 
240
- /**
241
- * Loads character data from localStorage
242
- */
243
  function loadCharacter() {
244
- try {
 
245
  const savedData = localStorage.getItem('textAdventureCharacter');
246
  if (savedData) {
247
  const loadedChar = JSON.parse(savedData);
248
- // Basic validation / merging with default structure
249
- gameState.character = {
250
- ...gameState.character, // Start with defaults
251
- ...loadedChar, // Override with loaded data
252
- stats: { // Ensure stats object exists and merge
253
- ...gameState.character.stats,
254
- ...(loadedChar.stats || {})
255
- },
256
- inventory: loadedChar.inventory || [] // Ensure inventory array exists
257
- };
258
- console.log('πŸ’Ύ Character loaded from local storage.');
259
- return true; // Indicate success
260
  }
261
- } catch (e) {
262
- console.error('Error loading character from localStorage:', e);
263
- // Don't overwrite gameState if loading fails
264
- }
265
- return false; // Indicate nothing loaded or error
266
  }
267
 
268
- /**
269
- * Exports character data as a JSON file download
270
- */
271
  function exportCharacter() {
272
- try {
273
- const charJson = JSON.stringify(gameState.character, null, 2); // Pretty print JSON
 
274
  const blob = new Blob([charJson], { type: 'application/json' });
275
  const url = URL.createObjectURL(blob);
276
- const a = document.createElement('a');
277
- a.href = url;
278
- // Sanitize name for filename
279
  const filename = `${gameState.character.name.replace(/[^a-z0-9]/gi, '_').toLowerCase() || 'character'}_save.json`;
280
- a.download = filename;
281
- document.body.appendChild(a); // Required for Firefox
282
- a.click();
283
- document.body.removeChild(a);
284
- URL.revokeObjectURL(url); // Clean up
285
- console.log(`πŸ“€ Character exported as ${filename}`);
286
- } catch (e) {
287
- console.error('Error exporting character:', e);
288
- alert('Failed to export character data.');
289
- }
290
  }
291
 
292
- // --- Event Listeners (Add New) ---
 
 
293
  charNameInput.addEventListener('change', () => {
294
  gameState.character.name = charNameInput.value.trim() || "Hero";
295
- // No need to re-render just for name change unless displaying it elsewhere
296
- console.log(`πŸ‘€ Name changed to: ${gameState.character.name}`);
297
- // Maybe save automatically on name change?
298
- // saveCharacter();
299
  });
300
-
301
  levelUpButton.addEventListener('click', handleLevelUp);
302
-
303
  statIncreaseButtons.forEach(button => {
304
  button.addEventListener('click', () => {
305
  const statToIncrease = button.dataset.stat;
306
- if (statToIncrease) {
307
- handleStatIncrease(statToIncrease);
308
- }
309
  });
310
  });
311
-
312
  saveCharButton.addEventListener('click', saveCharacter);
313
  exportCharButton.addEventListener('click', exportCharacter);
314
 
@@ -316,110 +222,38 @@ exportCharButton.addEventListener('click', exportCharacter);
316
  // --- Modify Existing Functions ---
317
 
318
  function startGame() {
319
- // Try loading character first
320
- if (!loadCharacter()) {
321
- // If no save found, initialize with defaults (already done by gameState definition)
322
- console.log("No saved character found, starting new.");
323
- }
324
- // Ensure compatibility if loaded save is old/missing fields
325
  gameState.character = {
326
- ...{ // Define ALL default fields here
327
- name: "Hero", race: "Human", alignment: "Neutral Good", class: "Fighter",
328
- level: 1, xp: 0, xpToNextLevel: 100, statPointsPerLevel: 1, availableStatPoints: 0,
329
- stats: { strength: 7, intelligence: 5, wisdom: 5, dexterity: 6, constitution: 6, charisma: 5, hp: 30, maxHp: 30 },
330
- inventory: []
331
- },
332
- ...gameState.character // Loaded data overrides defaults
333
  };
334
- // Ensure stats object has all keys after loading potentially partial data
335
- gameState.character.stats = {
336
- strength: 7, intelligence: 5, wisdom: 5, dexterity: 6, constitution: 6, charisma: 5, hp: 30, maxHp: 30, // Defaults first
337
- ...(gameState.character.stats || {}) // Loaded stats override defaults
338
- }
339
-
340
- gameState.currentPageId = 1; // Always start at page 1
341
- renderCharacterSheet(); // Initial render of the sheet
342
- renderPage(gameState.currentPageId); // Render the story page
343
  }
344
 
345
  function handleChoiceClick(choiceData) {
346
- const nextPageId = parseInt(choiceData.nextPage);
347
- const itemToAdd = choiceData.addItem;
348
-
349
- if (isNaN(nextPageId)) {
350
- console.error("Invalid nextPageId:", choiceData.nextPage); return;
351
- }
352
-
353
- // Process Choice Effects
354
- if (itemToAdd && !gameState.character.inventory.includes(itemToAdd)) {
355
- gameState.character.inventory.push(itemToAdd); // Add to character inventory
356
- console.log("Added item:", itemToAdd);
357
- // Limit inventory size?
358
- // if (gameState.character.inventory.length > 15) { /* Handle overflow */ }
359
- }
360
-
361
- // Process Landing Page Effects
362
- gameState.currentPageId = nextPageId;
363
- const nextPageData = gameData[nextPageId];
364
-
365
- if (nextPageData) {
366
- // Apply HP loss defined on the *landing* page
367
- if (nextPageData.hpLoss) {
368
- gameState.character.stats.hp -= nextPageData.hpLoss; // Update character HP
369
- console.log(`Lost ${nextPageData.hpLoss} HP.`);
370
- if (gameState.character.stats.hp <= 0) {
371
- gameState.character.stats.hp = 0;
372
- console.log("Player died from HP loss!");
373
- renderCharacterSheet(); // Update sheet before showing game over
374
- renderPage(99); // Go to game over page
375
- return;
376
- }
377
- }
378
-
379
- // --- Apply Rewards (New) ---
380
- if (nextPageData.reward) {
381
- if (nextPageData.reward.xp) {
382
- gameState.character.xp += nextPageData.reward.xp;
383
- console.log(`✨ Gained ${nextPageData.reward.xp} XP!`);
384
- }
385
- if (nextPageData.reward.statIncrease) {
386
- const stat = nextPageData.reward.statIncrease.stat;
387
- const amount = nextPageData.reward.statIncrease.amount;
388
- if (gameState.character.stats.hasOwnProperty(stat)) {
389
- gameState.character.stats[stat] += amount;
390
- console.log(`πŸ“ˆ Stat ${stat} increased by ${amount}!`);
391
- // Update derived stats if needed (e.g., CON -> HP)
392
- if (stat === 'constitution') { /* ... update maxHP ... */ }
393
- }
394
- }
395
- // Add other reward types here (e.g., items, stat points)
396
- if(nextPageData.reward.addItem && !gameState.character.inventory.includes(nextPageData.reward.addItem)){
397
- gameState.character.inventory.push(nextPageData.reward.addItem);
398
- console.log(`🎁 Found item: ${nextPageData.reward.addItem}`);
399
- }
400
- }
401
-
402
- // Check if landing page is game over
403
- if (nextPageData.gameOver) {
404
- console.log("Reached Game Over page.");
405
- renderCharacterSheet(); // Update sheet one last time
406
- renderPage(nextPageId);
407
- return;
408
- }
409
 
410
- } else {
411
- console.error(`Data for page ${nextPageId} not found!`);
412
- renderCharacterSheet();
413
- renderPage(99); // Fallback to game over
414
- return;
415
- }
416
 
417
- // Render the character sheet (updates XP, stats, inventory) BEFORE rendering page
418
- renderCharacterSheet();
419
- // Render the new story page
420
- renderPage(nextPageId);
421
- }
422
 
423
- // --- REMOVE/REPLACE Old UI Updates ---
424
- // Remove the old updateStatsDisplay() and updateInventoryDisplay() functions
425
- // as renderCharacterSheet() now handles this. Make sure no code is still calling them.
 
1
+ // --- Game State ---
2
+ // (Keep the gameState definition from the previous step, including the nested 'character' object)
3
  let gameState = {
4
  currentPageId: 1,
 
5
  character: {
6
+ name: "Hero", race: "Human", alignment: "Neutral Good", class: "Fighter",
7
+ level: 1, xp: 0, xpToNextLevel: 100, statPointsPerLevel: 1, availableStatPoints: 0,
8
+ stats: { strength: 7, intelligence: 5, wisdom: 5, dexterity: 6, constitution: 6, charisma: 5, hp: 30, maxHp: 30 },
9
+ inventory: []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  }
 
11
  };
12
 
13
+
14
+ // --- DOM Element Getters ---
15
+ // (Keep all the getters from the previous step: charNameInput, charRaceSpan, etc.)
16
  const charNameInput = document.getElementById('char-name');
17
  const charRaceSpan = document.getElementById('char-race');
18
  const charAlignmentSpan = document.getElementById('char-alignment');
 
23
  const charHPSpan = document.getElementById('char-hp');
24
  const charMaxHPSpan = document.getElementById('char-max-hp');
25
  const charInventoryList = document.getElementById('char-inventory-list');
26
+ const statSpans = {
27
+ strength: document.getElementById('stat-strength'), intelligence: document.getElementById('stat-intelligence'),
28
+ wisdom: document.getElementById('stat-wisdom'), dexterity: document.getElementById('stat-dexterity'),
29
+ constitution: document.getElementById('stat-constitution'), charisma: document.getElementById('stat-charisma'),
 
 
 
30
  };
31
  const statIncreaseButtons = document.querySelectorAll('.stat-increase');
32
  const levelUpButton = document.getElementById('levelup-btn');
33
  const saveCharButton = document.getElementById('save-char-btn');
34
  const exportCharButton = document.getElementById('export-char-btn');
35
  const statIncreaseCostSpan = document.getElementById('stat-increase-cost');
36
+ const statPointsAvailableSpan = document.getElementById('stat-points-available'); // Added span for clarity
37
 
38
+ // --- Character Sheet Functions ---
39
 
 
 
 
40
  function renderCharacterSheet() {
41
+ // (Keep the logic from the previous renderCharacterSheet function)
42
+ // ...
43
  const char = gameState.character;
44
 
45
  charNameInput.value = char.name;
 
50
  charXPSpan.textContent = char.xp;
51
  charXPNextSpan.textContent = char.xpToNextLevel;
52
 
 
53
  char.stats.hp = Math.min(char.stats.hp, char.stats.maxHp);
54
  charHPSpan.textContent = char.stats.hp;
55
  charMaxHPSpan.textContent = char.stats.maxHp;
56
 
 
57
  for (const stat in statSpans) {
58
  if (statSpans.hasOwnProperty(stat) && char.stats.hasOwnProperty(stat)) {
59
  statSpans[stat].textContent = char.stats[stat];
60
  }
61
  }
62
 
63
+ charInventoryList.innerHTML = '';
 
64
  const maxSlots = 15;
65
  for (let i = 0; i < maxSlots; i++) {
66
  const li = document.createElement('li');
 
73
  itemSpan.textContent = item;
74
  li.appendChild(itemSpan);
75
  } else {
 
76
  const emptySlotSpan = document.createElement('span');
77
  emptySlotSpan.classList.add('item-slot');
78
+ emptySlotSpan.textContent = '[Empty]'; // Set text directly
79
  li.appendChild(emptySlotSpan);
80
  }
81
  charInventoryList.appendChild(li);
82
  }
83
 
84
+ updateLevelUpAvailability(); // Handles button disabling logic
 
 
 
 
85
  }
86
 
 
 
 
87
  function calculateStatIncreaseCost() {
88
+ // (Keep the same logic)
89
+ return (gameState.character.level * 10) + 5;
90
  }
91
 
 
 
 
92
  function updateLevelUpAvailability() {
93
  const char = gameState.character;
94
  const canLevelUp = char.xp >= char.xpToNextLevel;
95
  levelUpButton.disabled = !canLevelUp;
96
 
97
+ const cost = calculateStatIncreaseCost();
98
+ const canIncreaseWithXP = char.xp >= cost;
99
+ const canIncreaseWithPoints = char.availableStatPoints > 0;
100
+
101
  statIncreaseButtons.forEach(button => {
102
+ button.disabled = !(canIncreaseWithPoints || canIncreaseWithXP);
103
+ // Optionally disable if level up is pending
 
104
  // button.disabled = button.disabled || canLevelUp;
105
  });
106
 
107
+ // Update cost/points display text
108
+ statIncreaseCostSpan.textContent = cost;
109
+ statPointsAvailableSpan.textContent = char.availableStatPoints;
 
 
 
 
 
110
  }
111
 
112
+
 
 
113
  function handleLevelUp() {
114
+ // (Keep the same logic)
115
+ const char = gameState.character;
116
  if (char.xp >= char.xpToNextLevel) {
117
  char.level++;
118
+ char.xp -= char.xpToNextLevel;
119
+ char.xpToNextLevel = Math.floor(char.xpToNextLevel * 1.6);
120
+ char.availableStatPoints += char.statPointsPerLevel;
121
 
 
122
  const conModifier = Math.floor((char.stats.constitution - 10) / 2);
123
+ const hpGain = Math.max(1, Math.floor(Math.random() * 6) + 1 + conModifier);
124
  char.stats.maxHp += hpGain;
125
+ char.stats.hp = char.stats.maxHp;
126
 
127
+ console.log(`Leveled Up to ${char.level}! Gained ${char.statPointsPerLevel} stat point(s) and ${hpGain} HP.`);
128
+ renderCharacterSheet();
129
  } else {
130
  console.warn("Not enough XP to level up yet.");
131
  }
132
  }
133
 
 
 
 
134
  function handleStatIncrease(statName) {
135
+ // (Keep the same logic, including point spending priority)
136
  const char = gameState.character;
137
  const cost = calculateStatIncreaseCost();
138
 
 
139
  if (char.availableStatPoints > 0) {
140
  char.stats[statName]++;
141
  char.availableStatPoints--;
142
+ console.log(`Increased ${statName} using a point. ${char.availableStatPoints} points remaining.`);
143
+ if (statName === 'constitution') { /* ... update maxHP ... */ } // Add HP update logic here if needed
 
 
 
 
 
 
 
 
 
 
 
 
144
  renderCharacterSheet();
145
+ return;
146
  }
147
 
 
148
  if (char.xp >= cost) {
149
  char.stats[statName]++;
150
  char.xp -= cost;
151
+ console.log(`Increased ${statName} for ${cost} XP.`);
152
+ if (statName === 'constitution') { /* ... update maxHP ... */ } // Add HP update logic here if needed
 
 
 
 
 
 
 
 
 
 
 
 
153
  renderCharacterSheet();
154
  } else {
155
+ console.warn(`Not enough XP or points to increase ${statName}.`);
156
  }
157
  }
158
 
159
 
 
 
 
160
  function saveCharacter() {
161
  try {
162
  localStorage.setItem('textAdventureCharacter', JSON.stringify(gameState.character));
163
+ console.log('Character saved locally.');
164
+ // Update button text for confirmation - NO EMOJI
165
+ saveCharButton.textContent = 'Saved!';
166
+ saveCharButton.disabled = true; // Briefly disable
167
+ setTimeout(() => {
168
+ saveCharButton.textContent = 'Save'; // Restore original text
169
+ saveCharButton.disabled = false;
170
+ }, 1500);
171
  } catch (e) {
172
+ console.error('Error saving character:', e);
173
+ alert('Failed to save character.');
174
  }
175
  }
176
 
 
 
 
177
  function loadCharacter() {
178
+ // (Keep the same logic)
179
+ try {
180
  const savedData = localStorage.getItem('textAdventureCharacter');
181
  if (savedData) {
182
  const loadedChar = JSON.parse(savedData);
183
+ gameState.character = { ...gameState.character, ...loadedChar, stats: { ...gameState.character.stats, ...(loadedChar.stats || {}) }, inventory: loadedChar.inventory || [] };
184
+ console.log('Character loaded from local storage.');
185
+ return true;
 
 
 
 
 
 
 
 
 
186
  }
187
+ } catch (e) { console.error('Error loading character:', e); }
188
+ return false;
 
 
 
189
  }
190
 
 
 
 
191
  function exportCharacter() {
192
+ // (Keep the same logic)
193
+ try {
194
+ const charJson = JSON.stringify(gameState.character, null, 2);
195
  const blob = new Blob([charJson], { type: 'application/json' });
196
  const url = URL.createObjectURL(blob);
197
+ const a = document.createElement('a'); a.href = url;
 
 
198
  const filename = `${gameState.character.name.replace(/[^a-z0-9]/gi, '_').toLowerCase() || 'character'}_save.json`;
199
+ a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url);
200
+ console.log(`Character exported as ${filename}`);
201
+ } catch (e) { console.error('Error exporting character:', e); alert('Failed to export character data.'); }
 
 
 
 
 
 
 
202
  }
203
 
204
+
205
+ // --- Event Listeners ---
206
+ // (Keep the same event listeners, they reference elements by ID)
207
  charNameInput.addEventListener('change', () => {
208
  gameState.character.name = charNameInput.value.trim() || "Hero";
209
+ console.log(`Name changed to: ${gameState.character.name}`);
 
 
 
210
  });
 
211
  levelUpButton.addEventListener('click', handleLevelUp);
 
212
  statIncreaseButtons.forEach(button => {
213
  button.addEventListener('click', () => {
214
  const statToIncrease = button.dataset.stat;
215
+ if (statToIncrease) { handleStatIncrease(statToIncrease); }
 
 
216
  });
217
  });
 
218
  saveCharButton.addEventListener('click', saveCharacter);
219
  exportCharButton.addEventListener('click', exportCharacter);
220
 
 
222
  // --- Modify Existing Functions ---
223
 
224
  function startGame() {
225
+ // (Keep the same logic, including loadCharacter and default merging)
226
+ if (!loadCharacter()) { console.log("No saved character found, starting new."); }
 
 
 
 
227
  gameState.character = {
228
+ ...{ name: "Hero", race: "Human", alignment: "Neutral Good", class: "Fighter", level: 1, xp: 0, xpToNextLevel: 100, statPointsPerLevel: 1, availableStatPoints: 0, stats: { strength: 7, intelligence: 5, wisdom: 5, dexterity: 6, constitution: 6, charisma: 5, hp: 30, maxHp: 30 }, inventory: [] },
229
+ ...gameState.character
 
 
 
 
 
230
  };
231
+ gameState.character.stats = {
232
+ strength: 7, intelligence: 5, wisdom: 5, dexterity: 6, constitution: 6, charisma: 5, hp: 30, maxHp: 30,
233
+ ...(gameState.character.stats || {})
234
+ }
235
+ gameState.currentPageId = 1;
236
+ renderCharacterSheet();
237
+ renderPage(gameState.currentPageId);
 
 
238
  }
239
 
240
  function handleChoiceClick(choiceData) {
241
+ // (Keep the same logic, including reward processing)
242
+ const nextPageId = parseInt(choiceData.nextPage); const itemToAdd = choiceData.addItem; if (isNaN(nextPageId)) { console.error("Invalid nextPageId:", choiceData.nextPage); return; }
243
+ if (itemToAdd && !gameState.character.inventory.includes(itemToAdd)) { gameState.character.inventory.push(itemToAdd); console.log("Added item:", itemToAdd); }
244
+ gameState.currentPageId = nextPageId; const nextPageData = gameData[nextPageId];
245
+ if (nextPageData) { if (nextPageData.hpLoss) { gameState.character.stats.hp -= nextPageData.hpLoss; console.log(`Lost ${nextPageData.hpLoss} HP.`); if (gameState.character.stats.hp <= 0) { gameState.character.stats.hp = 0; console.log("Player died from HP loss!"); renderCharacterSheet(); renderPage(99); return; } }
246
+ if (nextPageData.reward) { if (nextPageData.reward.xp) { gameState.character.xp += nextPageData.reward.xp; console.log(`Gained ${nextPageData.reward.xp} XP!`); } if (nextPageData.reward.statIncrease) { const stat = nextPageData.reward.statIncrease.stat; const amount = nextPageData.reward.statIncrease.amount; if (gameState.character.stats.hasOwnProperty(stat)) { gameState.character.stats[stat] += amount; console.log(`Stat ${stat} increased by ${amount}!`); if (stat === 'constitution') { /* ... update maxHP ... */ } } } if(nextPageData.reward.addItem && !gameState.character.inventory.includes(nextPageData.reward.addItem)){ gameState.character.inventory.push(nextPageData.reward.addItem); console.log(`Found item: ${nextPageData.reward.addItem}`); } }
247
+ if (nextPageData.gameOver) { console.log("Reached Game Over page."); renderCharacterSheet(); renderPage(nextPageId); return; }
248
+ } else { console.error(`Data for page ${nextPageId} not found!`); renderCharacterSheet(); renderPage(99); return; }
249
+ renderCharacterSheet(); renderPage(nextPageId);
250
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
 
 
 
 
 
 
252
 
253
+ // --- REMEMBER ---
254
+ // Make sure to remove the OLD #stats-display and #inventory-display elements from your HTML
255
+ // and the old updateStatsDisplay() and updateInventoryDisplay() functions from your JS.
 
 
256
 
257
+ // --- Initialization ---
258
+ // initThreeJS(); // Keep your Three.js initialization
259
+ startGame();