Overview

Brutal Maze is a thrilling shoot ‘em up game with minimalist art style.

_images/screenshot.png

Notable features:

  • Being highly portable.

  • Auto-generated and infinite maze.

  • No binary data for drawing.

  • Enemies with special abilities: stun, poison, camo, etc.

  • Somewhat a realistic physic and logic system.

  • Resizable game window in-game.

  • Easily customizable via INI file format.

  • Recordable in JSON (some kind of silent screencast).

  • Remote control through TCP/IP socket (can be used in AI researching).

Table of Contents

Installation

Brutal Maze should run on Python version 3.6 and above. If you’re using an Unix-like operating system, you’re likely to have Python installed on your computer. Otherwise, you can download it from python.org.

The game also uses multiple third-party libraries, which is recommended to be installed using pip. There is a detailed documentation about getting this package manager on pypa.io.

Install from PyPI

For convenience reasons, every release of Brutal Maze is uploaded to the Python Package Index. To either install or upgrade, open your terminal (on Windows: Command Prompt or PowerShell) and run:

pip install --user --upgrade brutalmaze

This requires the the user scheme scripts directory to be in your environmental variable $PATH.

Install from Source

If you want to tweak the game or contribute, clone the git repository:

git clone https://git.sr.ht/~cnx/brutalmaze

Then install it using pip, like so:

pip install --user brutalmaze/

Configuration

Configuration Files

At the time of writing, this is the default configuration file:

[Graphics]
Screen width: 640
Screen height: 480
# FPS should not be greater than refresh rate.
Maximum FPS: 60

[Sound]
Muted: no
# Volume must be between 0.0 and 1.0.
Music volume: 1.0
# Use space music background, which sounds cold and creepy.
Space theme: no

[Control]
# Touch-friendly control
Touch: no
# Input values should be either from Mouse1 to Mouse3 or a keyboard key
# and they are case-insensitively read.
# Aliases for special keys are listed here (without the K_ part):
# http://www.pygame.org/docs/ref/key.html
# Key combinations are not supported.
New game: F2
Toggle pause: p
Toggle mute: m
Move left: a
Move right: d
Move up: w
Move down: s
Long-range attack: Mouse1
Close-range attack: Mouse3

[Record]
# Directory to write record of game states, leave blank to disable.
Directory:
# Number of snapshots per second. This is preferably from 3 to 60.
Frequency: 30

[Server]
# Enabling remote control will disable control via keyboard and mouse.
Enable: no
Host: localhost
Port: 42069
# Timeout on blocking socket operations, in seconds.
Timeout: 1.0
# Disable graphics and sound (only if socket server is enabled).
Headless: no

By default, Brutal Maze also then tries to read site (system-wide) and user configuration.

Site Config File Location
  • Apple macOS: /Library/Application Support/brutalmaze/settings.ini

  • Other Unix-like: $XDG_CONFIG_DIRS/brutalmaze/settings.ini or /etc/xdg/brutalmaze/settings.ini

  • Microsoft Windows:

    • XP: C:\Documents and Settings\All Users\Application Data\brutalmaze\settings.ini

    • Vista: Fail! (C:\ProgramData is a hidden system directory, however if you use Windows Vista, please file an issue telling us which error you receive)

    • 7 and above: C:\ProgramData\brutalmaze\settings.ini

User Config File Location
  • Apple macOS: ~/Library/Application Support/brutalmaze/settings.ini

  • Other Unix-like: $XDG_CONFIG_HOME/brutalmaze/settings.ini or ~/.config/brutalmaze/settings.ini

  • Microsoft Windows (roaming is not supported until someone requests):

    • XP: C:\Documents and Settings\<username>\Application Data\brutalmaze\settings.ini

    • Vista and above: C:\Users\<username>\AppData\Local\brutalmaze\settings.ini

Command-Line Arguments

$ brutalmaze --help
usage: brutalmaze [options]

optional arguments:
  -h, --help            show this help message and exit
  -v, --version         show program's version number and exit
  --write-config [PATH]
                        write default config and exit, if PATH not specified use stdout
  -c PATH, --config PATH
                        location of the configuration file
  -s X Y, --size X Y    the desired screen size
  -f FPS, --max-fps FPS
                        the desired maximum FPS
  --mute, -m            mute all sounds
  --unmute              unmute sound
  --music-volume VOL    between 0.0 and 1.0
  --space-music         use space music background
  --default-music       use default music background
  --touch               enable touch-friendly control
  --no-touch            disable touch-friendly control
  --record-dir DIR      directory to write game records
  --record-rate SPF     snapshots of game state per second
  --server              enable server
  --no-server           disable server
  --host HOST           host to bind server to
  --port PORT           port for server to listen on
  -t TIMEOUT, --timeout TIMEOUT
                        socket operations timeout in seconds
  --head                run server with graphics and sound
  --headless            run server without graphics or sound

First, Brutal Mazes read the default settings, then it try to read site and user config whose locations are shown above. These files are listed as fallback of the --config option and their contents are fallback for other options (if they are absent default values are used instead). We don’t support control configuration via CLI because that is unarguably ugly.

If --config option is set, Brutal Maze parse it before other command-line options. Later-read preferences will override previous ones.

Gameplay

Brutal Maze is a fast-paced hack and slash game which aims to bring players a frustrating, horror-like experience. It tries to mimic real-life logic in a way that truly represents our loneliness, mortality and helplessness in the universe.

The game features a solitary hero in a trigon shape who got lost in world of squares. Unlucky for per, the squares have no intention to let their visitor leave in peace. Together, they form a greater being called The Maze. Naturally, The Maze is dynamically and infinitely generated. As our poor hero tries to find a way out, it releases its minions (we will call them enemies from here) to stop per. Since The Maze sees it all and knows it all, it keeps creating more and more enemies for the hero to fight. It also keeps track of which type of squares can do most damages to our trigon, in order to send out the most effective belligerents.

Your mission is to help the hero go the furthest distance possible, while fighting those aggressive enemies. Extra information below will give you a better understanding of what you fight and how you fight them.

Hero

The hero is a regular trigon in Aluminium color (from the Tango palette). Perse has the ability to attack and move (both horizontally and vertically) simultaneously. However, close- and long-range attacks can’t be stricken at the same time. When swinging per blade, our hero may also avoid getting damages caused by per enemies’ bullets.

Like heroes in other hack and slash games, the trigon can heal too, but irony, per HP recovery rate decreases as perse gets wounded. Been warned you have, bravery will only give you regrets.

Enemies

Enemies are put into hibernation and blend into The Maze at the time of their creation. When the hero comes across, they become awake and show their (physical) colors. Enemy of each color has an unique power as described below:

Butter

May strike critical hits.

Orange (also known as Agent Orange)

May prevent the hero from healing or blocking bullets. Poisoned hero will be drawn as a square.

Chocolate (a.k.a. MDMA in Disguise):

May make the hero high and shoot uncontrollably. Still, Chocolate is good for your health (and so is MDMA).

Chameleon

Invisible, only shows itself when attacking or being attacked.

Sky Blue (a.k.a. Lightning Sky)

May immobilize the hero. If this happen our hero can only see the enemies, including Chameleons, on a blank background. What a blessing in disguise!

Plum (a.k.a. Plum Wine)

May replicate. Very quickly.

Scarlet Red (a.k.a. Vampire’s Eye)

Moves faster and drains hero’s HP.

The possibility that an enemy attack with its special power increases when it’s able to prove its effectiveness to The Maze. In other words, the more a kind of enemy hit the hero, the more chance they may use their unique abilities. Lucky for you, squares are unitasking so they have to stop moving to perform attacks. This slows them down a bit, however the ones which fall off the display will respawn elsewhere in The Maze.

Attacks

In this game, attack’s damage is contingent on the distance between the attacker and its target. The closer they are, the more damage is caused. There is at least an one-third-second delay between two attacks stricken by any character.

Long-range Attacks

While projectiles are often called bullets in the code and the documentation, they are more similar to stones propelled by slingshots, as they don’t fly very far (about 6 times the width of an enemy). Those fired by enemies can fly though walls but the ones shot by the hero turn the grid into a new enemy. A bullet is counted as hitting the target when the distance between the center of the two object is less than the circumradius of a cell.

Close-range Attacks

It is needless to explain any further on how this kind of attack works, so we only provide the size of the characters for you to calculate when the strike can wound the target. To do so, the attacker must touch its opponents, or simplistically, the distance between the central points of the two characters must not be any greater than the sum of their circumradiuses. Do the calculations yourself, a square’s side is a fifth of the walls’, and covers the same area as a trigon.

Specially, hero’s closed-range attacks also block opponents’ bullets. If this happens, the hero won’t be able to attack in the next turn.

Manual slashing

As the hero always follow the mouse, perse perform close-range attack while doing so. Unlike the automatic ones, there isn’t any delay between two manual slashings.

Remote Control

Brutal Maze provides a INET (i.e. IPv4), STREAM (i.e. TCP) socket server which can be enabled in the config file or by adding the --server CLI flag. After binding to the given host and port, it will wait for a client to connect. Then, in each cycle of the loop, the server will send current details of each object (hero, walls, enemies and bullets), wait for the client to process the data and return instruction for the hero to follow. Since there is no EOT (End of Transfer) on a socket, messages sent and received between server and client must must be strictly formatted as explained below.

Server Output

First, the game will export its data to a byte sequence (which in this case, is simply a ASCII string without null-termination) of the length \(l\). Before sending the data to the client, the server would send the number \(l\) padded to 7 digits.

Below is the meta structure of the data:

<Map height (nh)> <Number of enemies (ne)> <Number of bullets (nb)> <Score>
<nh lines describing visible part of the maze>
<One line describing the hero>
<ne lines describing ne enemies>
<nb lines describing nb bullets>
The Maze

Visible parts of the maze with the width \(n_w\) and the height \(n_h\) are exported as a byte map of \(n_h\) lines and \(n_w\) columns. Any character other than 0 represents a blocking cell, i.e. a wall.

To avoid floating point number in later description of other objects, each cell has the width (and height) of 100, which means the top left corner of the top left cell has the coordinates of \((0, 0)\) and the bottom right vertex of the bottom right cell has the coordinates of \((100 n_w, 100 n_h)\).

The Hero

6 properties of the hero are exported in one line, separated by 1 space, in the following order:

Color

The current HP of the hero, as shown in in the later section.

X-coordinate

An integer within \([0, 100 n_w]\).

Y-coordinate

An integer within \([0, 100 n_h]\). Note that the y-axis points up-side-down instead of pointing upward.

Angle

The direction the hero is pointing to in degrees, cast to an integer from 0 to 360. Same note as above (the unit circle figure might help you understand this easier).

Can attack

0 for no and 1 for yes.

Can heal

0 for no and 1 for yes.

_images/unit-circle.png
The Enemies

Each enemy exports these properties:

Color

The type and the current HP of the enemy, as shown in the table below.

X-coordinate

An integer within \([0, 100 n_w]\).

Y-coordinate

An integer within \([0, 100 n_h]\).

Angle

The direction the enemy is pointing to in degrees, cast to a nonnegative integer.

To shorten the data, each color (in the Tango palette) is encoded to a lowercase letter. Different shades of a same color indicating different HP of the characters.

HP

5

4

3

2

1

Butter

fce94f

edd400

c4a000

Orange

fcaf3e

f57900

ce5c00

Chocolate

e9b96e

c17d11

8f5902

Chameleon

8ae234

73d216

4e9a06

Sky Blue

729fcf

3465a4

204a87

Plum

ad7f8a

75507b

5c3566

Scarlet Red

ef2929

cc0000

a40000

Aluminium

eeeeec

d3d7cf

babdb6

888a85

555753

Note

If a character shows up with color 0, it is safe to ignore it since it is a dead body yet to be cleaned up.

Flying bullets

Bullets also export 4 properties like enemies:

Color

The type and potential damage of the bullet (from 0.0 to 1.0), encoded similarly to characters’, except that aluminium bullets only have 4 colors v, w, x and 0.

X-coordinate

An integer within \([0, 100 n_w]\).

Y-coordinate

An integer within \([0, 100 n_h]\).

Angle

The bullet’s flying direction in degrees, cast to a nonnegative integer.

Example
_images/screenshot.png

Above snapshot of the game is exported as:

19 5 3 180
00000000000000000vvvv0000
v0000000000000000vvvv0000
v0000000000000000vvvv0000
v0000000000000000vvvv0000
vvvvvvvvvvvvvvvvvvvvv0000
vvvvvvvvvvvvvvvvvvvvv000v
vvvvvvvvvvvvvvvvvvvvv000v
vvvvvvvvvvvvvvvvvvvv00000
0000000000000000000000000
0000000000000000000000000
0000000000000000000000000
v000000000000000000000000
v000000000000000000000000
v000000000000000000000000
v000vvvvvvv000vvv0vvv0000
v000vvvvvvv000vvvvvvv0000
v000vvvvvvv000vvvvvvv0000
v000vvvvvvv000vvvvvvv0000
v000000vvvv000000vvvv0000
v 1267 975 47 0 1
p 1817 1050 45
g 1550 1217 45
a 2250 1194 45
p 2050 1017 45
e 1850 950 358
x 2126 1189 361
e 1541 1020 167
v 1356 1075 49

Client Output Format

Every loop, the server receives no more than 7 bytes in the format of <Movement> <Angle> <Attack>. Again, these values need to be specially encoded.

Movement

This is the most awkward one. As we can all imagine, there are nine different directions for the hero to move. Were they represented as two-dimensional vectors, at least three characters would be needed to describe such a simple thing, e.g. 1 0 for \(m = (1, 0)\), and in the worst-case scenario \(m = (-1, -1)\), we would need five: -1 -1. 40 bits are used to carry a four-bit piece of data, freaking insane, right? So instead, we decided to slightly encode it like this:

Direction

Left

Nil

Right

Up

0

1

2

Nil

3

4

5

Down

6

7

8

Angle

Direction to point to hero to, might be useful to aim or to perform a close-range attack manually. This value should also be converted to degrees and casted to a nonnegative integer.

Attack

Attack can be either of the three values:

  1. Do nothing

  2. Long-range attack

  3. Close-range attack

Simple, huh? Though be aware that this won’t have any effect if the hero can yet strike an attack (as described in above section about The Hero).

Pseudo-Client

  1. Create an INET, STREAMing socket sock

  2. Connect sock to the address host:port which the server is bound to

  3. Receive length \(l\) of data

  4. If \(l > 0\), close sock and quit

  5. Receive the data

  6. Process the data

  7. Send instruction for the hero to the server and go back to step 3

Your AI should try to not only reach the highest score possible, but also in the smallest amount of time. For convenience purpose, the server will log these values to stdout.

There are samples of client implementations in different languages in the client-examples directory (more are coming).

Copying

This listing is our best-faith, hard-work effort at accurate attribution, sources, and licenses for everything in Brutal Maze. If you discover an asset/contribution that is incorrectly attributed or licensed, please contact us immediately. We are happy to do everything we can to fix or remove the issue.

License

Brutal Maze’s source code and its icon are released under GNU Affero General Public License version 3 or later. This means if you run a modified program on a server and let other users communicate with it there, your server must also allow them to download the source code corresponding to the modified version running there.

https://www.gnu.org/graphics/agplv3-155x51.png

Other creative works retain their original licenses as listed below.

Color Palette

Brutal Maze uses the Tango color palette by the Tango desktop project to draw all of its graphics. The palette is released to the Public Domain.

Sound Effects

Sound Effects Artist—Tobiasz ‘unfa’ Karoń

Sound Effects Artist—HappyParakeet

Sound Effects Artist—jameswrowles

Sound Effects Artist—MrPork

Sound Effects Artist—suspensiondigital

Sound Effects Artist—gusgus26

Sound Effects Artist—braqoon

Sound Effects Artist—Qat

Sound Effects Artist—pepingrillin

Record Player