Skip to content

Commit

Permalink
Merge pull request #1 from Jayanaka-98:docs_jayanaka
Browse files Browse the repository at this point in the history
Docs_jayanaka
  • Loading branch information
Jayanaka-98 committed Jul 5, 2024
2 parents 6a5582f + 9a7fff6 commit bce9f93
Showing 1 changed file with 204 additions and 1 deletion.
205 changes: 204 additions & 1 deletion docs/docs/md/docs/tutorials/rpg_game.md
Original file line number Diff line number Diff line change
@@ -1 +1,204 @@
# Creating a Level Generator for an RPG Game
# Creating a Level Generator for an RPG Game

Procedurally generated maps in video games has become a hot topic in the recent years among the gaming community. the algorithms for generating these maps are extremely complected, and requires months of development to build such algorithms. Instead of symbolically written programs, what if we can use generative models to generate these maps?

In this Tutorial we will show you how you can generate game maps for a simple game where the map can be expressed using a list of strings.

## What is a level?

A level can be represented in this hypothetical game using a list of strings shown below.

```python
glob level = [ 'BBBBBBBBBB',
'B....E...B',
'B.P......B',
'B.....B..B',
'B.....BE.B',
'B....BBBBB',
'B....B...B',
'B.....E..B',
'BBBBBBBBBB' ];
```

In this level each character represent a different element in the map,

- 'B': Walls
- '.': Floor
- 'E': Enemy
- 'P': Player

## Building a level map generator?

The straightforward approach to build a map generator is to ask from the LLM to directly generate such a map as a list of strings. MTLLM allows you to do this by defining a function or a method. However, here we would discuss a more object oriented way of programming with LLMs which allow the model to 'think' using objects.

### Level Configuration

```python | level_manager.jac
obj Level {
has name: str,
difficulty: int;
has width: int,
height: int,
num_wall: int,
num_enemies: int;
has time_countdown: int,
n_retries_allowed: int;
}
```

Each level should have a basic configuration which describes the level in an abstract format. This level object embeds the difficulty of the level and the number of enemies and obstacles including other level configuration parameters.

However, filling in the values for fields requires a cognitive capacity, for which will use an LLM later on.

### Building the Map Elements

- **A coordinate system**

```python
obj Position {
has x: int,
y: int;
}
```

As the map we are aiming to generate is a 2D map the position of each object on the map can be designated using the ```Position``` custom type. It is simply representing a cartesian 2D coordinate system.

- **Object to Represent a Wall**

```python
obj Wall {
has start_pos: Position,
end_pos: Position;
}
```

The wall object represents a straight wall, as all obstacles in the 2D map can be represented by a collection of intersecting wall objects. Here each wall object will have a start position as well as a stop position

- **Map represented using objects**

```python
obj Map {
has level: Level;
has walls: list[Wall],
small_obstacles: list[Position];
has enemies: list[Position];
has player_pos: Position;
}
```

This Map object will hold the exact positions of all objects in the map. This is the object that we will generate using MT-LLM. Each field of this object is one of or a derivative of the custom types which we described above.

### The Level Manager

To manage all the generations we can define a Level manager object which can hold a directory of previous levels configurations and maps, which can be used to feed the LLM to give context about the play style of the player. We will be using the OpenAI GPT-4o as the LLM in this tutorial.

```python
import:py from mtllm.llms, OpenAI;
glob llm = OpenAI(model_name="gpt-4o");

obj LevelManager {
has current_level: int = 0,
current_difficulty: int = 1,
prev_levels: list[Level] = [],
prev_level_maps: list[Map] = [];

'''Generates the Next level configuration based upon previous playthroughs'''
can create_next_level( last_levels: list[Level],
difficulty: int,
level_width: int,
level_height: int) -> Level by llm(temperature=1.0);

'''Get the Next Level'''
can get_next_level -> tuple(Level, Map);

'''Generate the Map as a List of Strings'''
can get_map(map: Map) -> list[str];
}
```

We have three methods defined under the level manager. Each will handle a separate set of tasks.

- ```create_next_level``` : Takes in previous level configuration data from previously played levels and generate the new level configuration parameters and output a ```Level``` object which describes the new map, using the LLM.

- ```get_next_level``` : Uses the ```create_next_level``` to generate the ```Level``` config. object which is then used to fill in the rest of a newly initiated ```Map``` object using an LLM. This is where the actual map generation happens. Still the generated map cannot be visualize.

- ```get_map``` : This method will generate the actual list of strings which can be used with an actual game using the ```Map``` object generated by ```get_next_level``` method. This does not require any LLM as all objects of the map are included in the ```Map``` object with their exact positions.

The implementation of the above methods are as follows.

```python
:obj:LevelManager:can:get_next_level {
self.current_level += 1;
# Keeping Only the Last 3 Levels
if len(self.prev_levels) > 3 {
self.prev_levels.pop(0);
self.prev_level_maps.pop(0);
}
# Generating the New Level
new_level = self.create_next_level(
self.prev_levels,
self.current_difficulty,
20, 20
);
self.prev_levels.append(new_level);
# Generating the Map of the New Level
new_level_map = Map(level=new_level by llm());
self.prev_level_maps.append(new_level_map);
# Increasing the Difficulty for end of every 2 Levels
if self.current_level % 2 == 0 {
self.current_difficulty += 1;
}
return (new_level, new_level_map);
}
```

In the ```get_next_level``` method there are two llm calls which we will discuss in this tutorial while other parts are related the functionality of the game.

- ```Line 9-13``` : Here the saved data from previous levels are given as inputs which are defined previously along with the basic level config parameters of the new level. As the output type of this method was specified above to be a ```Level``` object the LLM will initiate and fill in the values of the objects. As the LLM hyperparameter temperature, is set for 1.0 at method declaration, the LLM is forced to be more creative.

- ```Line 14``` : Here the programmer is initiating a Map object while passing in only the level parameter with the newly generated ```level``` object and ask the LLM to fill in the rest of the fields by generating the relevant types. This nested type approach ensures the output is formatted according to how you expect them to be.

```python
:obj:LevelManager:can:get_map {
map_tiles = [['.' for _ in range(map.level.width)] for _ in range(map.level.height)];
for wall in map.walls {
for x in range(wall.start_pos.x, wall.end_pos.x + 1) {
for y in range(wall.start_pos.y, wall.end_pos.y + 1) {
map_tiles[y-1][x-1] = 'B';
}
}
}
for obs in map.small_obstacles {
map_tiles[obs.y-1][obs.x-1] = 'B';
}

for enemy in map.enemies {
map_tiles[enemy.y-1][enemy.x-1] = 'E';
}
map_tiles[map.player_pos.y-1][map.player_pos.x-1] = 'P';
map_tiles = [['B'] + row + ['B'] for row in map_tiles];
map_tiles = [['B' for _ in range(map.level.width + 2)]] + map_tiles + [['B' for _ in range(map.level.width + 2)]];
return [''.join(row) for row in map_tiles];
}
```

### Running the Program

```python
with entry {
level_manager = LevelManager();
for _ in range(2) {
(new_level, new_level_map) = level_manager.get_next_level();
print(new_level);
print('\n'.join(LevelManager.get_map(new_level_map)));
}
}
```

This program will now generate two consecutive maps and print them on the terminal. by running this jac file using ```jac run``` you can simply test your program.

## A full scale game demo

For the sake of this tutorial we have not included the entire development of an actual game. The full game is available on our [jac-lang repo](https://github.com/Jaseci-Labs/jaclang/tree/main/examples/rpg_game). A sample demonstration of the game can be viewed below.

[Demo Video](https://drive.google.com/file/d/1JXyWbmI6vJsjpNUnscRxdnK5vmo8312r/view?usp=sharing)

0 comments on commit bce9f93

Please sign in to comment.