Type something to search...
Using agents to breathe life into NPCs | by MichaelT Shomsky | Dec, 2024 | Medium

Using agents to breathe life into NPCs | by MichaelT Shomsky | Dec, 2024 | Medium

Outline

  • Simulating 2d societies
  • Create a 2d top down platformer
  • Setup non playable characters that maintain a personal history
  • Begin a framework to allow NPCs to conduct themselves in a 2d playable space
  • Github Repo
  • Outcomes/Conclusions

Simulating 2d Societies

Simulating the world is important enough to me that, when I was an undergrad, I simulated polymers for a physics group and coded light propagation in free space for my advisors group. This “want to simulate” was certainly guided by my honors engineering class; as during my freshman year in college, we focused almost exclusively on physics simulations. My final project in that class was a simulated world in C++, in 2d, and populated with two groups of virtual-life: student-researchers and teachers. When a simulated student-researcher discoverd a valueable trait, it could share that information via a non-mobile teacher to distribute the knowledge. In retrospect, the self-directed project wasn’t a very good project as it mostly demonstrated that knowledge-sharing constrained sharing useful attributes that would be beneficial to a larger group. What the project did accomplish was step outside the norm of the class and ponder a simulated society that shares meta-information not entirely associated with physics.

Recently in 2023, I encountered an exciting paper about a 2d society called Smallville ( https://arxiv.org/pdf/2304.03442 ), which made me think back to my first 2d society. Smallville used LLMs in a new way: not only to generate information but maintain a set of virtual inhabitants. I was intrigued by how it enabled NPCs (non-playable-characters) to maintain backstories, personal history, and the ability to lead a rich virtual life (as dictated by a large language model).

Intrigued by the notion of using LLMs to breathe life into a small group of NPCs got me curious about how quickly I could put something similar together and what could I learn from the endeavor.

Conversation Party: (Inspired by Smallville)

Imagine a game where virtual players automously chat, share stories, and respond to each other. The project “Conversation Party” is a unique blend of gaming and AI. You the player may control the simulation by adding character backstories and guide interactions by moving the characters closer to each other to converse.

Create a 2d top down platformer

Overview of the Technologies

To realize this interactive simulation, we employ:

  • Pygame: A powerful gaming library used for rendering visuals, handling events, and facilitating player movements within the game grid.
  • OpenAI: The backbone for generating AI-driven conversational responses, providing dynamic interactions between virtual players.
  • Faker: An essential tool for generating unique player profiles with randomized names and background stories, adding depth to our virtual characters.

Character and World Features

  • Player Movement (starting with randomness)

Movement Logic: Players navigate the grid while obeying boundaries. Rocks serve as obstacles, requiring players to strategize their paths or simply to impede movement in all directions.

Simulated autonomy (version 1): initially we start with random character movements, if two characters are close to each other, they talk and then move away.

Player/Observer movement: the player is assigned to a character by clicking on one, when assigned, the character can be moved by the player/observer.

Future Feature: We start with random movement , but we’ll want to have more meaningful movement when we enable our NPCs.

AI Conversations

  • Integrating OpenAI for player conversations
  • Saving conversations to retain character depth

Character Dialogues: Players engage in conversations driven by AI-generated responses.

Conversation Continuity: AI is responsible for generating coherent responses based on historical conversation logs, embedding realism into player interactions. Conversation data is persisted for continuity between runs.

Conversation History: Logged conversations create a continuous interaction history, enhancing story depth and player engagement over multiple sessions.

Data Files:

  • The world is defined by a CSV text file
  • player name + bio is also stored in CSV

Game states, including world setups and player data, are saved to CSV files. This ensures game progress can be preserved and reloaded.

Here’s an example world I started with:

world.csv

grass,grass,grass,grass,rock,grass,grass,water,grass,grass
grass,grass,grass,grass,water,grass,rock,grass,water,grass
grass,water,grass,grass,grass,grass,grass,grass,water,water
water,grass,grass,grass,rock,grass,grass,grass,grass,grass
grass,grass,grass,grass,grass,grass,grass,grass,water,water
grass,grass,grass,grass,grass,water,water,grass,rock,water
grass,grass,water,grass,grass,grass,grass,rock,grass,grass
water,water,grass,water,grass,grass,grass,grass,grass,rock
water,grass,grass,grass,grass,grass,grass,water,water,water
water,grass,grass,grass,grass,water,grass,grass,rock,grass
player,Carly Cummings,4,1,1,100,100, Loves bacon, searching for bacon
player,Katherine Jones,3,0,100,100,100,Build a house,loves animals
player,Ashley Brown,1,7,100,100,100,finds nemo,loves fish

Our initial Game

  • The python Faker library populates our users names with synthetic first name and last names
  • As a player, Bio and Tasks can be edited when the game is paused. This will be used to guide conversations of our characters
  • When the game is unpaused: some interactions through chance encounters result in conversations based on the individual’s Bio, Task, and history

Simple Game Overview

Simple Game Description

Game Initialization:

  • Load resources (images, world data) and initialize players.
  • Set initial positions and load conversation history.

Game Loop:

  1. Event Handling (User Input):
  • Detects user interactions such as keyboard presses and mouse clicks.
  • Pauses or resumes the game when the pause button is clicked.
  • Movement commands: Up, Down, Left, Right.
  • Update selected player if clicked on a player.

2. Update State:

  • If the game is NOT paused:
  • - Move the active player.
  • - Move inactive players and check for nearby interactions to start conversations.
  • - Handle player interactions, including responses to initiated conversations.
  • If the game IS paused:
  • - Click on the player to review/edit and make active (this results in the ability to move when unpaused)
  • - Update the active user’s bio or task (note: it may go off the screen, but it’s also possible to update the CSV if this is a problem)
  • - Use Up/Down arrow to scroll through the most recent conversation

3. Render:

  • Draw game world and players.
  • Update player stats, bio, and tasks from input boxes when paused.
  • Render UI elements, like chat notifications and pause button.

Player Interactions:

  • Proximity Detection: When a player is near another player, a conversation can be initiated.
  • Conversation Handling:
  • - Generate conversation text and request a response using the OpenAI client.
  • Save and log conversation history.

Python Game Requirements File

requirements.txt

pygame
faker
openai

Image Assets for the Pygame:

- https://github.com/mtshomskyieee/agenticCity/tree/main/assets

Conversation Party Python Game

conversation_party.py

import pygame
import random
import os
import csv
from faker import Faker
import openai
from openai import OpenAI

## Uncomment below if you want to set an openai key, or if it's not set in the environment
## os.environ["OPENAI_API_KEY"] = "ADD KEY HERE"

## Initialize Pygame and Faker
pygame.init()
fake = Faker()

## Screen dimensions and other constants
GRID_SIZE = 10
CELL_SIZE = 40
WIDTH, HEIGHT = GRID_SIZE * CELL_SIZE * 2, GRID_SIZE * CELL_SIZE
FPS = 30
NUM_PLAYERS = 3  # Number of players in the game
SCROLL_SPEED = 5
RESPONSE_BOX_HEIGHT = 100


## Display setup
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Conversation Party")
clock = pygame.time.Clock()
WRAPPED_LINES = ""
## Global variable to track chatting state
is_chatting = False


## Function to load and resize images
def load_image(filename, size):
    image = pygame.image.load(filename)
    return pygame.transform.scale(image, size)


## Load and resize images
assets_dir = 'assets'
grass_img = load_image(os.path.join(assets_dir, 'grass.png'), (CELL_SIZE, CELL_SIZE))
rock_img = load_image(os.path.join(assets_dir, 'rock.png'), (CELL_SIZE, CELL_SIZE))
water_img = load_image(os.path.join(assets_dir, 'water.png'), (CELL_SIZE, CELL_SIZE))
player_imgs = [load_image(os.path.join(assets_dir, f'player_{i}.png'), (CELL_SIZE, CELL_SIZE)) for i in range(5)]


## Create a 2D array for the world or load from CSV
def generate_world(grid_size):
    world = [['grass' for _ in range(grid_size)] for _ in range(grid_size)]
    for row in range(grid_size):
        for col in range(grid_size):
            rand = random.random()
            if rand < 0.1:
                world[row][col] = 'rock'
            elif rand < 0.3:
                world[row][col] = 'water'
    return world


def load_world_from_csv(filename):
    with open(filename, mode='r') as file:
        reader = csv.reader(file)
        return [row for row in reader]


def save_world_to_csv(filename, world, players):
    with open(filename, mode='w', newline='') as file:
        writer = csv.writer(file)
        for row in world:
            writer.writerow(row)
        # Save player positions, names, bios, and tasks
        for player in players:
            writer.writerow(['player', player.name, player.x, player.y, player.stats['Health'], player.stats['Speed'],
                             player.stats['Strength'], player.bio, player.tasks])


def generate_unique_position(existing_positions):
    while True:
        x, y = random.randint(0, GRID_SIZE - 1), random.randint(0, GRID_SIZE - 1)
        if (x, y) not in existing_positions:
            return x, y


conversation_dir = 'conversations'
os.makedirs(conversation_dir, exist_ok=True)

## Global conversation log
global_conversation_log = []


def save_conversation_history(player):
    filename = os.path.join(conversation_dir, f"{player.name.replace(' ', '_')}_conversation.txt")
    with open(filename, 'w') as file:
        file.write("\n".join(player.all_responses))


def load_conversation_history(player):
    filename = os.path.join(conversation_dir, f"{player.name.replace(' ', '_')}_conversation.txt")
    if os.path.exists(filename):
        with open(filename, 'r') as file:
            player.all_responses = file.read().splitlines()


def save_global_conversations():
    filename = os.path.join(conversation_dir, 'global_conversation.txt')
    with open(filename, 'w') as file:
        for entry in global_conversation_log:
            file.write(entry + "\n")


client = OpenAI(
    # This is the default and can be omitted
    api_key=os.environ.get("OPENAI_API_KEY"),
)


def llm_request(conversation_list, request_string):
    global is_chatting
    is_chatting = True

    # Before making a blocking call, update display to show chat status.
    draw_chatting_notification(screen)
    pygame.display.flip()

    # Combine the conversation history with the request string.
    conversation_history = "\n".join(conversation_list)
    # Construct the full prompt
    prompt = f"{conversation_history}\n{request_string}"
    try:
        # Make an API call using the chat-based endpoint
        response = client.chat.completions.create(
            model="gpt-4o-mini",  
            messages=[
                {"role": "system", "content": "You are a human conversing."},
                {"role": "user", "content": prompt}
            ],
        )
        # Extract and return the response text from the message
        response_message = response.choices[0].message.content
        is_chatting = False
        return response_message

    except Exception as e:
        is_chatting = False
        # Handle errors appropriately
        return f"An error occurred: {str(e)}"


def wrap_text(text, font, max_width):
    """Wraps the text to fit within the specified width."""
    words = text.split(' ')
    wrapped_lines = []
    current_line = ""

    for word in words:
        # Check the width of the new line if we add this word
        if font.size(current_line + word)[0] <= max_width:
            current_line += word + " "
        else:
            wrapped_lines.append(current_line)
            current_line = word + " "

    if current_line:
        wrapped_lines.append(current_line)

    return wrapped_lines


## Button class for handling the Pause/Unpause button
class Button:
    def __init__(self, x, y, w, h, text):
        self.rect = pygame.Rect(x, y, w, h)
        self.text = text
        self.color = (173, 216, 230)  # Light blue
        self.outline_color = (0, 0, 0)  # Black for outline
        self.font = pygame.font.Font(None, 36)

    def draw(self, screen):
        # Draw the outline
        pygame.draw.rect(screen, self.outline_color, self.rect, 2)  # 2 pixels border
        # Draw the inside of the button
        pygame.draw.rect(screen, self.color, self.rect.inflate(-4, -4))
        # Render the text
        text_surf = self.font.render(self.text, True, (0, 0, 0))
        text_rect = text_surf.get_rect(center=self.rect.center)
        screen.blit(text_surf, text_rect)

    def is_clicked(self, event):
        return event.type == pygame.MOUSEBUTTONDOWN and self.rect.collidepoint(event.pos)


## Button setup
button_width, button_height = 120, 40
pause_button = Button(WIDTH - button_width - 10, HEIGHT - button_height - 10, button_width, button_height, "Pause")


class Player:
    def __init__(self, x, y, image):
        self.x = x
        self.y = y
        self.image = image
        self.name = fake.name()
        self.stats = {'Health': 100, 'Speed': 5, 'Strength': 10}
        self.bio = fake.text(max_nb_chars=100)
        self.tasks = fake.text(max_nb_chars=100)
        self.current_conversation = ""
        self.all_responses = []
        self.health = 100
        self.speed = 100
        self.strength = 100
        self.current_scroll = 0
        load_conversation_history(self)

    def who_am_i_string(self):
        return f"I am {self.name}.My background is: {self.bio}.I am currently {self.tasks}."

    def generate_conversation(self):
        self.current_conversation = self.who_am_i_string()
        response_string = llm_request(self.all_responses, self.current_conversation)
        self.all_responses.append(response_string)
        return response_string

    def respond_conversation(self, text, other_player_name):
        self.current_conversation = self.who_am_i_string()
        self.current_conversation += f"I am talking to someone who is saying: {text}"
        self.current_conversation += "In one sentence, what should I respond with"
        response_string = llm_request(self.all_responses, self.current_conversation)
        self.all_responses.append(response_string)
        print(f"{self.name} convo:{self.current_conversation}")
        print(f"{self.name} response:{response_string}")

        # Add to global conversation log
        global_conversation_log.append(f"{self.name} (talking to {other_player_name}): {response_string}")

        save_conversation_history(self)
        return response_string


def find_nearby_player(player, players):
    for other_player in players:
        if other_player != player:
            if abs(player.x - other_player.x) <= 1 and abs(player.y - other_player.y) <= 1:
                return other_player
    return None


## Check if world.csv exists
world_filename = 'world.csv'

if os.path.exists(world_filename):
    data = load_world_from_csv(world_filename)
    world = [row for row in data if row[0] not in ['player']]
    players_data = [row for row in data if row[0] == 'player']

    players = []
    for player_data in players_data:
        _, name, x, y, health, speed, strength, bio, tasks = player_data
        player = Player(int(x), int(y), player_imgs[len(players)])
        player.name = name
        player.stats = {'Health': int(health), 'Speed': int(speed), 'Strength': int(strength)}
        player.bio = bio
        player.tasks = tasks
        load_conversation_history(player)
        players.append(player)
else:
    world = generate_world(GRID_SIZE)
    # Initialize players with unique positions
    players = []
    existing_positions = set()
    for i in range(NUM_PLAYERS):
        x, y = generate_unique_position(existing_positions)
        existing_positions.add((x, y))
        players.append(Player(x, y, player_imgs[i]))

    save_world_to_csv(world_filename, world, players)

active_player_idx = random.randint(0, NUM_PLAYERS - 1)


def move_player(player, dx, dy):
    new_x, new_y = player.x + dx, player.y + dy
    if 0 <= new_x < GRID_SIZE and 0 <= new_y < GRID_SIZE and world[new_x][new_y] != 'rock':
        # Check if the new position is occupied by another player
        if not any(p.x == new_x and p.y == new_y for p in players):
            player.x, player.y = new_x, new_y


def move_away(player, other_player):
    directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
    random.shuffle(directions)
    for dx, dy in directions:
        new_x, new_y = player.x + dx, player.y + dy
        if 0 <= new_x < GRID_SIZE and 0 <= new_y < GRID_SIZE and world[new_x][new_y] != 'rock':
            if not any(p.x == new_x and p.y == new_y for p in players):
                player.x, player.y = new_x, new_y
                break


def move_inactive_players(players, active_idx):
    for i, player in enumerate(players):
        if i != active_idx:
            nearby_player = find_nearby_player(player, players)
            if nearby_player:
                conversation = player.generate_conversation()
                response = nearby_player.respond_conversation(conversation, player.name)
                print(f"{player.name}: Converation {conversation}")
                print(f"{nearby_player.name}: Response {response}")
                player.all_responses.append(response)
                move_away(player, nearby_player)
                move_away(nearby_player, player)
            else:
                dx, dy = random.choice([(0, 1), (1, 0), (0, -1), (-1, 0)])
                move_player(player, dx, dy)


def draw_world(surface, world, players):
    for row in range(GRID_SIZE):
        for col in range(GRID_SIZE):
            image = grass_img if world[row][col] == 'grass' else rock_img if world[row][col] == 'rock' else water_img
            surface.blit(image, (col * CELL_SIZE, row * CELL_SIZE))
    for player in players:
        surface.blit(player.image, (player.y * CELL_SIZE, player.x * CELL_SIZE))


## Update draw_stats to show recent response
def draw_stats(surface, player, x_offset, y_offset):
    global WRAPPED_LINES # For UI calculations
    font = pygame.font.Font(None, 36)
    y = y_offset

    # Display player name
    name_text = font.render(player.name, True, (0, 0, 0))
    surface.blit(name_text, (x_offset, y))
    y += 40


    # Static positions for Bio and Tasks
    bio_label_y = y
    tasks_label_y = y + 40

    # Render the labels
    bio_label = font.render('Bio:', True, (0, 0, 0))
    tasks_label = font.render('Tasks:', True, (0, 0, 0))

    # Display Bio and Tasks in the same section
    surface.blit(bio_label, (x_offset, bio_label_y))
    surface.blit(tasks_label, (x_offset, tasks_label_y))

    # Display recent response
    if player.all_responses:
        y+=80

        wrapped_lines = wrap_text('Recent: ' + player.all_responses[-1], font, WIDTH - x_offset - 20)
        WRAPPED_LINES = wrapped_lines
        # Scrolling debug
        # print(f"{player.current_scroll}, {len(wrapped_lines)} :  {player.current_scroll + (RESPONSE_BOX_HEIGHT // 20)} : {WRAPPED_LINES},")
        for line in WRAPPED_LINES[player.current_scroll: player.current_scroll + (RESPONSE_BOX_HEIGHT // 20)]:
            surface.blit(font.render(line, True, (0, 0, 0)), (x_offset, y))
            y += 20

        # Scroll indicator (optional)
        if len(wrapped_lines) > (RESPONSE_BOX_HEIGHT // 20) and paused:
            surface.blit(font.render("[Up/Down]:Scroll", True, (155, 155, 155)), (x_offset, y+10))


    # Draw input boxes in paused mode
    if paused:
        bio_box.rect.topleft = (x_offset + 80, bio_label_y)
        tasks_box.rect.topleft = (x_offset + 80, tasks_label_y)
        bio_box.draw(screen)
        tasks_box.draw(screen)
    else:
        # Unpaused, display current Bio and Tasks
        bio_text = font.render(player.bio, True, (0, 0, 0))
        tasks_text = font.render(player.tasks, True, (0, 0, 0))
        surface.blit(bio_text, (x_offset + 80, bio_label_y))
        surface.blit(tasks_text, (x_offset + 80, tasks_label_y))

## Function to draw "Chatting..." notification
def draw_chatting_notification(surface):
    notification_rect = pygame.Rect(WIDTH - 300, HEIGHT - 50, 150, 40)
    pygame.draw.rect(surface, (200, 200, 255), notification_rect)  # White box
    pygame.draw.rect(surface, (0, 0, 0), notification_rect, 2)  # Black border
    font = pygame.font.Font(None, 30)
    text_surf = font.render("Chatting...", True, (0, 0, 0))
    surface.blit(text_surf, (notification_rect.x + 10, notification_rect.y + 10))


class InputBox:
    def __init__(self, x, y, w, h, text=''):
        self.rect = pygame.Rect(x, y, w, h)
        self.color_active = (173, 216, 230)  # Light blue
        self.color_inactive = (0, 0, 0)  # Black
        self.text = text
        self.txt_surface = pygame.font.Font(None, 36).render(text, True, self.color_inactive)
        self.active = False
        self.enter_pressed = False  # Track if Enter has been pressed

    def handle_event(self, event, paused):
        if event.type == pygame.MOUSEBUTTONDOWN:
            # Toggle the active variable.
            if self.rect.collidepoint(event.pos):
                if paused:  # Only activate if the game is paused
                    self.active = not self.active
                else:
                    self.active = False
        if event.type == pygame.KEYDOWN:
            if self.active:
                if event.key == pygame.K_RETURN:
                    self.enter_pressed = True  # Mark Enter as pressed
                elif event.key == pygame.K_BACKSPACE:
                    self.text = self.text[:-1]
                else:
                    self.text += event.unicode
                # Re-render the text.
                self.txt_surface = pygame.font.Font(None, 36).render(self.text, True, self.color_active if self.active else self.color_inactive)

    def draw(self, screen):
        # Blit the text.
        screen.blit(self.txt_surface, (self.rect.x + 5, self.rect.y + 5))
        # Blit the rect.
        pygame.draw.rect(screen, self.color_active if self.active else self.color_inactive, self.rect, 2)

    def update_text(self, text):
        self.text = text
        self.txt_surface = pygame.font.Font(None, 36).render(text, True,
                                                             self.color_active if self.active else self.color_inactive)

    def reset_enter_pressed(self):
        self.enter_pressed = False
def get_player_at_pos(players, x, y):
    for i, player in enumerate(players):
        player_rect = pygame.Rect(player.y * CELL_SIZE, player.x * CELL_SIZE, CELL_SIZE, CELL_SIZE)
        if player_rect.collidepoint(x, y):
            return i
    return None

## Initial configuration
sub_epoch = 30  # time increments
idle_mod = 20 % sub_epoch  # update cycle for idle things
idle_count = 0
running = True
paused = False
selected_player = players[active_player_idx]

## Create input boxes for player attributes
stat_boxes = [
    InputBox(GRID_SIZE * CELL_SIZE + 100, 100 + i * 40, 100, 32, str(getattr(selected_player, stat.lower())))
    for i, stat in enumerate(['Health', 'Speed', 'Strength'])
]
## 260, 350
bio_box = InputBox(GRID_SIZE * CELL_SIZE + 20, 20, 300, 32, selected_player.bio)
tasks_box = InputBox(GRID_SIZE * CELL_SIZE + 20, 50, 300, 32, selected_player.tasks)
#input_boxes = stat_boxes + [bio_box, tasks_box]
input_boxes = [bio_box, tasks_box]

while running:
    idle_count = (idle_count + 1) % sub_epoch
    for event in pygame.event.get():
        if pause_button.is_clicked(event):
            paused = not paused
            pause_button.text = "Unpause" if paused else "Pause"
        if event.type == pygame.QUIT:
            save_world_to_csv(world_filename, world, players)
            for player in players:
                save_conversation_history(player)
            save_global_conversations()
            running = False
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = event.pos
            clicked_player_idx = get_player_at_pos(players, mouse_x, mouse_y)
            if clicked_player_idx is not None:
                active_player_idx = clicked_player_idx
                selected_player = players[active_player_idx]
                for stat, box in zip(selected_player.stats.values(), stat_boxes):
                    box.update_text(str(stat))
                bio_box.update_text(selected_player.bio)
                tasks_box.update_text(selected_player.tasks)

        # Handle input boxes even if paused
        for box in input_boxes:
            box.handle_event(event, paused)

    # Process other game logic only if not paused
    if not paused:
        keys = pygame.key.get_pressed()
        if keys[pygame.K_UP]:
            move_player(players[active_player_idx], -1, 0)
        if keys[pygame.K_DOWN]:
            move_player(players[active_player_idx], 1, 0)
        if keys[pygame.K_LEFT]:
            move_player(players[active_player_idx], 0, -1)
        if keys[pygame.K_RIGHT]:
            move_player(players[active_player_idx], 0, 1)

        if idle_count % idle_mod == 0:
            move_inactive_players(players, active_player_idx)

    else:  # When paused, allow scrolling
        keys = pygame.key.get_pressed()
        if keys[pygame.K_UP]:
            if selected_player.current_scroll > 0:
                selected_player.current_scroll -= SCROLL_SPEED
        elif keys[pygame.K_DOWN]:
            if selected_player.current_scroll < len(WRAPPED_LINES) - (RESPONSE_BOX_HEIGHT // 20):
                selected_player.current_scroll += SCROLL_SPEED

    screen.fill((255, 255, 255))
    draw_world(screen, world, players)

    for stat, box in zip(selected_player.stats.keys(), stat_boxes):
        try:
            value = int(box.text)
            selected_player.stats[stat] = value
        except ValueError:
            pass
    selected_player.bio = bio_box.text
    selected_player.tasks = tasks_box.text

    draw_stats(screen, selected_player, GRID_SIZE * CELL_SIZE + 20, 10)

    # allow input updates if paused
    if paused:
        for box in input_boxes:
            box.draw(screen)

    # Draw the pause/unpause button
    pause_button.draw(screen)

    # Draw chatting notification if chatting is enabled
    if is_chatting:
        draw_chatting_notification(screen)


    pygame.display.flip()
    clock.tick(FPS)

pygame.quit()

Outcomes/Conclusions

  • Games, like simulations, care about some simulacrum of time. In our case time is measured by iterations of the main loop. Since the main loop is constantly running, but also sometimes allowing edits which can interrupt, resulted in the addition of a “pause/unpause” button. That way we could stop time when necessary.
  • With initial charcters set to have bios like “loves animals”, “loves fish”, and “searching for bacon”, it lacked the depth that it could have which should result in richer interactions.
  • With the current simplistic setup, there were an enormous amount of recipe sharing; which confims that the base game is working enough that it is time to vary the parameters and see what kind of conversations can occur.

Next Steps

Github Repo

If you found this article insightful, please consider clapping for this piece — it not only supports the author but also helps others discover valuable insights. Additionally, don’t forget to subscribe for more articles that delve into innovative technologies and AI developments. Your engagement is greatly appreciated!

Sources mentioned

  • Park, Joon Sung, et al. “Generative Agents: Interactive Simulacra of Human Behavior.” arXiv preprint arXiv:2304.03442 (2023). Available at: https://arxiv.org/pdf/2304.03442

Related Posts

10 Creative Ways to Use ChatGPT Search The Web Feature

10 Creative Ways to Use ChatGPT Search The Web Feature

For example, prompts and outputs Did you know you can use the “search the web” feature of ChatGPT for many tasks other than your basic web search? For those who don't know, ChatGPT’s new

Read More
📚 10 Must-Learn Skills to Stay Ahead in AI and Tech 🚀

📚 10 Must-Learn Skills to Stay Ahead in AI and Tech 🚀

In an industry as dynamic as AI and tech, staying ahead means constantly upgrading your skills. Whether you’re aiming to dive deep into AI model performance, master data analysis, or transform trad

Read More
10 Powerful Perplexity AI Prompts to Automate Your Marketing Tasks

10 Powerful Perplexity AI Prompts to Automate Your Marketing Tasks

In today’s fast-paced digital world, marketers are always looking for smarter ways to streamline their efforts. Imagine having a personal assistant who can create audience profiles, suggest mar

Read More
10+ Top ChatGPT Prompts for UI/UX Designers

10+ Top ChatGPT Prompts for UI/UX Designers

AI technologies, such as machine learning, natural language processing, and data analytics, are redefining traditional design methodologies. From automating repetitive tasks to enabling personal

Read More
100 AI Tools to Finish Months of Work in Minutes

100 AI Tools to Finish Months of Work in Minutes

The rapid advancements in artificial intelligence (AI) have transformed how businesses operate, allowing people to complete tasks that once took weeks or months in mere minutes. From content creat

Read More
17 Mindblowing GitHub Repositories You Never Knew Existed

17 Mindblowing GitHub Repositories You Never Knew Existed

Github Hidden Gems!! Repositories To Bookmark Right Away Learning to code is relatively easy, but mastering the art of writing better code is much tougher. GitHub serves as a treasur

Read More