Generals.io environment compliant with PettingZoo API standard powered by Numpy.
Project description
Generals-bots is a fast-paced strategy environment where players compete to conquer their opponents' generals on a 2D grid. While the goal is simple — capture the enemy general — the gameplay combines strategic depth with fast-paced action, challenging players to balance micro and macro-level decision-making. The combination of these elements makes the game highly engaging and complex.
Highlights:
- 🚀 blazing-fast simulator: run thousands of steps per second with
numpy
-powered efficiency - 🤝 seamless integration: fully compatible with RL standards 🤸Gymnasium and 🦁PettingZoo
- 🔧 effortless customization: easily tailor environments to your specific needs
- 🔬 analysis tools: leverage features like replays for deeper insights
[!Note] This repository is based on the generals.io game (check it out, it's a lot of fun!). The one and only goal of this project is to provide a bot development platform, especially for Machine Learning based agents.
📦 Installation
You can install the latest stable version via pip
for reliable performance
pip install generals
or clone the repo for the most up-to-date features
git clone https://github.com/strakam/generals-bots
cd generals-bots
pip install -e .
🚀 Getting Started
Creating an agent is very simple. Start by subclassing an Agent
class just like
RandomAgent
or ExpanderAgent
.
You can specify your agent id
(name) and color
and the only thing remaining is to implement the act
function,
that has the signature explained in sections down below.
Usage Example (🤸 Gymnasium)
The example loop for running the game looks like this
import gymnasium as gym
from generals.agents import RandomAgent, ExpanderAgent # import your agent
# Initialize agents
agent = RandomAgent()
npc = ExpanderAgent()
# Create environment
env = gym.make("gym-generals-v0", agent=agent, npc=npc, render_mode="human")
observation, info = env.reset()
terminated = truncated = False
while not (terminated or truncated):
action = agent.act(observation)
observation, reward, terminated, truncated, info = env.step(action)
env.render()
[!TIP] Check out Wiki for more commented examples to get a better idea on how to start 🤗.
🎨 Custom Grids
Grids on which the game is played on are generated via GridFactory
. You can instantiate the class with desired grid properties, and it will generate
grid with these properties for each run.
import gymnasium as gym
from generals import GridFactory
grid_factory = GridFactory(
grid_dims=(10, 10), # Dimensions of the grid (height, width)
mountain_density=0.2, # Probability of a mountain in a cell
city_density=0.05, # Probability of a city in a cell
general_positions=[(0,3),(5,7)], # Positions of generals (i, j)
)
# Create environment
env = gym.make(
"gym-generals-v0",
grid_factory=grid_factory,
...
)
You can also specify grids manually, as a string via options
dict:
import gymnasium as gym
env = gym.make("gym-generals-v0", ...)
grid = """
.3.#
#..A
#..#
.#.B
"""
options = {"grid": grid}
# Pass the new grid to the environment (for the next game)
env.reset(options=options)
Grids are created using a string format where:
.
represents passable terrain#
indicates impassable mountainsA, B
mark the positions of generals- digits
0-9
represent cities, where the number specifies amount of neutral army in the city, which is calculated as40 + digit
🔬 Interactive Replays
We can store replays and then analyze them in an interactive fashion. Replay
class handles replay related functionality.
Storing a replay
import gymnasium as gym
env = gym.make("gym-generals-v0", ...)
options = {"replay": "my_replay"}
env.reset(options=options) # The next game will be encoded in my_replay.pkl
Loading a replay
from generals import Replay
# Initialize Replay instance
replay = Replay.load("my_replay")
replay.play()
🕹️ Replay controls
You can control your replays to your liking! Currently we support these controls:
q
— quit/close the replayr
— restart replay from the beginning←/→
— increase/decrease the replay speedh/l
— move backward/forward by one frame in the replayspacebar
— toggle play/pausemouse
click on the player's row — toggle the FoV (Field of View) of the given player
[!WARNING] We are using the pickle module which is not safe! Only open replays you trust.
🌍 Environment
🔭 Observation
An observation for one agent is a dictionary {"observation": observation, "action_mask": action_mask}
.
The observation
is a Dict
. Values are either numpy
matrices with shape (N,M)
, or simple int
constants:
Key | Shape | Description |
---|---|---|
army |
(N,M) |
Number of units in a cell regardless of the owner |
general |
(N,M) |
Mask indicating cells containing a general |
city |
(N,M) |
Mask indicating cells containing a city |
visible_cells |
(N,M) |
Mask indicating cells that are visible to the agent |
owned_cells |
(N,M) |
Mask indicating cells owned by the agent |
opponent_cells |
(N,M) |
Mask indicating cells owned by the opponent |
neutral_cells |
(N,M) |
Mask indicating cells that are not owned by any agent |
structure |
(N,M) |
Mask indicating whether cells contain cities or mountains, even out of FoV |
owned_land_count |
— | Number of cells the agent owns |
owned_army_count |
— | Total number of units owned by the agent |
opponent_land_count |
— | Number of cells owned by the opponent |
opponent_army_count |
— | Total number of units owned by the opponent |
is_winner |
— | Indicates whether the agent won |
timestep |
— | Current timestep of the game |
The action_mask
is a 3D array with shape (N, M, 4)
, where each element corresponds to whether a move is valid from cell
[i, j]
in one of four directions: 0 (up)
, 1 (down)
, 2 (left)
, or 3 (right)
.
⚡ Action
Actions are in a dict
format with the following key: value
format:
pass
indicates whether you want to1 (pass)
or0 (play)
.cell
value is annp.array([i,j])
wherei,j
are indices of the cell you want to move fromdirection
indicates whether you want to move0 (up)
,1 (down)
,2 (left)
, or3 (right)
split
indicates whether you want to1 (split)
units and send only half, or0 (no split)
where you send all units to the next cell
[!TIP] You can see how actions and observations look like by printing a sample form the environment:
print(env.observation_space.sample()) print(env.action_space.sample())
🎁 Reward
It is possible to implement custom reward function. The default reward is awarded only at the end of a game
and gives 1
for winner and -1
for loser, otherwise 0
.
def custom_reward_fn(observation, action, done, info):
# Give agent a reward based on the number of cells they own
return observation["observation"]["owned_land_count"]
env = gym.make(..., reward_fn=custom_reward_fn)
observations, info = env.reset()
🌱 Contributing
You can contribute to this project in multiple ways:
- 🤖 If you implement ANY non-trivial agent, send it to us! We will publish it so others can play against it
- 💡 If you have an idea on how to improve the game, submit an issue or create a PR, we are happy to improve! We also have some ideas (see issues), so you can see what we plan to work on
[!Tip] Check out wiki to learn in more detail on how to contribute.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file generals-1.0.0.tar.gz
.
File metadata
- Download URL: generals-1.0.0.tar.gz
- Upload date:
- Size: 31.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.11.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 825b01dd2a41dc47179a93f236fc00fd320ef282f346fed16670fc8ccb9a5124 |
|
MD5 | fca7245865522fba0e2e45e6170f95bf |
|
BLAKE2b-256 | 39929637a6553c8d156d8f55df26c1aac83a11f88afec3b92de4fa3e94bb2726 |
File details
Details for the file generals-1.0.0-py3-none-any.whl
.
File metadata
- Download URL: generals-1.0.0-py3-none-any.whl
- Upload date:
- Size: 35.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.11.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3617e0c1e8a27e0b78de6568dc49989c51622718ee04d83066494258f5db7af2 |
|
MD5 | 0d0c0291adc74689e4ae668b3e15718d |
|
BLAKE2b-256 | 433fa99ab4e71a7fc2fc4c216a1c184688044a3c3dd5b65168b1d114614df0ca |