Skip to main content

Generals.io environment compliant with PettingZoo API standard powered by Numpy.

Project description

Gameplay GIF

Generals.io Bots

CodeQL CI

InstallationGetting StartedCustomizationEnvironment

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 mountains
  • A, 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 as 40 + 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 replay
  • r — restart replay from the beginning
  • ←/→ — increase/decrease the replay speed
  • h/l — move backward/forward by one frame in the replay
  • spacebar — toggle play/pause
  • mouse 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 to 1 (pass) or 0 (play).
  • cell value is an np.array([i,j]) where i,j are indices of the cell you want to move from
  • direction indicates whether you want to move 0 (up), 1 (down), 2 (left), or 3 (right)
  • split indicates whether you want to 1 (split) units and send only half, or 0 (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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

generals-1.0.0.tar.gz (31.1 kB view details)

Uploaded Source

Built Distribution

generals-1.0.0-py3-none-any.whl (35.1 kB view details)

Uploaded Python 3

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

Hashes for generals-1.0.0.tar.gz
Algorithm Hash digest
SHA256 825b01dd2a41dc47179a93f236fc00fd320ef282f346fed16670fc8ccb9a5124
MD5 fca7245865522fba0e2e45e6170f95bf
BLAKE2b-256 39929637a6553c8d156d8f55df26c1aac83a11f88afec3b92de4fa3e94bb2726

See more details on using hashes here.

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

Hashes for generals-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3617e0c1e8a27e0b78de6568dc49989c51622718ee04d83066494258f5db7af2
MD5 0d0c0291adc74689e4ae668b3e15718d
BLAKE2b-256 433fa99ab4e71a7fc2fc4c216a1c184688044a3c3dd5b65168b1d114614df0ca

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page