Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
30 changes: 30 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,32 @@
*.pyc
myenv/
venv/
logs/
.github
ppo_plane_tensorboard/

# Virtual environments
venv/
venv1/

# Python cache and bytecode
__pycache__/
*.pyc
*.pyo
*.pyd

# Distribution / packaging
build/
dist/
*.egg-info/

# VSCode / PyCharm / macOS files
.vscode/
.idea/
.DS_Store

# Jupyter Notebook checkpoints
.ipynb_checkpoints/

# Logs
*.log
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[![Demo](https://i.imgur.com/xpUow2f.png)](https://youtu.be/W5fCgqlECeI)

# python_mini_metro
This repo uses `pygame` to implement Mini Metro, a fun 2D strategic game where you try to optimize the max number of passengers your metro system can handle. Both human and program inputs are supported. One of the purposes of this implementation is to enable reinforcement learning agents to be trained on it.
# python_mini_plane
This repo uses `pygame` to implement Mini plane, a fun 2D strategic game where you try to optimize the max number of passengers your plane system can handle. Both human and program inputs are supported. One of the purposes of this implementation is to enable reinforcement learning agents to be trained on it.

# Installation
`pip install -r requirements.txt`
Expand Down
Binary file added models/.DS_Store
Binary file not shown.
Binary file added models/PPO/.DS_Store
Binary file not shown.
Binary file added models/PPO/1763002825/.DS_Store
Binary file not shown.
Binary file added models/final_model.zip
Binary file not shown.
Binary file added models/vec_normalize.pkl
Binary file not shown.
51 changes: 47 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,47 @@
numpy==1.24.2
pygame==2.3.0
shapely==2.0.1
shortuuid==1.0.11
absl-py==2.3.1
ale-py==0.11.2
cloudpickle==3.1.2
contourpy==1.3.3
cycler==0.12.1
Farama-Notifications==0.0.4
filelock==3.20.0
fonttools==4.60.1
fsspec==2025.10.0
grpcio==1.76.0
gymnasium==1.2.2
Jinja2==3.1.6
kiwisolver==1.4.9
Markdown==3.10
markdown-it-py==4.0.0
MarkupSafe==3.0.3
matplotlib==3.10.7
mdurl==0.1.2
mpmath==1.3.0
networkx==3.5
numpy==2.2.6
opencv-python==4.12.0.88
packaging==25.0
pandas==2.3.3
pillow==12.0.0
protobuf==6.33.0
psutil==7.1.3
pygame==2.6.1
Pygments==2.19.2
pyparsing==3.2.5
python-dateutil==2.9.0.post0
pytz==2025.2
rich==14.2.0
sb3_contrib==2.7.0
setuptools==80.9.0
shapely==2.1.2
shortuuid==1.0.13
six==1.17.0
stable_baselines3==2.7.0
sympy==1.14.0
tensorboard==2.20.0
tensorboard-data-server==0.7.2
torch==2.9.1
tqdm==4.67.1
typing_extensions==4.15.0
tzdata==2025.2
Werkzeug==3.1.3
Binary file added src/.DS_Store
Binary file not shown.
35 changes: 20 additions & 15 deletions src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@
screen_width = 1920
screen_height = 1080
screen_color = (255, 255, 255)

# station
num_stations = 10
station_size = 30
station_capacity = 12
station_color = (0, 0, 0)
station_shape_type_list = [
border_padding = 100

# airport
num_airports = 10
airport_size = 30
airport_capacity = 12
airport_color = (0, 0, 0)
airport_shape_type_list = [
ShapeType.RECT,
ShapeType.CIRCLE,
ShapeType.TRIANGLE,
ShapeType.CROSS,
]
station_passengers_per_row = 4
airport_passengers_per_row = 4
airport_spawn_interval = 1250

# passenger
passenger_size = 5
Expand All @@ -28,13 +30,13 @@
passenger_spawning_interval_step = 10 * framerate
passenger_display_buffer = 3 * passenger_size

# metro
num_metros = 4
metro_size = 30
metro_color = (200, 200, 200)
metro_capacity = 6
metro_speed_per_ms = 150 / 1000 # pixels / ms
metro_passengers_per_row = 3
# plane
num_planes = 4
plane_size = 30
plane_color = (200, 200, 200)
plane_capacity = 6
plane_speed_per_ms = 150 / 1000 # pixels / ms
plane_passengers_per_row = 3

# path
num_paths = 3
Expand All @@ -55,3 +57,6 @@
# text
score_font_size = 50
score_display_coords = (20, 20)

airport_max_passengers = 6
overcrowd_time_limit_ms = 20_000 # 20 seconds
16 changes: 9 additions & 7 deletions src/entity/station.py → src/entity/airport.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,26 @@
import pygame
from shortuuid import uuid # type: ignore

from config import station_capacity, station_passengers_per_row, station_size
from config import airport_capacity, airport_passengers_per_row, airport_size
from entity.holder import Holder
from geometry.point import Point
from geometry.shape import Shape


class Station(Holder):
class Airport(Holder):
def __init__(self, shape: Shape, position: Point) -> None:
super().__init__(
shape=shape,
capacity=station_capacity,
id=f"Station-{uuid()}-{shape.type}",
capacity=airport_capacity,
id=f"Airport-{uuid()}-{shape.type}",
)
self.size = station_size
self.size = airport_size
self.position = position
self.passengers_per_row = station_passengers_per_row
self.passengers_per_row = airport_passengers_per_row
self.overcrowd_start_time = 0
self.is_overcrowded = False

def __eq__(self, other: Station) -> bool:
def __eq__(self, other: Airport) -> bool:
return self.id == other.id

def __hash__(self):
Expand Down
63 changes: 47 additions & 16 deletions src/entity/get_entity.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,57 @@
import random
from typing import List

from config import screen_height, screen_width
from entity.metro import Metro
from entity.station import Station
from utils import get_random_position, get_random_station_shape
from config import screen_height, screen_width, airport_size
from entity.plane import Plane
from entity.airport import Airport
from geometry.point import Point
from geometry.type import ShapeType
from utils import get_random_position, get_random_airport_shape, get_shape_from_type


def get_random_station() -> Station:
shape = get_random_station_shape()
def get_new_random_airport() -> Airport:
all_shape_types = list(ShapeType)

weights_map = {
ShapeType.CIRCLE: 0.40,
ShapeType.TRIANGLE: 0.30,
ShapeType.RECT: 0.20,
ShapeType.CROSS: 0.10
}

ordered_weights = [weights_map[shape_type] for shape_type in all_shape_types]

chosen_shape_type = random.choices(all_shape_types, ordered_weights, k=1)[0]

position = get_random_position(screen_width, screen_height)
return Station(shape, position)
shape = get_shape_from_type(chosen_shape_type, (0, 0, 0), airport_size)
return Airport(shape, position)

def get_initial_airports() -> List[Airport]:
airports: List[Airport] = []

initial_shapes = [ShapeType.TRIANGLE, ShapeType.CIRCLE, ShapeType.RECT]

positions = [
Point(screen_width * 0.25, screen_height * 0.3),
Point(screen_width * 0.75, screen_height * 0.3),
Point(screen_width * 0.5, screen_height * 0.7),
]

def get_random_stations(num: int) -> List[Station]:
stations: List[Station] = []
for _ in range(num):
stations.append(get_random_station())
return stations
for i, shape_type in enumerate(initial_shapes):
shape = get_shape_from_type(shape_type, (0, 0, 0), airport_size)
airport = Airport(shape, positions[i])
airports.append(airport)

return airports

def get_random_airport() -> Airport:
shape = get_random_airport_shape()
position = get_random_position(screen_width, screen_height)
return Airport(shape, position)

def get_metros(num: int) -> List[Metro]:
metros: List[Metro] = []
def get_planes(num: int) -> List[Plane]:
planes: List[Plane] = []
for _ in range(num):
metros.append(Metro())
return metros
planes.append(Plane())
return planes
32 changes: 0 additions & 32 deletions src/entity/metro.py

This file was deleted.

Loading