Skip to content

Commit

Permalink
Merge pull request #86 from kvankova/add_precommit
Browse files Browse the repository at this point in the history
ci: Add pre-commit hooks
  • Loading branch information
strakam authored Oct 13, 2024
2 parents 1e79af3 + 9a1b273 commit b3f8e0a
Show file tree
Hide file tree
Showing 36 changed files with 295 additions and 335 deletions.
File renamed without changes.
26 changes: 26 additions & 0 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: pre-commit hooks

on:
push:

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.11'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run pre-commit hooks
run: |
pre-commit run --all-files
42 changes: 42 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: check-ast
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
args: [--unsafe]
- id: check-json
- id: debug-statements
- id: end-of-file-fixer

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks:
- id: ruff
args: ["--select", "E", "--select", "F", "--select", "I",
"--fix",
"--line-length", "120",
"--exclude", "tests/", "--exclude", "examples/"
]
- id: ruff-format
args: ["--line-length", "120", "--exclude", "tests/", "--exclude", "examples/"]

- repo: local
hooks:
- id: mypy
name: mypy
entry: mypy --exclude 'examples/' --exclude 'tests/' --disable-error-code "annotation-unchecked" .
pass_filenames: false
language: system
types: [python]

- repo: local
hooks:
- id: run-pytest
name: Run tests
entry: make test
language: system
types: [python]
6 changes: 1 addition & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,11 @@ replay:
###################
# Developer tools #
###################
at:
pytest tests/test_game.py
pytest tests/test_map.py
python3 tests/gym_check.py

test_performance:
python3 -m tests.parallel_api_check

pytest:
test:
pytest

build:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ You can also check an example for 🦁[PettingZoo](./examples/pettingzoo_example
an example with commentary showcasing various features [here](./examples/complete_example.py).

## 🚀 Getting Started
Creating your first agent is very simple.
Creating your first agent is very simple.
- Start by subclassing an `Agent` class just like [`RandomAgent`](./generals/agents/random_agent.py) or [`ExpanderAgent`](./generals/agents/expander_agent.py).
- Every agent must have a name as it is his ID by which he is called for actions.
- Every agent must implement `play(observation)` function that takes in `observation` and returns an `action` (both defined below).
Expand Down Expand Up @@ -183,7 +183,7 @@ The `observation` is a `Dict`. Values are either `numpy` matrices with shape `(N
| `timestep` || Current timestep of the game |

`action_mask` is a mask with shape `(N,M,4)` where value `[i,j,d]` says whether you can move from cell `[i,j]` in a direction `d`.

### ⚡ Action
Action is a `tuple(pass, cell, direction, split)`, where:
- `pass` indicates whether you want to `1 (pass)` or `0 (play)`.
Expand Down
25 changes: 13 additions & 12 deletions examples/complete_example.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import gymnasium as gym

from generals import AgentFactory, GridFactory

# Initialize agents -- see generals/agents/agent_factory.py for more options
Expand All @@ -7,20 +8,20 @@

# Initialize grid factory
grid_factory = GridFactory(
grid_dims=(10, 10), # Grid height and width
mountain_density=0.2, # Expected percentage of mountains
city_density=0.05, # Expected percentage of cities
general_positions=[(1, 2), (7, 8)], # Positions of the generals
seed=38 # Seed to generate the same map every time
grid_dims=(10, 10), # Grid height and width
mountain_density=0.2, # Expected percentage of mountains
city_density=0.05, # Expected percentage of cities
general_positions=[(1, 2), (7, 8)], # Positions of the generals
seed=38, # Seed to generate the same map every time
)

env = gym.make(
"gym-generals-v0", # Environment name
"gym-generals-v0", # Environment name
grid_factory=grid_factory, # Grid factory
npc=npc, # NPC that will play against the agent
agent_id="Agent", # Agent ID
agent_color=(67, 70, 86), # Agent color
render_mode="human", # "human" mode is for rendering, None is for no rendering
npc=npc, # NPC that will play against the agent
agent_id="Agent", # Agent ID
agent_color=(67, 70, 86), # Agent color
render_mode="human", # "human" mode is for rendering, None is for no rendering
)

# We can draw custom maps - see symbol explanations in README
Expand All @@ -38,8 +39,8 @@

# Options are used only for the next game
options = {
"replay_file": "my_replay", # Save replay as my_replay.pkl
"grid": grid # Use the custom map
"replay_file": "my_replay", # Save replay as my_replay.pkl
"grid": grid, # Use the custom map
}

observation, info = env.reset(options=options)
Expand Down
3 changes: 2 additions & 1 deletion examples/gymnasium_example.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import gymnasium as gym

from generals import AgentFactory

# Initialize agents
Expand All @@ -10,6 +11,6 @@
observation, info = env.reset()
terminated = truncated = False
while not (terminated or truncated):
action = env.action_space.sample() # Here you put your agent's action
action = env.action_space.sample() # Here you put your agent's action
observation, reward, terminated, truncated, info = env.step(action)
env.render()
1 change: 1 addition & 0 deletions examples/pettingzoo_example.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import gymnasium as gym

from generals.agents import AgentFactory

# Initialize agents
Expand Down
21 changes: 11 additions & 10 deletions examples/record_replay_example.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import gymnasium as gym

from generals import AgentFactory, GridFactory

# Initialize agents -- see generals/agents/agent_factory.py for more options
Expand All @@ -7,24 +8,24 @@

# Initialize grid factory
grid_factory = GridFactory(
grid_dims=(5, 5), # Grid height and width
mountain_density=0.2, # Expected percentage of mountains
city_density=0.05, # Expected percentage of cities
general_positions=[(1, 2), (3, 4)], # Positions of the generals
seed=38 # Seed to generate the same map every time
grid_dims=(5, 5), # Grid height and width
mountain_density=0.2, # Expected percentage of mountains
city_density=0.05, # Expected percentage of cities
general_positions=[(1, 2), (3, 4)], # Positions of the generals
seed=38, # Seed to generate the same map every time
)

env = gym.make(
"gym-generals-v0", # Environment name
"gym-generals-v0", # Environment name
grid_factory=grid_factory, # Grid factory
agent_id="Agent", # Agent ID
agent_color=(67, 70, 86), # Agent color
npc=npc, # NPC that will play against the agent
agent_id="Agent", # Agent ID
agent_color=(67, 70, 86), # Agent color
npc=npc, # NPC that will play against the agent
)

# Options are used only for the next game
options = {
"replay_file": "my_replay", # Save replay as my_replay.pkl
"replay_file": "my_replay", # Save replay as my_replay.pkl
}

observation, info = env.reset(options=options)
Expand Down
7 changes: 3 additions & 4 deletions generals/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from .core.grid import GridFactory, Grid
from .core.replay import Replay
from .agents.agent_factory import AgentFactory
from gymnasium.envs.registration import register

from .agents.agent_factory import AgentFactory
from .core.grid import Grid, GridFactory
from .core.replay import Replay

__all__ = [
"AgentFactory",
Expand All @@ -25,5 +25,4 @@ def _register_generals_envs():
)



_register_generals_envs()
4 changes: 2 additions & 2 deletions generals/agents/agent_factory.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .random_agent import RandomAgent
from .expander_agent import ExpanderAgent
from .agent import Agent
from .expander_agent import ExpanderAgent
from .random_agent import RandomAgent


class AgentFactory:
Expand Down
7 changes: 3 additions & 4 deletions generals/agents/expander_agent.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import numpy as np
from .agent import Agent

from generals.core.config import Direction

from .agent import Agent


class ExpanderAgent(Agent):
def __init__(self, id="Expander", color=(0, 130, 255)):
Expand Down Expand Up @@ -30,9 +31,7 @@ def act(self, observation):

directions = [Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT]
for i, action in enumerate(valid_actions):
di, dj = (
action[:-1] + directions[action[-1]].value
) # Destination cell indices
di, dj = action[:-1] + directions[action[-1]].value # Destination cell indices
if army[action[0], action[1]] <= army[di, dj] + 1: # Can't capture
continue
elif opponent[di, dj]:
Expand Down
7 changes: 3 additions & 4 deletions generals/agents/random_agent.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from .agent import Agent
import numpy as np

from .agent import Agent


class RandomAgent(Agent):
def __init__(
self, id="Random", color=(242, 61, 106), split_prob=0.25, idle_prob=0.05
):
def __init__(self, id="Random", color=(242, 61, 106), split_prob=0.25, idle_prob=0.05):
super().__init__(id, color)

self.idle_probability = idle_prob
Expand Down
Empty file added generals/assets/__init__.py
Empty file.
2 changes: 1 addition & 1 deletion generals/assets/fonts/OFL.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
Expand Down
20 changes: 10 additions & 10 deletions generals/core/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@

valid_generals = ["A", "B"] # Generals are represented by A and B


class Channels:
"""
army - army size in each cell
general - general mask (1 if general is in cell, 0 otherwise)
mountain - mountain mask (1 if cell is mountain, 0 otherwise)
city - city mask (1 if cell is city, 0 otherwise)
passable - passable mask (1 if cell is passable, 0 otherwise)
ownership_i - ownership mask for player i (1 if player i owns cell, 0 otherwise)
ownership_neutral - ownership mask for neutral cells that are passable (1 if cell is neutral, 0 otherwise)
army - army size in each cell
general - general mask (1 if general is in cell, 0 otherwise)
mountain - mountain mask (1 if cell is mountain, 0 otherwise)
city - city mask (1 if cell is city, 0 otherwise)
passable - passable mask (1 if cell is passable, 0 otherwise)
ownership_i - ownership mask for player i (1 if player i owns cell, 0 otherwise)
ownership_neutral - ownership mask for neutral cells that are passable (1 if cell is neutral, 0 otherwise)
"""

def __init__(self, map: np.ndarray, _agents: list[str]):
self._army: np.ndarray = np.where(np.isin(map, valid_generals), 1, 0).astype(int)
self._general: np.ndarray = np.where(np.isin(map, valid_generals), 1, 0).astype(bool)
self._mountain: np.ndarray = np.where(map == MOUNTAIN, 1, 0).astype(bool)
self._city: np.ndarray = np.where(np.char.isdigit(map), 1, 0).astype(bool)
self._passable: np.ndarray = (map != MOUNTAIN).astype(bool)

self._ownership: dict[str, np.ndarray] = {
"neutral": ((map == PASSABLE) | (np.char.isdigit(map))).astype(bool)
}
self._ownership: dict[str, np.ndarray] = {"neutral": ((map == PASSABLE) | (np.char.isdigit(map))).astype(bool)}
for i, agent in enumerate(_agents):
self._ownership[agent] = np.where(map == chr(ord("A") + i), 1, 0).astype(bool)

Expand Down
4 changes: 2 additions & 2 deletions generals/core/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Literal
from importlib.resources import files
from enum import Enum, IntEnum, StrEnum
from importlib.resources import files
from typing import Literal

#################
# Game Literals #
Expand Down
Loading

0 comments on commit b3f8e0a

Please sign in to comment.