diff --git a/README.rst b/README.rst
index 720d270..705f722 100644
--- a/README.rst
+++ b/README.rst
@@ -12,13 +12,14 @@ job is to help the trigon fight against those evil squares and find a way out
(if there is any). Be aware that the more get killed, the more will show up and
our hero will get weaker when wounded.
-As a research game, Brutal Maze has a few primary goals:
+Being a research game, Brutal Maze has a few primary goals:
* Highly portable.
* Auto-generated and infinite maze.
-* No binary art data.
-* Enemies with randomized attributes: stun, poison, etc.
-* Resizable in realtime.
+* No binary data for drawing.
+* Enemies with randomized attributes: stun, poison, camo, etc.
+* Somewhat a realistic physic and logic system.
+* Resizable game window in-game.
Installation
------------
@@ -27,10 +28,10 @@ Brutal Maze is written in Python and is compatible with both version 2 and 3.
The installation procedure should be as simply as follow:
* Install Python and `pip `_. Make sure the
- directory for Python executables is your ``PATH``.
-* Clone the Github repository or download the Zip achieve and unpack.
-* Open Terminal in the directory containing the repo's folder and run
- ``pip install --user brutalmaze``.
+ directory for `Python scripts `_
+ is your ``PATH``.
+* Open Terminal or Command Prompt and run ``pip install --user brutalmaze``.
+ Now you can lauch the game by running the command ``brutalmaze``.
Control
-------
@@ -54,7 +55,7 @@ Right, ``d``
Move right.
Left Mouse
- Long-ranged attack.
+ Long-range attack.
Return, Right Mouse
- Close-ranged attack.
+ Close-range attack, also dodge from bullets.
diff --git a/brutalmaze/characters.py b/brutalmaze/characters.py
index 7a68f37..2e33379 100644
--- a/brutalmaze/characters.py
+++ b/brutalmaze/characters.py
@@ -20,18 +20,33 @@
__doc__ = 'brutalmaze module for hero and enemy classes'
from collections import deque
-from math import atan2, sin, pi
+from math import atan, atan2, sin, pi
from random import choice, shuffle, uniform
import pygame
from .constants import *
-from .utils import randsign, regpoly, fill_aapolygon, sign
+from .utils import sign, cosin, randsign, regpoly, fill_aapolygon
from .weapons import Bullet
class Hero:
- """Object representing the hero."""
+ """Object representing the hero.
+
+ Attributes:
+ surface (pygame.Surface): the display to draw on
+ x, y (int): coordinates of the center of the hero (in pixels)
+ angle (float): angle of the direction the hero pointing (in radians)
+ color (tuple of pygame.Color): colors of the hero on different HPs
+ R (int): circumradius of the regular triangle representing the hero
+ next_strike (int): the tick that the hero can do the next attack
+ slashing (bool): flag indicates if the hero is doing close-range attack
+ firing (bool): flag indicates if the hero is doing long-range attack
+ dead (bool): flag indicates if the hero is dead
+ spin_speed (float): speed of spinning (in frames per slash)
+ spin_queue (float): frames left to finish spinning
+ wound (float): amount of wound
+ """
def __init__(self, surface, fps):
self.surface = surface
w, h = self.surface.get_width(), self.surface.get_height()
@@ -82,15 +97,29 @@ class Hero:
class Enemy:
- """Object representing an enemy."""
+ """Object representing an enemy.
+
+ Attributes:
+ maze (Maze): the maze
+ x, y (int): coordinates of the center of the enemy (in grids)
+ angle (float): angle of the direction the enemy pointing (in radians)
+ color (tuple of pygame.Color): colors of the enemy on different HPs
+ awake (bool): flag indicates if the enemy is active
+ next_strike (int): the tick that the enemy can do the next attack
+ move_speed (float): speed of movement (in frames per grid)
+ offsetx, offsety (integer): steps moved from the center of the grid
+ spin_speed (float): speed of spinning (in frames per slash)
+ spin_queue (float): frames left to finish spinning
+ wound (float): amount of wound
+ """
def __init__(self, maze, x, y):
self.maze = maze
- self.angle, self.color = pi / 4, TANGO[choice(ENEMIES)]
self.x, self.y = x, y
self.maze.map[x][y] = ENEMY
+ self.angle, self.color = pi / 4, TANGO[choice(ENEMIES)]
self.awake = False
- self.next_move = 0
+ self.next_strike = 0
self.move_speed = self.maze.fps / ENEMY_SPEED
self.offsetx = self.offsety = 0
self.spin_speed = self.maze.fps / ENEMY_HP
@@ -108,15 +137,33 @@ class Enemy:
self.y += y
self.maze.map[self.x][self.y] = ENEMY
+ def wake(self):
+ """Wake the enemy up if it can see the hero."""
+ if self.awake: return
+ startx = starty = MIDDLE
+ stopx, stopy, distance = self.x, self.y, self.maze.distance
+ if startx > stopx: startx, stopx = stopx, startx
+ if starty > stopy: starty, stopy = stopy, starty
+ dx = (self.x-MIDDLE)*distance + self.maze.centerx - self.maze.x
+ dy = (self.y-MIDDLE)*distance + self.maze.centery - self.maze.y
+ mind = cosin(abs(atan(dy / dx)) if dx else 0) * distance
+ def length(x, y): return abs(dy*x - dx*y) / (dy**2 + dx**2)**0.5
+ for i in range(startx, stopx + 1):
+ for j in range(starty, stopy + 1):
+ if self.maze.map[i][j] != WALL: continue
+ x, y = self.maze.pos(i, j)
+ if length(x - self.maze.x, y - self.maze.y) <= mind: return
+ self.awake = True
+
def fire(self):
"""Return True if the enemy shot the hero, False otherwise."""
x, y = self.pos()
if (self.maze.length(x, y) > FIRANGE*self.maze.distance
- or self.next_move > pygame.time.get_ticks()
+ or self.next_strike > pygame.time.get_ticks()
or (self.x, self.y) in AROUND_HERO or self.offsetx or self.offsety
or uniform(-2, 2) < (INIT_SCORE/self.maze.score) ** 2):
return False
- self.next_move = pygame.time.get_ticks() + ATTACK_SPEED
+ self.next_strike = pygame.time.get_ticks() + ATTACK_SPEED
self.maze.bullets.append(Bullet(
self.maze.surface, x, y,
atan2(self.maze.y - y, self.maze.x - x), self.color[0]))
diff --git a/brutalmaze/maze.py b/brutalmaze/maze.py
index ae12ded..8fd8645 100644
--- a/brutalmaze/maze.py
+++ b/brutalmaze/maze.py
@@ -20,7 +20,7 @@
__doc__ = 'brutalmaze module for the maze class'
from collections import deque
-from math import pi, atan, atan2, log
+from math import pi, atan2, log
from random import choice, getrandbits
import pygame
@@ -28,7 +28,7 @@ from pygame import RESIZABLE
from .characters import Hero, Enemy
from .constants import *
-from .utils import round2, sign, cosin, regpoly, fill_aapolygon
+from .utils import round2, sign, regpoly, fill_aapolygon
from .weapons import Bullet
@@ -53,13 +53,33 @@ def new_column():
class Maze:
- """Object representing the maze, including the characters."""
+ """Object representing the maze, including the characters.
+
+ Attributes:
+ w, h: width and height of the display
+ fps: current frame rate
+ surface (pygame.Surface): the display to draw on
+ distance (float): distance between centers of grids (in px)
+ x, y (int): coordinates of the center of the hero (in px)
+ centerx, centery (float): center grid's center's coordinates (in px)
+ rangex, rangey: range of the index of the grids on display
+ paused (bool): flag indicates if the game is paused
+ score (float): current score
+ map (deque of deque): map of grids representing objects on the maze
+ down, right (int): direction the maze moving
+ rotatex, rotatey: grids rotated
+ bullets (list of Bullet): bullets flying
+ enemies (list of Enemy): alive enemies
+ hero (Hero): the hero
+ slashd (float): minimum distance for slashes to be effective
+ """
def __init__(self, size, fps):
self.w, self.h = size
self.fps = fps
self.surface = pygame.display.set_mode(size, RESIZABLE)
self.distance = (self.w * self.h / 416) ** 0.5
- self.middlex, self.middley = self.x, self.y = self.w >> 1, self.h >> 1
+ self.x, self.y = self.w // 2, self.h // 2
+ self.centerx, self.centery = self.w / 2.0, self.h / 2.0
w, h = (int(i/self.distance/2 + 2) for i in size)
self.rangex = range(MIDDLE - w, MIDDLE + w + 1)
self.rangey = range(MIDDLE - h, MIDDLE + h + 1)
@@ -90,8 +110,8 @@ class Maze:
def pos(self, x, y):
"""Return coordinate of the center of the grid (x, y)."""
- return (self.middlex + (x - MIDDLE)*self.distance,
- self.middley + (y - MIDDLE)*self.distance)
+ return (self.centerx + (x - MIDDLE)*self.distance,
+ self.centery + (y - MIDDLE)*self.distance)
def draw(self):
"""Draw the maze."""
@@ -103,36 +123,19 @@ class Maze:
square = regpoly(4, self.distance / SQRT2, pi / 4, x, y)
fill_aapolygon(self.surface, square, FG_COLOR)
- def wake(self, enemy):
- """Wake the enemy up if it can see the hero."""
- dx = (enemy.x-MIDDLE)*self.distance + self.middlex - self.x
- dy = (enemy.y-MIDDLE)*self.distance + self.middley - self.y
- mind = cosin(abs(atan(dy / dx)) if dx else 0) * self.distance
- startx = starty = MIDDLE
- stopx, stopy = enemy.x, enemy.y
- if startx > stopx : startx, stopx = stopx, startx
- if starty > stopy : starty, stopy = stopy, starty
- for i in range(startx, stopx + 1):
- for j in range(starty, stopy + 1):
- if self.map[i][j] != WALL: continue
- x, y = self.pos(i, j)
- d = abs(dy*(x-self.x) - dx*(y-self.y)) / (dy**2 + dx**2)**0.5
- if d <= mind: return
- enemy.awake = True
-
def rotate(self):
"""Rotate the maze if needed."""
- x = int((self.middlex-self.x) * 2 / self.distance)
- y = int((self.middley-self.y) * 2 / self.distance)
+ x = int((self.centerx-self.x) * 2 / self.distance)
+ y = int((self.centery-self.y) * 2 / self.distance)
if x == y == 0: return
for enemy in self.enemies: self.map[enemy.x][enemy.y] = EMPTY
self.map[MIDDLE][MIDDLE] = EMPTY
if x:
- self.middlex -= x * self.distance
+ self.centerx -= x * self.distance
self.map.rotate(x)
self.rotatex += x
if y:
- self.middley -= y * self.distance
+ self.centery -= y * self.distance
for d in self.map: d.rotate(y)
self.rotatey += y
self.map[MIDDLE][MIDDLE] = HERO
@@ -174,7 +177,7 @@ class Maze:
return ((self.x-x)**2 + (self.y-y)**2)**0.5
def slash(self):
- """Handle close-ranged attacks."""
+ """Handle close-range attacks."""
for enemy in self.enemies:
if not enemy.spin_queue: continue
x, y = enemy.pos()
@@ -249,14 +252,13 @@ class Maze:
if self.paused: return
self.fps, step = fps, self.distance * HERO_SPEED / fps
dx = step * self.right * self.isvalid(step, dx=self.right)
- self.middlex += dx
+ self.centerx += dx
dy = step * self.down * self.isvalid(step, dy=self.down)
- self.middley += dy
+ self.centery += dy
if dx or dy:
self.rotate()
- for enemy in self.enemies:
- if not enemy.awake: self.wake(enemy)
+ for enemy in self.enemies: enemy.wake()
for bullet in self.bullets: bullet.place(dx, dy)
self.draw()
@@ -275,12 +277,12 @@ class Maze:
self.surface = pygame.display.set_mode(size, RESIZABLE)
self.hero.resize()
- offsetx = (self.middlex-self.x) / self.distance
- offsety = (self.middley-self.y) / self.distance
+ offsetx = (self.centerx-self.x) / self.distance
+ offsety = (self.centery-self.y) / self.distance
self.distance = (w * h / 416) ** 0.5
- self.x, self.y = w >> 1, h >> 1
- self.middlex = self.x + offsetx*self.distance
- self.middley = self.y + offsety*self.distance
+ self.x, self.y = w // 2, h // 2
+ self.centerx = self.x + offsetx*self.distance
+ self.centery = self.y + offsety*self.distance
w, h = int(w/self.distance/2 + 2), int(h/self.distance/2 + 2)
self.rangex = range(MIDDLE - w, MIDDLE + w + 1)
self.rangey = range(MIDDLE - h, MIDDLE + h + 1)
diff --git a/brutalmaze/weapons.py b/brutalmaze/weapons.py
index 062ba68..7504457 100644
--- a/brutalmaze/weapons.py
+++ b/brutalmaze/weapons.py
@@ -28,7 +28,15 @@ from .utils import regpoly, fill_aapolygon
class Bullet:
- """Object representing a bullet."""
+ """Object representing a bullet.
+
+ Attributes:
+ surface (pygame.Surface): the display to draw on
+ x, y (int): coordinates of the center of the bullet (in pixels)
+ angle (float): angle of the direction the bullet pointing (in radians)
+ color (pygame.Color): color of the bullet
+ fall_time (int): the tick that the bullet will fall down
+ """
def __init__(self, surface, x, y, angle, color):
self.surface = surface
self.x, self.y, self.angle, self.color = x, y, angle, color
diff --git a/setup.py b/setup.py
index b1f9853..cc739a2 100755
--- a/setup.py
+++ b/setup.py
@@ -7,8 +7,8 @@ with open('README.rst') as f:
setup(
name='brutalmaze',
- version='0.0.1',
- description='Brutal Maze',
+ version='0.0.2',
+ description='A research hash and slash game with fast-paced action and a minimalist art style',
long_description=long_description,
url='https://github.com/McSinyx/brutalmaze',
author='Nguyễn Gia Phong',