Ashes of Time: Surviving the AI Apocalypse

Inspiration

Ashes of Time started as an experiment inspired by classic resource management and survival games like 60 Seconds! and Oregon Trail. The core idea was to create a tense, bunker-survival experience where the narrative wasn't pre-scripted but dynamically generated, making each play through feel unique and unpredictable. I wanted to explore how generative AI could be used as a "dungeon master" or storyteller, reacting to the player's situation and choices to weave a narrative within a defined game structure. The goal was high replayability driven by both player decisions and AI creativity, all wrapped in a post-apocalyptic theme.

How It Was Built

This project is built as a web application using the Next.js framework (based on React).

Tech Stack & Utilities:

  • Framework: Next.js 15 / React 19
  • Language: JavaScript (ES6+)
  • AI: Google Gemini API (specifically gemini-2.0-flash via a Next.js API route)
  • State Management: React Hooks (useReducer for core game state, useState, useEffect, useCallback for component logic and side effects)
  • Styling: Tailwind CSS v4 (with custom theme colors defined in globals.css)
  • UI Components: Functional React components
  • Icons: lucide-react

Core Features:

  • Day-by-day survival loop.
  • Resource management (Food, Water).
  • Survivor management (Health, Statuses, Companions).
  • AI-driven event generation via a backend API route.
  • Player choice system impacting game state.
  • Randomized overarching game themes per playthrough (e.g., 'Nuclear Winter', 'Mutated Beasts') to guide AI generation.
  • Player-initiated hunting action with a QTE (Quick Time Event) mini-game.

The game loop revolves around the useGameState hook managing state via gameStateReducer. Each day (after the first), it constructs a promptContext including the current day, resources, survivor details, the game's theme, and the previous day's outcome. This context is sent to a Next.js API route (/api/gemini), which crafts a detailed prompt for the Google Gemini API.

The API route includes robust parsing logic (tryParseJson) to handle the AI's response, extracting the event description and choices into a structured JSON format. This JSON is sent back to the frontend, updating the state via the reducer, presenting the player with the new situation and choices. Actions like choosing an event option or initiating the hunting mini-game dispatch actions to the reducer, which updates the state, applies daily consumption/status effects, checks for game over, and triggers the fetch for the next day's event.

Challenges Faced

  • AI Consistency: Getting the Gemini API to consistently return valid, correctly structured JSON was a primary challenge. Prompt engineering became crucial, involving clear instructions, examples, and even server-side filtering (like the survivor limit check) to enforce game rules the AI might occasionally ignore.

  • Prompt Engineering: Crafting prompts that encouraged varied, thematic, and engaging events without being overly repetitive required iteration. Balancing specific instructions (like JSON format) with creative freedom for the AI was key.

  • State Management Complexity: Using useReducer worked well, but the main reducer function grew quite large as features like companion naming and the hunting mini-game (with its different state flow) were added. Keeping the day progression logic consistent across different action types (APPLY_CHOICE, RESOLVE_HUNTING, FINISH_NAMING_COMPANION) required careful handling.

  • QTE Implementation: Implementing the hunting QTE involved managing timers (setTimeout), adding/removing event listeners correctly within React's lifecycle (useEffect), and handling the different phases of the mini-game state within the HuntSelection component.

  • Game Balancing: Tuning resource costs, gains (especially from hunting), health changes, and the frequency/impact of negative statuses is an ongoing process to ensure the game is challenging but fair.

What I Learned

Building Ashes of Time was a fantastic learning experience:

  • AI Integration: Gained practical experience integrating a powerful LLM like Gemini into a dynamic application, including prompt design, API communication, and handling unpredictable responses.

  • React State Management: Deepened my understanding of useReducer for managing complex, interconnected state and handling various action types within a game loop.

  • React Hooks: Leveraged useEffect extensively for managing side effects like API calls, timers, and event listeners, including the importance of cleanup functions. useCallback was essential for optimizing event handlers passed down to child components.

  • Next.js: Utilized Next.js for both the frontend React application and the backend API route, providing a streamlined full-stack development experience.

  • Asynchronous JavaScript: Handled multiple asynchronous operations involved in API calls, state updates, and timed events (like the QTE).

Overall, this project demonstrates how AI can be integrated to create dynamic and replayable narrative experiences within a structured game framework.

Built With

Share this project:

Updates