# Project export: LoreKraft

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: Unleash infinite adventures with AI driven dynamic storytelling. Your Dungeon Master, reimagined!
- Devpost: https://devpost.com/software/lorekraft
- GitHub: https://github.com/plasya/LoreKraft
- Team: 3 GitHub contributor(s) — Suriya Kumar (8 commits), jayanta-banik (5 commits), pamarthilasya (3 commits)

## Devpost submission (written by the team)

### Overview

LoreKraft: The Future of MMORPGs Motivation: What happens when broke grad students, armed with a love for AI and late-night RPG marathons, dream big? You get LoreKraft, an AI-driven MMORPG engine with a twist—AI Dungeon Masters orchestrating vast and dynamic worlds. We were inspired by the idea of replacing the conventional Dungeon Master with an AI expert who could generate epic adventures on the fly. With the rise of Generative AI, transformers, and multi-agent systems, we saw an opportunity to revolutionize RPG gaming into something more immersive, smarter, and more unpredictable—just like the real world of adventuring! We didn’t just want an RPG; we wanted an engine where multiple AI agents collaborate, much like a council of wise wizards, to create infinite storylines. The idea of multi-agent systems intrigued us—AI as a Dungeon Master that knows the lore, tracks player stats, and even conjures up epic narratives in real-time! What We Learned: Berkeley’s hackathon culture taught us one thing: why spend your weekend snacking when you could be hacking? We plunged into the depths of multi-agent systems and learned the true magic of AI-driven collaboration. It’s one thing to have a chatbot, but getting multiple AI agents to work together harmoniously? That’s an entirely different game! We learned how cutting-edge models like Gemini can be leveraged for creative text generation, while GPT-4 function calls take care of table queries and stats tracking. Beyond the coding, we dove into the intricacies of game mechanics, narrative pacing, and how to maintain an engaging multiplayer experience, all orchestrated through intelligent agents. How We Built It: LoreKraft’s foundation lies in a multi-agent system where each agent plays a distinct role in the game’s ecosystem. Here's the technical breakdown: Creative Text Generation: We utilized Gemini AI to generate dynamic, immersive narratives, giving life to the AI Dungeon Master that never gets tired of spinning epic tales. Creative Text Generation: We utilized Gemini AI to generate dynamic, immersive narratives, giving life to the AI Dungeon Master that never gets tired of spinning epic tales. GPT Function Calls: For database queries and knowledge retrieval, we relied on GPT’s function calling capabilities to fetch player stats and interact with the game world seamlessly. GPT Function Calls: For database queries and knowledge retrieval, we relied on GPT’s function calling capabilities to fetch player stats and interact with the game world seamlessly. Retrieval-Augmented Generation (RAG): We incorporated RAG models to retrieve knowledge from the database, ensuring that player attributes, inventory, and past actions were always at the AI's fingertips. Retrieval-Augmented Generation (RAG): We incorporated RAG models to retrieve knowledge from the database, ensuring that player attributes, inventory, and past actions were always at the AI's fingertips. Union of Experts (Agent-Based Collaboration): Each AI agent had a specific task—whether it was map generation, combat event creation, or managing delayed trigger events. These agents operated like a team of expert Dungeon Masters, constantly collaborating to build a robust game engine that responds dynamically to player input. Union of Experts (Agent-Based Collaboration): Each AI agent had a specific task—whether it was map generation, combat event creation, or managing delayed trigger events. These agents operated like a team of expert Dungeon Masters, constantly collaborating to build a robust game engine that responds dynamically to player input. Frontend with Reflex AI: On the frontend, we implemented Reflex AI to create a seamless, interactive interface. The dynamic game board was rendered based on the AI’s decisions in real-time, providing instant feedback to the players. Frontend with Reflex AI: On the frontend, we implemented Reflex AI to create a seamless, interactive interface. The dynamic game board was rendered based on the AI’s decisions in real-time, providing instant feedback to the players. Node.js for Session Management: We utilized Node.js to handle player sessions, allowing for multiplayer interaction and saving the state of each player’s game. Node.js for Session Management: We utilized Node.js to handle player sessions, allowing for multiplayer interaction and saving the state of each player’s game. Backend with Flask: For the backend, Flask was our framework of choice, ensuring smooth communication between our AI agents and the player interface. Backend with Flask: For the backend, Flask was our framework of choice, ensuring smooth communication between our AI agents and the player interface. Database: We employed a hybrid system—SingleStoreDB for fast retrieval and analytics of game data, and MongoDB to manage dynamic, unstructured data like character traits and lore information. Database: We employed a hybrid system—SingleStoreDB for fast retrieval and analytics of game data, and MongoDB to manage dynamic, unstructured data like character traits and lore information. Snap Spectacles for Immersive Experience: To take things up a notch, we tried integrating Snap Spectacles to allow players to experience the game world in augmented reality, where AI could dynamically alter the environment around them, blending the virtual with the real. Snap Spectacles for Immersive Experience: To take things up a notch, we tried integrating Snap Spectacles to allow players to experience the game world in augmented reality, where AI could dynamically alter the environment around them, blending the virtual with the real. Challenges We Faced: What’s a hackathon without some technical dragons to slay? Here are a few: Multi-Agent Orchestration: Managing multiple AI agents to work in harmony presented synchronization issues. Making sure all agents were on the same page without overwhelming the system took some delicate balancing. Multi-Agent Orchestration: Managing multiple AI agents to work in harmony presented synchronization issues. Making sure all agents were on the same page without overwhelming the system took some delicate balancing. Data Optimization: With so much data being passed between AI agents and the database, we faced challenges with optimizing data retrieval and storage. We worked hard to ensure fast queries using hybrid database solutions. Data Optimization: With so much data being passed between AI agents and the database, we faced challenges with optimizing data retrieval and storage. We worked hard to ensure fast queries using hybrid database solutions. Unstable Beta Products: We tried pushing the limits with beta AI tools and platforms, but sometimes they weren’t quite ready for production-level use. While we planned some groundbreaking features, a few had to be scaled back due to instability in beta models. Unstable Beta Products: We tried pushing the limits with beta AI tools and platforms, but sometimes they weren’t quite ready for production-level use. While we planned some groundbreaking features, a few had to be scaled back due to instability in beta models. Session Handling at Scale: Handling multiple players while maintaining persistent sessions and ensuring smooth transitions between game states required some significant optimization work on the Node.js side. Session Handling at Scale: Handling multiple players while maintaining persistent sessions and ensuring smooth transitions between game states required some significant optimization work on the Node.js side. Pitch Idea: For the presentation, we want to generate the entire pitch live using the same AI-driven game engine we've built! Our Dungeon Master AI will craft the narrative of the project as we demo, bringing the technical elements to life through creative storytelling. The agents will work together to present how they built LoreKraft, while seamlessly transitioning between technical explanations, player interactions, and visual frames—giving the judges a real sense of the power of AI collaboration. Final Thoughts: LoreKraft is more than just a game engine—it's a platform that could revolutionize MMORPGs by utilizing multi-agent systems. Imagine a world where multiple AI agents act like experts, building, managing, and constantly evolving a game world tailored to each player's decisions. This kind of intelligent orchestration can bring depth and immersion to games, unlike anything seen before. We’re not just building a game; we’re building a future where AI and human creativity unite to craft limitless adventures.

## README (from the GitHub repository)

# LoreKraft: The Future of MMORPGs
> **An AI-driven MMORPG engine with AI Dungeon Masters creating endless adventures.**


![image](https://github.com/user-attachments/assets/35e6f7d7-b5a7-4eab-84fe-7db6f5a8935f)

---

## Motivation
What happens when broke grad students, fueled by a passion for AI and late-night RPG marathons, dream big? You get **LoreKraft**—an AI-powered MMORPG engine where AI Dungeon Masters collaborate to generate dynamic and engaging worlds in real-time. With Generative AI, multi-agent systems, and our love for gaming, we set out to revolutionize RPGs into something more immersive, unpredictable, and intelligent.


---

## What We Learned
In Berkeley’s hackathon culture, we learned why we’d rather spend our weekends hacking than snacking! We dove into multi-agent systems, understanding how to create harmony among AI agents and push the limits of AI collaboration. Our efforts in combining creative text generation, efficient data handling, and game mechanics led us to a groundbreaking RPG experience powered by multi-agent AI.


---

## How We Built It

LoreKraft is built on a foundation of **multi-agent collaboration** where each agent has a specific role in the game’s ecosystem. Here’s a technical breakdown:

1. **Creative Text Generation**  
   Using **Gemini AI**, we generated immersive narratives to keep players engaged. Our AI Dungeon Master spun tales without tiring, adding endless creative twists to the gameplay.

2. **GPT Function Calls for Database Management**  
   With GPT-4’s function calling, we handled database queries and knowledge retrieval, tracking player stats and game dynamics seamlessly.

3. **Retrieval-Augmented Generation (RAG)**  
   We used RAG models to retrieve knowledge from our databases, ensuring that player attributes, inventory, and history were instantly accessible to AI agents.

![image](https://github.com/user-attachments/assets/00ce1047-8a8c-4b16-a2c1-76283791991b)

4. **Union of Experts (Agent-Based Collaboration)**  
   Each agent specialized in a task—whether map generation, combat events, or managing delayed events. Together, they collaborated like a council of Dungeon Masters, enhancing player experience.

5. **Frontend with Reflex AI**  
   We leveraged Reflex AI for a dynamic, interactive interface, allowing players to see the AI’s decisions reflected on the game board in real-time.

6. **Node.js for Session Management**  
   With **Node.js**, we maintained player sessions, supporting multiplayer interactions and state persistence.

7. **Backend with Flask**  
   **Flask** facilitated smooth communication between AI agents and the frontend, ensuring fast and reliable performance.

8. **Database Management**  
   A hybrid approach with **SingleStoreDB** for quick retrieval and **MongoDB** for handling unstructured data like character traits.

9. **Augmented Reality with Snap Spectacles**  
   For immersive gameplay, we integrated **Snap Spectacles** to bring the game world to life, blending virtual and real environments.


---

## Challenges We Faced

1. **Multi-Agent Orchestration**  
   Synchronizing multiple agents was a challenge, ensuring consistent responses without overwhelming the system.

2. **Data Optimization**  
   Efficient data handling between agents and databases was essential, prompting us to optimize retrieval and storage using hybrid databases.

3. **Beta Instability**  
   Working with cutting-edge AI tools in beta stages sometimes introduced instability, requiring us to adapt our approach for reliability.

4. **Session Handling at Scale**  
   Ensuring smooth transitions between game states and persistent sessions for multiple players was complex but rewarding.


---
## Implementation
![image](https://github.com/user-attachments/assets/1390c700-ac4b-4a63-9f4c-ba52695f3e4c)
![image](https://github.com/user-attachments/assets/17759a29-57ee-4786-92de-2001e597660b)
![image](https://github.com/user-attachments/assets/ad89b160-1a4e-4eba-95fe-d80b9313eca1)
---

## Final Thoughts
LoreKraft isn’t just a game engine; it’s a revolutionary approach to MMORPGs. Using multi-agent systems, we’re creating a world where multiple AI agents collaborate to build, manage, and evolve a game world based on player decisions. This kind of intelligent orchestration can offer unparalleled immersion and adaptability, crafting unique adventures for every player.


---

## Built With
- **fetchai**
- **flask**
- **gemini**
- **gpt**
- **hume**
- **javascript**
- **python**
- **reflex**
- **ripple**
- **singlestore**
- **snapchat**
- **vapi**

---

## Acknowledgments
Special thanks to the Berkeley Hackathon team and our mentors for their guidance and support throughout this project.

---

> Ready to experience the next level of MMORPGs? Join us in the adventure with LoreKraft!

---



## Detected evidence (automated analysis)

Indexed codebase: 15 recognized source files, 76 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- Flask (technology) — claimed on Devpost, not found in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (16 of 16)

```
app.py
game-engine/dungeon_master.py
game-engine/image_gen.py
game-engine/map_gen_assistant.py
game-engine/map_outline.py
game-engine/validation_assistant.py
libs/DoublyLinkedList.py
libs/MapStruct.py
LICENSE
README.md
static/login_style.css
static/login.js
static/src/game.css
templates/game.html
templates/guild.html
templates/login.html
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Update README.md
- Update README.md
- sdfd
- test
- update
- Merge pull request #10 from plasya/game-engine
- Merge pull request #9 from plasya/main
- Create map_outline.py
- Created dungeon_master.py
- Merge pull request #8 from plasya/game-engine
- Create map according to theme
- [update] renamed some filenames
- [deleted] deleted zip file
- Merge pull request #2 from plasya/game-engine
- Created Validation assistant using gpt-4o
- Created DoublyLinkedList
- Add files via upload
- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### app.py

```python
from flask import Flask, render_template, request, redirect, url_for, flash, session, jsonify
from collections import deque
import random
import string
from libs.MapStruct import Map, generate_random_numbers
from flask_cors import CORS
from flask_pymongo import PyMongo
from bson import json_util, ObjectId
import numpy as np 
from image_gen import image_gen 
# Configure MongoDB connection (example with MongoDB Atlas)
# Initialize PyMongo

# def create_new_map(d, start, end):
#     n_common, n_triggers, n_friendly, n_delayed, delayed_numbers = generate_random_numbers(len(d))
#     return Map(d, start, end, n_common, n_triggers, n_friendly, n_delayed, delayed_numbers)

guilds = {}



app = Flask(__name__)
app.secret_key = 'I_am_upto_no_good'  # Required for session management and flashing messages
app.config["MONGO_URI"] = "mongodb+srv://joy:lol.java@website.fn5zw.mongodb.net/Hackathon?retryWrites=true&w=majority"
CORS(app)
mongo = PyMongo(app)

users = {
    "testuser": "password123",
    "surya": 'qwerty123',
    "lasya":'lol',
    "joy": '1121'
}

@app.route('/')
def home():
    return render_template('login.html')

@app.route('/test')
def sometest():
    try:
        # Hardcoded MongoDB query similar to SQL:
        # SELECT * FROM collection_name WHERE image_id IS NOT NULL
        query = { }

        # Query MongoDB (assuming 'collection_name' is the name of your collection)
        documents = mongo.db.characters.find(query)
        # Convert MongoDB cursor to list and then to JSON format
        data = eval(json_util.dumps(documents))
        data = [i for i in data if i["image_ids"]]

        return jsonify({"status": "success", "data": data}), 200

    except Exception as e:
        return jsonify({"status": "error", "message": str(e)}), 500

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']

    # Check if the username exists and the password is correct
    if username in users and users[username] == password:
        session['username'] = username  # Save the username in the session
        flash('Login successful!', 'success')
        return render_template('guild.html') #redirect(url_for('guild_create_join'))  # Redirect to welcome page
    else:
        flash('Invalid username or password. Please try again.', 'error')
        return redirect(url_for('home'))


@app.route('/game')
def game():
    return render_template('game.html')

@app.route('/guild_create_joinpage', methods=['POST'])
def guild_create_joi():
    data = request.get_json()
    gcode = data['guildCode']
    task = data['task']
    if task == 'join':
        if gcode not in guilds:
            return jsonify({'success':0})
        session['guildcode'] = gcode  
    if task == 'create':  
        if gcode not in guilds:
            guilds[gcode] = 1
            session['guildcode'] = gcode  
        else:
            return jsonify({'success':0})
    return jsonify({'success':1}) #redirect(url_for('game'))

@app.route('/imagen', methods = ['POST'])
def imag_gen():
    data = request.get_json()
    p = data["prompt"]
    str1 = imag_gen(p)
    return str1

@app.route('/narato')
def ttpts():
    
    return jsonify({'data': "lasya is cutie pie"})

@app.route('/create_new_map', methods=['POST'])
def create_new_map():
    try:
        # Get JSON data from the POST request
        data = request.get_json()

        # Extract 'd', 'start', and 'end' from the JSON data
        d = data.get('d')
        start = data.get('start')
        end = data.get('end')

        if d is None or start is None or end is None:
            return jsonify({'error': 'Missing data in request'}), 400

        # Generate random numbers and create the map
        n_common, n_triggers, n_friendly, n_delayed, delayed_numbers = generate_random_numbers(len(d))
        new_map = Map(d, start, end, n_common, n_triggers, n_friendly, n_delayed, delayed_numbers)

        # Return a success response (customize the response data as needed)
        return jsonify({'message': 'Map created successfully', 'map': str(new_map)}), 200

    except Exception as e:
        return jsonify({'error': str(e)}), 500
        
@app.route('/logout')
def logout():
    session.pop('username', None)  # Remove the username from the session
    flash('You have been logged out.', 'success')
    return redirect(url_for('home'))

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

```

### game-engine/image_gen.py

```python
from openai import OpenAI
import requests
import os
import time
import random
import string

def image_gen(prompt):
    # Initialize OpenAI client
    client = OpenAI(api_key="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")
    
    # Generate the image
    response = client.images.generate(
        model="dall-e-3",
        prompt=prompt,
        size="1024x1024",
        quality="standard",
        n=1
    )
    
    # Get the URL of the generated image
    image_url = response['data'][0]['url']
    
    # Create a unique filename based on the current timestamp
    timestamp = int(time.time())
    file_name = f"generated_image_{timestamp}.png"
    
    # Define the file path where the image will be saved
    file_path = os.path.join("static/src", file_name)
    
    # Ensure the directory exists
    os.makedirs(os.path.dirname(file_path), exist_ok=True)
    
    # Download the image from the URL and save it locally
    image_data = requests.get(image_url).content
    with open(file_path, 'wb') as image_file:
        image_file.write(image_data)
    return "54.67.37.79:8000/"+file_path


```

### libs/DoublyLinkedList.py

```python
#python
class Node:
    def __init__(self, key, image):
        self.key = key       # Key for the node
        self.image = image   # Image data (e.g., file path)
        self.next = None     # Pointer to the next node
        self.prev = None     # Pointer to the previous node

class DoublyLinkedList:
    def __init__(self):
        self.head = None

    def insert(self, key, image):
        new_node = Node(key, image)
        if self.head is None:
            self.head = new_node
            return
        last = self.head
        while last.next:
            last = last.next
        last.next = new_node
        new_node.prev = last

    def delete(self, key):
        current = self.head
        while current:
            if current.key == key:
                if current.prev:
                    current.prev.next = current.next
                if current.next:
                    current.next.prev = current.prev
                if current == self.head:
                    self.head = current.next
                return
            current = current.next

    def display(self):
        current = self.head
        while current:
            print(f"Key: {current.key}, Image: {current.image}")
            current = current.next

```

### static/login.js

```javascript
document.addEventListener('DOMContentLoaded', function() {
    const loginForm = document.querySelector('form');
    const passwordInput = document.getElementById('password');
    
    loginForm.addEventListener('submit', function(event) {
        const username = document.getElementById('username').value;
        const password = passwordInput.value;

        // Basic validation
        if (!username || !password) {
            alert('Please fill in both fields.');
            event.preventDefault(); // Prevent form submission if validation fails
        }
    });

    // Handle password visibility toggle
    const togglePasswordVisibility = document.getElementById('password');
    const togglePasswordVisibilityImage = document.getElementById('peekaboo');

    togglePasswordVisibilityImage.addEventListener('click', function(event) {
        event.preventDefault();  // Prevent the anchor tag from causing any navigation
        console.log("clicked");
        // Toggle between password and text type for input
        if (togglePasswordVisibility.type === 'password') {
            togglePasswordVisibility.type = 'text';
            togglePasswordVisibilityImage.src = '/static/show.png';  // Change icon to "show" state
        } else {
            togglePasswordVisibility.type = 'password';
            togglePasswordVisibilityImage.src = '/static/hide.png';  // Change icon to "hide" state
        }
    });
});

```

### templates/login.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login Page</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='login_style.css') }}">
</head>
<body>
    <div class="container">
        <div class="login-box">
            {% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages %}
    <div class="flashes">
      {% for category, message in messages %}
        <div class="flash flash-{{ category }}">{{ message }}</div>
      {% endfor %}
    </div>
  {% endif %}
{% endwith %}

            <h2>Welcome Back</h2>
            <form action="/login" method="POST">
                <div class="input-group">
                    <label for="username">Username</label>
                    <input type="text" id="username" name="username" required>
                </div>
                <div class="input-group">
                    <label for="password">Password</label>
                    <div class="input-container">
                        <input type="password" id="password" class="custom-input" name="password" placeholder="Enter password" required>
                        <img src="{{ url_for('static', filename='hide.png') }}" alt="Show/Hide" id="peekaboo" class="input-icon">
                    </div>
                </div>
                
                <button type="submit" class="btn">Sign In</button>
                <div class="forgot-password">
                    <a href="#">Forgot username or password?</a>
                </div>
                <div class="create-account">
                    <span>Don't have an account?</span>
                    <a href="#">Create new account</a>
                </div>
                <div class="social-login">
                    <span>Or sign in with</span>
                    <div class="social-icons">
                        <a href="#"><img style="width:30px;" src="{{ url_for('static', filename='google.png') }}" alt="Gmail"></a>
                        <!-- <a href="#"><img style="width:30px;" src="{{ url_for('static', filename='facebook.png') }}" alt="Facebook"></a> -->
                        <a href="#"><img style="width:30px;" src="{{ url_for('static', filename='snapchat.png') }}" alt="Snapchat"></a>
                        <!-- <a href="#"><img style="width:30px;" src="{{ url_for('static', filename='twitter.png') }}" alt="Twitter"></a> -->
                        <a href="#"><img style="width:30px;" src="{{ url_for('static', filename='email.png') }}" alt="Email"></a>
                    </div>
                </div>
            </form>
        </div>
    </div>
    <script src="{{ url_for('static', filename='login.js') }}"></script>

</body>
</html>

```

### static/login_style.css

```css
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: 'Arial', sans-serif;
}

body {
    background-color: #1e2235; /* Dark background */
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
}

.container {
    background-color: #1e2235;
    padding: 30px;
    border-radius: 12px;
    text-align: center;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 100%;
}

.login-box {
    background-color: #1e2235;
    border-radius: 10px;
    padding: 30px;
    width: 350px;
    text-align: center;
    color: white;
}

h2 {
    font-size: 36px;
    color: white;
    margin-bottom: 20px;
    font-weight: bold;
}

.input-group {
    margin-bottom: 20px;
    text-align: left;
}

.input-group label {
    font-size: 16px;
    color: #aaa;
    display: block;
    margin-bottom: 5px;
}

.input-group input {
    width: 100%;
    padding: 12px;
    font-size: 16px;
    border-radius: 5px;
    border: 1px solid #ddd;
    background-color: #252a41; /* Darker input field */
    color: white;
}

.input-container {
    position: relative;
    width: 100%;
}

.custom-input {
    width: 100%;
    padding-right: 40px; /* Space for the icon */
    padding-left: 10px;
    padding-top: 10px;
    padding-bottom: 10px;
    font-size: 16px;
    border-radius: 5px;
    border: 1px solid #ddd;
    background-color: #252a41; /* Darker input field */
    color: white;
    box-sizing: border-box;
}

.input-icon {
    position: absolute;
    right: 10px;
    top: 50%;
    transform: translateY(-50%);
    height: 24px;
    width: 24px;
    cursor: pointer;
    z-index: 2;
}

.btn {
    padding: 12px 50px;
    font-size: 18px;
    border: none;
    border-radius: 30px;
    color: grey;
    cursor: pointer;
    transition: background-color 0.3s, transform 0.3s;
    margin: 20px 0;
    width: 100%;
    text-align: center;
}

.btn:hover {
    transform: translateY(-3px);
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}

.forgot-password, .create-account, .social-login {
    margin-top: 15px;
}

.forgot-password a, .create-account a {
    color: #aaa;
    text-decoration: none;
    font-size: 14px;
}

.social-login {
    margin-top: 20px;
    text-align: center;
}

.social-login span {
    display: block;
    margin-bottom: 10px;
    color: #aaa;
    font-size: 14px;
}

.social-icons {
    display: flex;
    justify-content: center;
    gap: 10px;
}

.social-icons img {
    width: 30px;
    height: 30px;
}

.flashes {
    margin-bottom: 20px;
}

.flash {
    padding: 10px;
    border-radius: 5px;
    margin-bottom: 10px;
    color: white;
}

.flash-success {
    background-color: #4CAF50; /* Green for success */
}

.flash-error {
    background-color: #f44336; /* Red for error */
}

```

### game-engine/map_gen_assistant.py

```python
from openai import OpenAI
import os
import json
# from dotenv import load_dotenv

# Load environment variables from .env file
# load_dotenv()

# Load API keys from environment variables
openai_api_key = "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"

# Initialize OpenAI client
client = OpenAI(api_key=openai_api_key)

def generate_dnd_map(theme, checkpoints=10):
    # Define the system instruction and user input in the message format
    messages = [
        {
            "role": "system", 
            "content": """You are a specialized system designed to generate a Dungeons & Dragons (DND) map with a given theme and a specified number of checkpoints. You will follow the detailed instruction below to ensure the map is consistent, engaging, and meets the requirements.

# Task:
Generate a DND map and the story based on the given theme and the number of checkpoints. The map must contain a starting point and an ending point, with at least 4 sequential checkpoints and the rest being either sequential or parallel paths.

# Instructions:
1. The output should always be a dictionary named `dnd_map` where the keys are unique place names, such as "Goblin Fortress" or "Ancient Shrine", and the values are lists of places they lead to.
2. Ensure the following structure:
    - The `Start` point must lead to one or more specific places.
    - The `End` point must be reached at the conclusion and have no parallel paths after it.
3. The "Start" and "End" points must have unique and specific names (e.g., "Village Outskirts" for Start, and "Royal Citadel" for End).
4. All locations must be connected — no disconnected or isolated points.
5. Avoid infinite loops or circular paths unless it serves a specific purpose, and ensure they eventually lead to the `End`.
6. There must be at least 4 levels of sequential paths. Each level can contain branching paths, but the path should still eventually lead to the `End`.
7. The names of places (e.g., "Goblin Fortress", "Dragon"s Lair", "Ancient Shrine") should be randomly generated, ensuring variety in each map.
8. Each node that leads to an empty list should represent a dead end in the game.

""" }, 
        { "role": "user", "content": f"""The theme is {theme} and the checkpoints is {checkpoints}.
"""     } 
    ]
    
    # Call GPT-4O to generate the map
    completion = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        max_tokens=1000,
        temperature=0.7
    )

    # Extract the generated map from the assistant"s response
    dnd_map_code = completion.choices[0].message.content.strip()

    # Safely extract the dictionary portion
    try:
        start = dnd_map_code.find("{")  # Find the start of the dictionary
        end = dnd_map_code.rfind("}") + 1  # Find the end of the dictionary
        map_code_str = dnd_map_code[start:end]  # Extract just the dictionary string
        dnd_map = eval(map_code_str)  # Evaluate the string safely to get the Python dictionary
    except Exception as e:
        print(f"Error while parsing map: {e}, {map_code_str}")
        return None,None

    # Convert dnd_map to JSON string to pass in the next message
    dnd_map_json = json.dumps(dnd_map)

    # Now ask for the start and end points, without appending any additional messages
    messages = [ 
        { "role": "user", 
          "content": f"Given the dictionary: {dnd_map_json}, output ONLY the starting point and end point as start: starting point, end: ending point as a dictionary and NOTHING else" }
    ]

    # Call GPT-4O again to extract start and end points
    completion = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,  # Pass the full conversation
        max_tokens=1000,
        temperature=0.7
    )

    start_end = completion.choices[0].message.content.strip()

    # Safely extract the dictionary portion for start and end points
    try:
        start = start_end.find("{")  # Find the start of the dictionary
        end = start_end.rfind("}") + 1  # Find the end of the dictionary
        map_code_str = start_end[start:end]  # Extract just the dictionary string
        start_end_dict = eval(map_code_str)  # Evaluate the string safely to get the Python dictionary
    except Exception as e:
        print(f"Error while parsing start/end points: {e}")
        return dnd_map, [dnd_map.keys()[0], dnd_map.keys()[-1]]

    # print("Start and End Points:", start_end_dict)
    return dnd_map, start_end_dict


def main(): 
    theme = "Avengers save the world from Thanos" 
    checkpoints = 10
    # Generate the DND map and get start/end points
    dnd_map, start_end_dict = generate_dnd_map(theme, checkpoints)

    # Print the generated map and start/end points
    if dnd_map:
        print("Generated DND Map:", dnd_map)
    if start_end_dict:
        print("Start and End Points:", start_end_dict)


if __name__ == "__main__":
    main()

```

### libs/MapStruct.py

```python
from collections import deque
import random
import string

def generate_random_numbers(num_max):
    """Generates 4 random numbers: n_common, n_triggers, n_friendly, and n_delayed.

    Args:
    num_max: The maximum value for the random numbers.

    Returns:
    A tuple of 4 random numbers: (n_common, n_triggers, n_friendly, n_delayed).
    """

    n_common = random.randint(num_max - 2, num_max + 6)
    n_triggers = random.randint(1, num_max // 4)
    n_friendly = random.randint(1, num_max // 3)
    n_delayed = random.randint(0, num_max)

    delayed_numbers = []
    for _ in range(n_delayed):
        delayed_numbers.append(random.randint(1, 3))

    return n_common, n_triggers, n_friendly, n_delayed, delayed_numbers

def random_gen_id(k=10):
    # Define the characters to choose from: digits and letters
    characters = string.ascii_letters + string.digits
    # Generate a random 10-character alphanumeric string
    gen_id = ''.join(random.choices(characters, k=k))
    return gen_id

class Node:
    def __init__(self, name, _id):
        self.name = name
        self._id = _id
        self.next_nodes = []
        self.prev_nodes = []
        self.events = {
            'common': [],
            'delayed': {},
            'trigger': False,
            'friendly': False
        }
    def __repr__(self):
        return f'{self.name} has {len(self.next_nodes)} paths ahead and has events \n {self.events}'
    
    def __str__(self):
        return f'{self.name} has {len(self.next_nodes)} paths ahead and has events \n {self.events}'
    
class Map:
    def __init__(self, d, start, end, n_common, n_triggers, n_friendly, n_delayed, delayed_numbers):
        list_of_nodes = {key: Node(key, random_gen_id()) for key, val in d.items()}
        self.test = list_of_nodes
        self.head = list_of_nodes[start]
        self.tail = list_of_nodes[end]
        for key, val in d.items():
            for v in val:
                list_of_nodes[key].next_nodes.append(list_of_nodes[v])
                list_of_nodes[v].prev_nodes.append(list_of_nodes[key])
        self.isvisited = {start:1}
        
#         n_common, n_triggers, n_friendly, n_delayed, delayed_numbers
        
        self.head.events['common'] = ['start the journey event'] # modify prompt
        self.tail.events['common'] = ['end the journey event'] # modify prompt
        
        list_of_n = list(list_of_nodes.keys())
        for i in random.choices(list_of_n, k=n_common):
            if i != self.head and i!= self.tail:
                list_of_nodes[i].events['common'].append('event')
        for i,e in enumerate(random.choices(list_of_n, k=n_delayed)):
            if e != self.head and e != self.tail:
                list_of_nodes[e].events['delayed'][f'event_{random_gen_id(4)}'] = delayed_numbers[i]
        for i in random.sample(list_of_n, n_triggers):
            if i != self.head and i!= self.tail:
                list_of_nodes[i].events['trigger'] = True
        for i in random.sample(list_of_n, n_friendly):
            if i != self.head and i!= self.tail:
                list_of_nodes[i].events['friendly'] = True
            
    def visit_node(self, name):
        self.isvisited[name] = 1
    
    def list_visited_nodes(self):
        return list(self.isvisited.keys())
    
    def BFS(self):
        queue = deque([self.head])  # Start with the head node
        visited = set([self.head.name])  # Track visited nodes by name
        traversal = []  # List to store the BFS order
        
        while queue:
            current_node = queue.popleft()  # Get the node at the front of the queue
            traversal.append(current_node.name)  # Add to traversal order
            self.visit_node(current_node.name)  # Mark as visited
            
            # Explore the next nodes
            for neighbor in current_node.next_nodes:
                if neighbor.name not in visited:
                    visited.add(neighbor.name)
                    queue.append(neighbor)
        
        return traversal  # Return the BFS traversal order
    
    def calc_depth(self, node_name):
        """
        Calculates the depth of a node by name starting from the head (root).
        The depth is the number of edges from the head node to the target node.
        """
        if node_name == self.head.name:
            return 0  # If the node is the head, its depth is 0

        queue = deque([(self.head, 0)])  # Queue stores (node, depth) pairs
        visited = set([self.head.name])  # Track visited nodes

        while queue:
            current_node, depth = queue.popleft()

            # Explore the next nodes
            for neighbor in current_node.next_nodes:
                if neighbor.name not in visited:
                    if neighbor.name == node_name:
                        return depth + 1  # Return the depth when node is found
                    visited.add(neighbor.name)
                    queue.append((neighbor, depth + 1))

        return -1  # Return -1 if the node is not found in the map
    
    def print_map(self):
        for i in self.test.values():
            print(i)
            print('===========================')
```

### game-engine/map_outline.py

```python
from collections import deque
import random
import string
from map_gen_assistant import generate_dnd_map


##### test case
d, start_end_dict = generate_dnd_map("Pureblood and darkknight reluctantly join up against goblins and dragons", 10)
start = start_end_dict['start'] 
end = start_end_dict['end']     

# Define the Node class for creating nodes in the map
class Node:
    def __init__(self, name: str, node_id: int):
        self.name = name
        self.id = node_id
        self.next_nodes = []  # List of pointers to next nodes
        self.prev_nodes = []  # List of pointers to previous nodes
        self.events = {
            'common': [],
            'delayed': {},
            'trigger': False,
            'friendly': False
        }

    def __repr__(self):
        return f'{self.name} has {len(self.next_nodes)} paths ahead and has events \n {self.events}'

    def __str__(self):
        return f'{self.name} has {len(self.next_nodes)} paths ahead and has events \n {self.events}'

# Function to generate random numbers for the number of events
def generate_random_numbers(num_max):
    n_common = random.randint(num_max - 2, num_max + 6)
    n_triggers = random.randint(1, num_max // 4)
    n_friendly = random.randint(1, num_max // 3)
    n_delayed = random.randint(0, num_max)

    delayed_numbers = [random.randint(1, 3) for _ in range(n_delayed)]
    return n_common, n_triggers, n_friendly, n_delayed, delayed_numbers

# Helper function to generate random ID for each node
def random_gen_id(k=10):
    characters = string.ascii_letters + string.digits
    return ''.join(random.choices(characters, k=k))

# Define the Map class to represent the DND map
class Map:
    def __init__(self, d, start, end, n_common, n_triggers, n_friendly, n_delayed, delayed_numbers):
        # Creating nodes for each location in the map
        list_of_nodes = {key: Node(key, random_gen_id()) for key, val in d.items()}
        self.test = list_of_nodes
        self.head = list_of_nodes[start]
        self.tail = list_of_nodes[end]
        self.isvisited = {start: 1}

        # Linking nodes based on the map structure
        for key, val in d.items():
            for v in val:
                list_of_nodes[key].next_nodes.append(list_of_nodes[v])
                list_of_nodes[v].prev_nodes.append(list_of_nodes[key])

        
        list_of_n = list(list_of_nodes.keys())
        for i in random.choices(list_of_n, k=n_common):
            if i != self.head and i != self.tail:
                list_of_nodes[i].events['common'].append('event')
        for i, e in enumerate(random.choices(list_of_n, k=n_delayed)):
            if e != self.head and e != self.tail:
                list_of_nodes[e].events['delayed'][f'event_{random_gen_id(4)}'] = delayed_numbers[i]
        for i in random.sample(list_of_n, n_triggers):
            if i != self.head and i != self.tail:
                list_of_nodes[i].events['trigger'] = True
        for i in random.sample(list_of_n, n_friendly):
            if i != self.head and i != self.tail:
                list_of_nodes[i].events['friendly'] = True
        # Adding events to nodes based on random selections
        self.head.events['common'] = ['start the journey event']  # Modify prompt
        self.tail.events['common'] = ['end the journey event']    # Modify prompt
        self.head.events['delayed'] = {}
        self.tail.events['delayed'] = {}
        self.head.events['trigger'] = False
        self.head.events['friendly'] = False
        self.tail.events['trigger'] = False
        self.tail.events['friendly'] = False



    def visit_node(self, name):
        self.isvisited[name] = 1

    def list_visited_nodes(self):
        return list(self.isvisited.keys())

    def BFS(self):
        queue = deque([self.head])  # Start with the head node
        visited = set([self.head.name])  # Track visited nodes by name
        traversal = []  # List to store the BFS order

        while queue:
            current_node = queue.popleft()  # Get the node at the front of the queue
            traversal.append(current_node.name)  # Add to traversal order
            self.visit_node(current_node.name)  # Mark as visited

            # Explore the next nodes
            for neighbor in current_node.next_nodes:
                if neighbor.name not in visited:
                    visited.add(neighbor.name)
                    queue.append(neighbor)

        return traversal  # Return the BFS traversal order

    def calc_depth(self, node_name):
        if node_name == self.head.name:
            return 0  # If the node is the head, its depth is 0

        queue = deque([(self.head, 0)])  # Queue stores (node, depth) pairs
        visited = set([self.head.name])

        while queue:
            current_node, depth = queue.popleft()

            for neighbor in current_node.next_nodes:
                if neighbor.name not in visited:
                    if neighbor.name == node_name:
                        return depth + 1  # Return the depth when node is found
                    visited.add(neighbor.name)
                    queue.append((neighbor, depth + 1))

        return -1  # Return -1 if the node is not found in the map

    # def print_map(self):
    #     for i in self.test.values():
    #         print(i)
    #         print('===========================')

# Function to create a new map with random events
def create_new_map(d, start, end):
    n_common, n_triggers, n_friendly, n_delayed, delayed_numbers = generate_random_numbers(len(d))
    return Map(d, start, end, n_common, n_triggers, n_friendly, n_delayed, delayed_numbers)

m = create_new_map(d, start = start, end = end)

def map_to_dict(map_obj):
    map_dict = {}
    for node_name, node_obj in map_obj.test.items():
        map_dict[node_name] = {
            'next_nodes': [n.name for n in node_obj.next_nodes],
            'events': node_obj.events
        }
    return map_dict
# Main function to create and simulate the map

[truncated — 238 more characters]
```

[5 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]