Metadata-Version: 2.1
Name: codepraxis-football
Version: 0.2.1
Summary: CodePraxis Football: write Python agents that play football.
Author: CodePraxis
License: Proprietary
Requires-Python: >=3.12
Requires-Dist: gymnasium>=0.29
Requires-Dist: numpy>=1.23
Requires-Dist: pillow>=9
Requires-Dist: imageio-ffmpeg>=0.4.9
Requires-Dist: absl-py>=1
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Provides-Extra: arena
Requires-Dist: codepraxis-arena>=0.6; extra == "arena"
Description-Content-Type: text/markdown

# CodePraxis Football

Write a Python agent that plays football. Test it on your laptop, watch it play,
record videos, and score it the same way the judges do.

## Install

```sh
pip install codepraxis-football        # Python 3.12 or newer; Mac, Linux, Windows
codepraxis-football doctor             # checks the engine, rendering and video
codepraxis-football init my-agent      # sample agent + this guide + a practice level
cd my-agent
```

On a Linux server or a minimal Linux install, the game needs the system OpenGL libraries:
`sudo apt install libgl1 libegl1` (desktops already have them). No GPU is needed.

## Try it

```sh
codepraxis-football play shoot_keeper                    # play it yourself with the keyboard
codepraxis-football watch agent.py shoot_keeper          # watch your agent in a window
codepraxis-football eval agent.py shoot_keeper           # official score: 100 seeded episodes
codepraxis-football video agent.py shoot_keeper -o a.mp4 # record without a window
codepraxis-football watch-match agent.py random match_5v5 # two agents live: yellow vs blue
codepraxis-football match agent.py other.py match_5v5    # same, no window, JSON result
codepraxis-football levels                               # every level
```

The window and videos hold the picture for a moment after goals, cards and set pieces.
Add `--fast` to skip that. Training and `eval` never pause.
The 3D view (window, `video --view 3d`) runs extra engine code that changes how a match plays out, so the
same seed can end differently there than in `eval`. `video --view topdown` shows exactly what `eval` scored.

Keyboard: arrows move, hold `e` sprint, hold `c` dribble, `s` short pass, `a` high pass,
`w` long pass, `d` shoot, `x` slide tackle, `Esc` quit.

## Write an agent

An agent is a file with one function. It gets what its player sees and returns one action:

```python
from codepraxis_football import Action

def agent(obs):
    me = obs['left_team'][obs['active']]
    if me[0] > 0.7:
        return Action.SHOT
    return Action.RIGHT
```

Agents that need memory can use a class instead:

```python
class Agent:
    def reset(self):          # start of every episode
        self.steps = 0
    def act(self, obs):
        self.steps += 1
        return Action.RIGHT
```

Optional settings at the top of the file:

| Setting | Default | Meaning |
|---|---|---|
| `OBSERVATION` | `'raw'` | `'raw'` (dict), `'vector'` (115 numbers), `'minimap'` (72x96x4), `'pixels'` (72x96x3, slow) |
| `PLAYERS` | `1` | How many of our players you control. With more than 1, `obs` is a list and you return a list |

### The pitch

`x` runs from -1 (our goal) to 1 (the goal you attack). `y` runs from -0.42 (top) to 0.42 (bottom).
You always attack to the right, also when the judges play your agent as the other team.

### Raw observation fields

| Field | Meaning |
|---|---|
| `ball` | `[x, y, z]` ball position (z = height) |
| `ball_direction` | `[x, y, z]` ball movement per step |
| `ball_owned_team` | -1 nobody, 0 us, 1 them |
| `ball_owned_player` | index of the player with the ball |
| `left_team`, `right_team` | `[x, y]` of every player (left = us) |
| `left_team_direction`, `right_team_direction` | movement per step of every player |
| `left_team_roles`, `right_team_roles` | 0 GK, 1 CB, 2 LB, 3 RB, 4 DM, 5 CM, 6 LM, 7 RM, 8 AM, 9 CF |
| `left_team_tired_factor`, `right_team_tired_factor` | 0 fresh to 1 exhausted |
| `left_team_yellow_card`, `left_team_active` (and right) | cards; `active` False = sent off |
| `active` | index (in `left_team`) of the player you control |
| `sticky_actions` | 10 flags: 8 directions, sprint, dribble (on until released) |
| `score` | `[ours, theirs]` |
| `steps_left` | steps until the episode ends |
| `game_mode` | 0 normal, 1 kick-off, 2 goal kick, 3 free kick, 4 corner, 5 throw-in, 6 penalty |

### Actions

| # | Action | # | Action |
|---|---|---|---|
| 0 | IDLE | 10 | HIGH_PASS |
| 1-8 | LEFT, TOP_LEFT, TOP, TOP_RIGHT, RIGHT, BOTTOM_RIGHT, BOTTOM, BOTTOM_LEFT | 11 | SHORT_PASS |
| 9 | LONG_PASS | 12 | SHOT |
| 13 | SPRINT | 14 | RELEASE_DIRECTION |
| 15 | RELEASE_SPRINT | 16 | SLIDING |
| 17 | DRIBBLE | 18 | RELEASE_DRIBBLE |

Directions, sprint and dribble stay on until released. Passes and shots go the way the player faces.

## Use it from Python (training)

```python
import codepraxis_football as cpf

env = cpf.make('shoot_keeper', observation='vector')    # Gymnasium API
obs, info = env.reset(seed=0)
obs, reward, terminated, truncated, info = env.step(cpf.Action.SHOT)
```

`reward` is +1 for our goal and -1 for theirs. With no window the game runs at several hundred
steps per second per CPU core; run one environment per core to train faster:

```python
import gymnasium as gym

def make_env():
    return cpf.make('shoot_keeper', observation='vector')

if __name__ == '__main__':
    envs = gym.vector.AsyncVectorEnv([make_env] * 4)   # 4 processes
    obs, info = envs.reset(seed=[0, 1, 2, 3])
```

On a 2024 laptop, 4 environments run ~1,500 steps/s: a million training steps in about 11 minutes.

## Reinforcement learning

Any library that speaks Gymnasium works. With Stable-Baselines3 (`pip install stable-baselines3`):

```python
from stable_baselines3 import PPO
from stable_baselines3.common.vec_env import SubprocVecEnv, VecMonitor
import codepraxis_football as cpf

def make_env():
    return cpf.make('shoot_keeper', observation='vector', reward='checkpoints')

if __name__ == '__main__':
    envs = VecMonitor(SubprocVecEnv([make_env] * 4))
    model = PPO('MlpPolicy', envs, n_steps=512, batch_size=512, gamma=0.993)
    model.learn(total_timesteps=2_000_000)
    model.save('model')
```

`reward='checkpoints'` adds small rewards for carrying the ball towards the goal, so learning
starts long before the first goal. Use the saved model as an agent:

```python
import os
from stable_baselines3 import PPO
OBSERVATION = 'vector'
model = PPO.load(os.path.join(os.path.dirname(__file__), 'model.zip'))  # next to agent.py

def agent(obs):
    return int(model.predict(obs, deterministic=True)[0])
```

## Scoring

`eval` runs your agent for 100 episodes with fixed seeds and prints JSON: `goal_rate`
(episodes where you scored), `avg_steps_to_goal`, wins/draws/losses, timeouts and errors.
A step slower than 50 ms counts as IDLE. If your agent raises an error, that episode scores
nothing and the rest still run. The judges use the same command with seeds you haven't seen.

## Make your own practice level

See `my_level.py` from `init`. Use a level file anywhere a level name goes:
`codepraxis-football watch agent.py my_level.py`.
