No project description provided
Project description
snAIk
A snake-against-snake battleground
Inspired by BattleSnake
snAIk
allows you to create your own snake AI and pit it against other snakes (or yourself) in a battle arena. The goal
is to be the last snake standing.
Features
- Customizable board size, number of snakes, snake "brain" (AI)
- Easily export both graphical and JSON game history
- Works with any language that can communicate over stdin/stdout
Installation
Requires Python 3.8+. On linux, also requires Qt graphics libs (See Dockerfile for apt packages). Simply install using pip (preferably in a virtual environment):
# Via pypi
pip install snaik
# Or via git:
pip install git+https://gitlab.com/ntjess/snaik.git
Note that snaik
uses Qt for its gui system, so one of PySide2
, PySide6
, PyQt5
or PyQt6
must be installed.
snaik
will install PySide6
for you if you specify the full
extra:
pip install snaik[full]
Also note that imageio
is required for saving gifs (a functionality mentioned in Exporting game history
). This is installed with the full
extra.
Usage
# See available options
python -m snaik --help
# usage: __main__.py [-h] [--grid_size GRID_SIZE] [--snake_brains SNAKE_BRAINS] [--n_food_points N_FOOD_POINTS]
# [--json_path JSON_PATH] [--frames_path FRAMES_PATH] [--gif_path GIF_PATH] [--headless HEADLESS]
# Run a game of Snaik.
# optional arguments:
# ...
Examples
Play a game by yourself using the up, down, left, right keys:
# It's the default mode :)
python -m snaik
Play a game with two humans, one using the arrow keys and the other using the WASD keys:
# nsew = "north, south, east, west"
python -m snaik --snake_brains 'keyboard; keyboard nsew_keys="wsda"'
Play a game against a greedy algorithm snake. It will always pursue the closest food at any cost
python -m snaik --snake_brains 'keyboard; greedy'
Watch two greedy snakes fight it out with a randomly moving snake:
python -m snaik --snake_brains 'greedy; greedy; random'
Run several external programs against each other (more details in the "External snake brains" section below):
python -m snaik --snake_brains 'python my_brain.py; my-compiled-brain.exe; java -jar my-brain.jar'
More details are provided in the Creating your own brain section.
Scoring
Each tick, every snake loses one point. They gain 20 points (currently hard-coded but can change in the future) for each food point they eat. Snakes can die by hitting walls, themselves or other snakes, or starvation (running out of points). The leaderboard shown in the top-right corner of the game window is sorted by score, with the highest score at the top. Note that dead snakes are always ranked below living ones, even if their score is higher.
In-game controls
(While keyboard shortcuts are used in the docs below, you can also press the corresponding menu options in the top bar to achieve the same results.)
Run/Pause/Restart: the most basic options
Press Ctrl+R
to run the game. The game will run until a snake dies or the game is manually paused using Ctrl+P
.
Note that the game speed can be changed by adjusting the tick rate (in ms) found in the right-side control panel.
To restart a game, simply press Ctrl+Shift+R
. Note this is only undoable if you specified a save path for the game
history (more details below).
Step-by-step ticking
Press Ctrl+T
to tick the game once. This is useful for debugging or watching the game play out slowly.
Exporting game history
Provide a Json Path
in the right-hand control panel to save the game history as a JSON file. This file is updated each
tick and stores enough data about each board state to recreate any given game at any point in time. Also, gifs or
individual frames can be saved by providing a Frames Path
or Gif Path
respectively. Be aware that these files are
updated each tick and can quickly become very large. Additionally, they are reset each time the game is restart, so be
careful to either copy them before restarting or change the save paths if this is not desired.
Viewing playback history
At any point during a game, you can press Ctrl+H
to view the game history. This will open a new window that allows you
to step backward in time and view the game state at any point in the past. This view is read-only, since rewinding and
moving forward is not supported. Instead, you can use the dev console and run the command
game.set_board_state(game.recorder.json_states[<index>])
to set the board state to any point in the past. Be aware
that this will mangle the game history, so you should only do this if you are sure you want to.
Changing snake brains
You can change the snake brains at any point during a game by pressing Ctrl+B
. This will open a new window that allows
you to specify newline-separated brains in the same format as the --snake_brains
argument. Note that newlines are
used instead of ;
to separate brains in this case. Also, nothing will happen until you press the Run
button above
the text box.
Changing board size
The easiest way is to set the board size on startup using the --grid_size
argument:
python -m snaik --grid_size (15, 15)
However, you can change it at runtime using the dev console and running board.update_grid_size((<width>, <height>))
.
Be aware this will reset the game.
Changing number of snakes
Simply adjust which brains are present using Ctrl+B
as described above. The number of snakes will always match the
number of brains.
Creating your own brain
Every brain must accept a board_state
and snake_id
(in that order) and return an action (currently only a "TURN"
command is supported). The board state includes the following information:
snake_data:
# One entry per snake
0:
id: int
score: int
status: Status ("ALIVE", "DEAD", "WINNER")
direction: Direction ("NORTH", "SOUTH", "EAST", "WEST")
food_indexes: list[int] # Which 0-based indexes in the array of points are currently digesting food
points: Nx2 int array
1:
# ...
food: Nx2 int array # food locations
grid_size: (int, int) # (width, height) of the board
snake_id
is the id of the snake that the brain is controlling, i.e. a brain can find its own status information by
looking up snake_data[snake_id]
.
See snaik.game.Game.get_board_state
for the full implementation.
In python
The easiest kind of brain is a standalone Python function that takes a single argument (the game state) and returns a snake Action (i.e., a direction and "move" command). For example, here is a brain that always moves north:
from snaik.brain import Action, Direction, Command
def move_north(board_state: dict, snake_id: int):
return Action(Command.TURN, direction=Direction.NORTH)
See snaik.brain.random_brain
for a slightly more complex example that always moves in a random direction while
attempting to avoid walls and other snakes.
Brains that are slightly more complex or need to preserve state can be implemented as a class. The class must define a
__call__
method that takes board_state
and snake_id
as arguments and returns an Action, just like the standalone
function above. This is how snaik.brain.GreedyBrain
is implemented.
If the class defines a restart
method, it will be called when the game is restarted and passed the new
board_state
(see snaik.brain.KeyboardBrain
for an example).
Brains made this way are not yet visible to the game (a PR will gladly be accepted that makes snaik
search for
setuptools-based entry points). Currently, you can expose your brains to the game in three ways:
NOT RECOMMENDED: Modify the source code
Simply add/import your brains directly in snaik/brain.py
and add them to the brain_name_factory_map
dictionary.
RECOMMENDED: Create a new cli entry point:
# new_entry_point.py
from snaik.__main__ import main_cli
from snaik.brain import brain_name_factory_map
from my_brain import MyClassBrain, my_function_brain
# If your brain is a class (like GreedyBrain), register the class directly.
brain_name_factory_map['my_class_brain'] = MyClassBrain
# Since a factory is required (a callable that *produces* the brain function), you can use a lambda to wrap functional brains:
brain_name_factory_map['my_function_brain'] = lambda: my_function_brain
if __name__ == '__main__':
main_cli()
Then, you can run snaik
using this new entry point:
# Also accepts any other arguments used in `python -m snaik`
python new_entry_point.py --snake_brains 'my_class_brain; my_function_brain'
ALTERNATIVE: Use the dev console after starting the game:
# In the open dev console:
from snaik.brain import brain_name_factory_map
from my_brain import MyClassBrain, my_function_brain
brain_name_factory_map['my_class_brain'] = MyClassBrain
brain_name_factory_map['my_function_brain'] = lambda: my_function_brain
After you close the dev console, you can use these names under the Update Brains (Ctrl+B)
menu option.
In any other language
External programs can be passed in as snake brains. snaik
spawns a new subprocess for each "external" brain and
communicates with it over stdin/stdout. Note that snake_id
is added as an additional int
key in the board_state
passed to external programs. The subprocess must accept this JSON-serialized board state on stdin and return a
JSON-serialized action on stdout. For example, here is a C++ implementation that
always moves north:
#include <iostream>
#include <string>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main() {
std::string line;
while (std::getline(std::cin, line)) {
auto board_state = json::parse(line);
// Best to do something with the board state, but if always
// moving north is your thing...
json action = {
{"command", "TURN"},
{"direction", "NORTH"}
};
std::cout << action.dump() << std::endl;
}
return 0;
}
Compile this with g++ -std=c++11 -o north main.cpp
and then run snaik
with the --snake_brains
argument:
python -m snaik --snake_brains 'north'
Note that for this to work, north
must be accessible by Popen
(on the path, in your cwd, etc.) or you can specify
the full path to the executable. Keep in mind that a long full path might completely clobber the leaderboard
representation, so this is unadvised.
Contributing
Contributions are welcome! Please open an issue or PR if you have any suggestions or find any bugs.
License
Do whatever you want with this code. I'd appreciate a shoutout if you use it for anything cool.
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 snaik-0.1.1.tar.gz
.
File metadata
- Download URL: snaik-0.1.1.tar.gz
- Upload date:
- Size: 174.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: python-httpx/0.23.3
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | a7ddbc5b1e8a5b5fa3e5dc1d01c96b5b7e9b15860bd9125a54d86380f4e596fb |
|
MD5 | ea6f1c4f969a8832d08bd6fb2c052453 |
|
BLAKE2b-256 | c2ecbabcb43e2f458b745a911166d5824097818d8150e4398dde2e929df770ea |
File details
Details for the file snaik-0.1.1-py3-none-any.whl
.
File metadata
- Download URL: snaik-0.1.1-py3-none-any.whl
- Upload date:
- Size: 120.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: python-httpx/0.23.3
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5e28f8015e8261f6ae84c139a50f35cd4ef9e67dc9b6a5519327c46cac5d7831 |
|
MD5 | 9859f8b4d16dcd5c5ec0a5ab682d5c55 |
|
BLAKE2b-256 | 46a85796bbd57b138cc87906f8e1b3025b1e605c55eb2ee042cc7bb314bbd2f3 |