Update documentations
This commit is contained in:
parent
13e0ea6f98
commit
b1f0c052f9
21
README.rst
21
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
|
(if there is any). Be aware that the more get killed, the more will show up and
|
||||||
our hero will get weaker when wounded.
|
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.
|
* Highly portable.
|
||||||
* Auto-generated and infinite maze.
|
* Auto-generated and infinite maze.
|
||||||
* No binary art data.
|
* No binary data for drawing.
|
||||||
* Enemies with randomized attributes: stun, poison, etc.
|
* Enemies with randomized attributes: stun, poison, camo, etc.
|
||||||
* Resizable in realtime.
|
* Somewhat a realistic physic and logic system.
|
||||||
|
* Resizable game window in-game.
|
||||||
|
|
||||||
Installation
|
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:
|
The installation procedure should be as simply as follow:
|
||||||
|
|
||||||
* Install Python and `pip <https://pip.pypa.io/en/latest/>`_. Make sure the
|
* Install Python and `pip <https://pip.pypa.io/en/latest/>`_. Make sure the
|
||||||
directory for Python executables is your ``PATH``.
|
directory for `Python scripts <https://docs.python.org/2/install/index.html#alternate-installation-the-user-scheme>`_
|
||||||
* Clone the Github repository or download the Zip achieve and unpack.
|
is your ``PATH``.
|
||||||
* Open Terminal in the directory containing the repo's folder and run
|
* Open Terminal or Command Prompt and run ``pip install --user brutalmaze``.
|
||||||
``pip install --user brutalmaze``.
|
Now you can lauch the game by running the command ``brutalmaze``.
|
||||||
|
|
||||||
Control
|
Control
|
||||||
-------
|
-------
|
||||||
|
@ -54,7 +55,7 @@ Right, ``d``
|
||||||
Move right.
|
Move right.
|
||||||
|
|
||||||
Left Mouse
|
Left Mouse
|
||||||
Long-ranged attack.
|
Long-range attack.
|
||||||
|
|
||||||
Return, Right Mouse
|
Return, Right Mouse
|
||||||
Close-ranged attack.
|
Close-range attack, also dodge from bullets.
|
||||||
|
|
|
@ -20,18 +20,33 @@
|
||||||
__doc__ = 'brutalmaze module for hero and enemy classes'
|
__doc__ = 'brutalmaze module for hero and enemy classes'
|
||||||
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from math import atan2, sin, pi
|
from math import atan, atan2, sin, pi
|
||||||
from random import choice, shuffle, uniform
|
from random import choice, shuffle, uniform
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .utils import randsign, regpoly, fill_aapolygon, sign
|
from .utils import sign, cosin, randsign, regpoly, fill_aapolygon
|
||||||
from .weapons import Bullet
|
from .weapons import Bullet
|
||||||
|
|
||||||
|
|
||||||
class Hero:
|
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):
|
def __init__(self, surface, fps):
|
||||||
self.surface = surface
|
self.surface = surface
|
||||||
w, h = self.surface.get_width(), self.surface.get_height()
|
w, h = self.surface.get_width(), self.surface.get_height()
|
||||||
|
@ -82,15 +97,29 @@ class Hero:
|
||||||
|
|
||||||
|
|
||||||
class Enemy:
|
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):
|
def __init__(self, maze, x, y):
|
||||||
self.maze = maze
|
self.maze = maze
|
||||||
self.angle, self.color = pi / 4, TANGO[choice(ENEMIES)]
|
|
||||||
self.x, self.y = x, y
|
self.x, self.y = x, y
|
||||||
self.maze.map[x][y] = ENEMY
|
self.maze.map[x][y] = ENEMY
|
||||||
|
self.angle, self.color = pi / 4, TANGO[choice(ENEMIES)]
|
||||||
|
|
||||||
self.awake = False
|
self.awake = False
|
||||||
self.next_move = 0
|
self.next_strike = 0
|
||||||
self.move_speed = self.maze.fps / ENEMY_SPEED
|
self.move_speed = self.maze.fps / ENEMY_SPEED
|
||||||
self.offsetx = self.offsety = 0
|
self.offsetx = self.offsety = 0
|
||||||
self.spin_speed = self.maze.fps / ENEMY_HP
|
self.spin_speed = self.maze.fps / ENEMY_HP
|
||||||
|
@ -108,15 +137,33 @@ class Enemy:
|
||||||
self.y += y
|
self.y += y
|
||||||
self.maze.map[self.x][self.y] = ENEMY
|
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):
|
def fire(self):
|
||||||
"""Return True if the enemy shot the hero, False otherwise."""
|
"""Return True if the enemy shot the hero, False otherwise."""
|
||||||
x, y = self.pos()
|
x, y = self.pos()
|
||||||
if (self.maze.length(x, y) > FIRANGE*self.maze.distance
|
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 (self.x, self.y) in AROUND_HERO or self.offsetx or self.offsety
|
||||||
or uniform(-2, 2) < (INIT_SCORE/self.maze.score) ** 2):
|
or uniform(-2, 2) < (INIT_SCORE/self.maze.score) ** 2):
|
||||||
return False
|
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.bullets.append(Bullet(
|
||||||
self.maze.surface, x, y,
|
self.maze.surface, x, y,
|
||||||
atan2(self.maze.y - y, self.maze.x - x), self.color[0]))
|
atan2(self.maze.y - y, self.maze.x - x), self.color[0]))
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
__doc__ = 'brutalmaze module for the maze class'
|
__doc__ = 'brutalmaze module for the maze class'
|
||||||
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from math import pi, atan, atan2, log
|
from math import pi, atan2, log
|
||||||
from random import choice, getrandbits
|
from random import choice, getrandbits
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
|
@ -28,7 +28,7 @@ from pygame import RESIZABLE
|
||||||
|
|
||||||
from .characters import Hero, Enemy
|
from .characters import Hero, Enemy
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .utils import round2, sign, cosin, regpoly, fill_aapolygon
|
from .utils import round2, sign, regpoly, fill_aapolygon
|
||||||
from .weapons import Bullet
|
from .weapons import Bullet
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,13 +53,33 @@ def new_column():
|
||||||
|
|
||||||
|
|
||||||
class Maze:
|
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):
|
def __init__(self, size, fps):
|
||||||
self.w, self.h = size
|
self.w, self.h = size
|
||||||
self.fps = fps
|
self.fps = fps
|
||||||
self.surface = pygame.display.set_mode(size, RESIZABLE)
|
self.surface = pygame.display.set_mode(size, RESIZABLE)
|
||||||
self.distance = (self.w * self.h / 416) ** 0.5
|
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)
|
w, h = (int(i/self.distance/2 + 2) for i in size)
|
||||||
self.rangex = range(MIDDLE - w, MIDDLE + w + 1)
|
self.rangex = range(MIDDLE - w, MIDDLE + w + 1)
|
||||||
self.rangey = range(MIDDLE - h, MIDDLE + h + 1)
|
self.rangey = range(MIDDLE - h, MIDDLE + h + 1)
|
||||||
|
@ -90,8 +110,8 @@ class Maze:
|
||||||
|
|
||||||
def pos(self, x, y):
|
def pos(self, x, y):
|
||||||
"""Return coordinate of the center of the grid (x, y)."""
|
"""Return coordinate of the center of the grid (x, y)."""
|
||||||
return (self.middlex + (x - MIDDLE)*self.distance,
|
return (self.centerx + (x - MIDDLE)*self.distance,
|
||||||
self.middley + (y - MIDDLE)*self.distance)
|
self.centery + (y - MIDDLE)*self.distance)
|
||||||
|
|
||||||
def draw(self):
|
def draw(self):
|
||||||
"""Draw the maze."""
|
"""Draw the maze."""
|
||||||
|
@ -103,36 +123,19 @@ class Maze:
|
||||||
square = regpoly(4, self.distance / SQRT2, pi / 4, x, y)
|
square = regpoly(4, self.distance / SQRT2, pi / 4, x, y)
|
||||||
fill_aapolygon(self.surface, square, FG_COLOR)
|
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):
|
def rotate(self):
|
||||||
"""Rotate the maze if needed."""
|
"""Rotate the maze if needed."""
|
||||||
x = int((self.middlex-self.x) * 2 / self.distance)
|
x = int((self.centerx-self.x) * 2 / self.distance)
|
||||||
y = int((self.middley-self.y) * 2 / self.distance)
|
y = int((self.centery-self.y) * 2 / self.distance)
|
||||||
if x == y == 0: return
|
if x == y == 0: return
|
||||||
for enemy in self.enemies: self.map[enemy.x][enemy.y] = EMPTY
|
for enemy in self.enemies: self.map[enemy.x][enemy.y] = EMPTY
|
||||||
self.map[MIDDLE][MIDDLE] = EMPTY
|
self.map[MIDDLE][MIDDLE] = EMPTY
|
||||||
if x:
|
if x:
|
||||||
self.middlex -= x * self.distance
|
self.centerx -= x * self.distance
|
||||||
self.map.rotate(x)
|
self.map.rotate(x)
|
||||||
self.rotatex += x
|
self.rotatex += x
|
||||||
if y:
|
if y:
|
||||||
self.middley -= y * self.distance
|
self.centery -= y * self.distance
|
||||||
for d in self.map: d.rotate(y)
|
for d in self.map: d.rotate(y)
|
||||||
self.rotatey += y
|
self.rotatey += y
|
||||||
self.map[MIDDLE][MIDDLE] = HERO
|
self.map[MIDDLE][MIDDLE] = HERO
|
||||||
|
@ -174,7 +177,7 @@ class Maze:
|
||||||
return ((self.x-x)**2 + (self.y-y)**2)**0.5
|
return ((self.x-x)**2 + (self.y-y)**2)**0.5
|
||||||
|
|
||||||
def slash(self):
|
def slash(self):
|
||||||
"""Handle close-ranged attacks."""
|
"""Handle close-range attacks."""
|
||||||
for enemy in self.enemies:
|
for enemy in self.enemies:
|
||||||
if not enemy.spin_queue: continue
|
if not enemy.spin_queue: continue
|
||||||
x, y = enemy.pos()
|
x, y = enemy.pos()
|
||||||
|
@ -249,14 +252,13 @@ class Maze:
|
||||||
if self.paused: return
|
if self.paused: return
|
||||||
self.fps, step = fps, self.distance * HERO_SPEED / fps
|
self.fps, step = fps, self.distance * HERO_SPEED / fps
|
||||||
dx = step * self.right * self.isvalid(step, dx=self.right)
|
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)
|
dy = step * self.down * self.isvalid(step, dy=self.down)
|
||||||
self.middley += dy
|
self.centery += dy
|
||||||
|
|
||||||
if dx or dy:
|
if dx or dy:
|
||||||
self.rotate()
|
self.rotate()
|
||||||
for enemy in self.enemies:
|
for enemy in self.enemies: enemy.wake()
|
||||||
if not enemy.awake: self.wake(enemy)
|
|
||||||
for bullet in self.bullets: bullet.place(dx, dy)
|
for bullet in self.bullets: bullet.place(dx, dy)
|
||||||
|
|
||||||
self.draw()
|
self.draw()
|
||||||
|
@ -275,12 +277,12 @@ class Maze:
|
||||||
self.surface = pygame.display.set_mode(size, RESIZABLE)
|
self.surface = pygame.display.set_mode(size, RESIZABLE)
|
||||||
self.hero.resize()
|
self.hero.resize()
|
||||||
|
|
||||||
offsetx = (self.middlex-self.x) / self.distance
|
offsetx = (self.centerx-self.x) / self.distance
|
||||||
offsety = (self.middley-self.y) / self.distance
|
offsety = (self.centery-self.y) / self.distance
|
||||||
self.distance = (w * h / 416) ** 0.5
|
self.distance = (w * h / 416) ** 0.5
|
||||||
self.x, self.y = w >> 1, h >> 1
|
self.x, self.y = w // 2, h // 2
|
||||||
self.middlex = self.x + offsetx*self.distance
|
self.centerx = self.x + offsetx*self.distance
|
||||||
self.middley = self.y + offsety*self.distance
|
self.centery = self.y + offsety*self.distance
|
||||||
w, h = int(w/self.distance/2 + 2), int(h/self.distance/2 + 2)
|
w, h = int(w/self.distance/2 + 2), int(h/self.distance/2 + 2)
|
||||||
self.rangex = range(MIDDLE - w, MIDDLE + w + 1)
|
self.rangex = range(MIDDLE - w, MIDDLE + w + 1)
|
||||||
self.rangey = range(MIDDLE - h, MIDDLE + h + 1)
|
self.rangey = range(MIDDLE - h, MIDDLE + h + 1)
|
||||||
|
|
|
@ -28,7 +28,15 @@ from .utils import regpoly, fill_aapolygon
|
||||||
|
|
||||||
|
|
||||||
class Bullet:
|
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):
|
def __init__(self, surface, x, y, angle, color):
|
||||||
self.surface = surface
|
self.surface = surface
|
||||||
self.x, self.y, self.angle, self.color = x, y, angle, color
|
self.x, self.y, self.angle, self.color = x, y, angle, color
|
||||||
|
|
4
setup.py
4
setup.py
|
@ -7,8 +7,8 @@ with open('README.rst') as f:
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='brutalmaze',
|
name='brutalmaze',
|
||||||
version='0.0.1',
|
version='0.0.2',
|
||||||
description='Brutal Maze',
|
description='A research hash and slash game with fast-paced action and a minimalist art style',
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
url='https://github.com/McSinyx/brutalmaze',
|
url='https://github.com/McSinyx/brutalmaze',
|
||||||
author='Nguyễn Gia Phong',
|
author='Nguyễn Gia Phong',
|
||||||
|
|
Loading…
Reference in New Issue