Retain game state after pauses
This commit is contained in:
parent
f7c600934e
commit
b5039285d5
|
@ -23,8 +23,6 @@ from math import atan, atan2, sin, pi
|
|||
from random import choice, randrange, shuffle
|
||||
from sys import modules
|
||||
|
||||
from pygame.time import get_ticks
|
||||
|
||||
from .constants import (
|
||||
TANGO, HERO_HP, SFX_HEART, HEAL_SPEED, MIN_BEAT, ATTACK_SPEED, ENEMY,
|
||||
ENEMY_SPEED, ENEMY_HP, SFX_SLASH_HERO, MIDDLE, WALL, FIRANGE, AROUND_HERO,
|
||||
|
@ -42,9 +40,9 @@ class Hero:
|
|||
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_heal (int): the tick that the hero gains back healing ability
|
||||
next_beat (int): the tick to play next heart beat
|
||||
next_strike (int): the tick that the hero can do the next attack
|
||||
next_heal (float): ms until the hero gains back healing ability
|
||||
next_beat (float): time until next heart beat (in ms)
|
||||
next_strike (float): time until the hero can do the next attack (in ms)
|
||||
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
|
||||
|
@ -60,7 +58,7 @@ class Hero:
|
|||
self.angle, self.color = -pi * 3 / 4, TANGO['Aluminium']
|
||||
self.R = (w * h / sin(pi*2/3) / 624) ** 0.5
|
||||
|
||||
self.next_heal = self.next_beat = self.next_strike = 0
|
||||
self.next_heal = self.next_beat = self.next_strike = 0.0
|
||||
self.slashing = self.firing = self.dead = False
|
||||
self.spin_speed = fps / HERO_HP
|
||||
self.spin_queue = self.wound = 0.0
|
||||
|
@ -72,19 +70,24 @@ class Hero:
|
|||
if self.dead:
|
||||
self.spin_queue = 0.0
|
||||
return
|
||||
old_speed, time = self.spin_speed, get_ticks()
|
||||
old_speed = self.spin_speed
|
||||
self.spin_speed = fps / (HERO_HP-self.wound**0.5)
|
||||
self.spin_queue *= self.spin_speed / old_speed
|
||||
if time >= self.next_heal:
|
||||
if self.next_heal <= 0:
|
||||
self.wound -= HEAL_SPEED / self.spin_speed / HERO_HP
|
||||
if self.wound < 0: self.wound = 0.0
|
||||
if time >= self.next_beat:
|
||||
else:
|
||||
self.next_heal -= 1000.0 / fps
|
||||
if self.next_beat <= 0:
|
||||
play(self.sfx_heart)
|
||||
self.next_beat = time + MIN_BEAT*(2 - self.wound/HERO_HP)
|
||||
self.next_beat = MIN_BEAT*(2 - self.wound/HERO_HP)
|
||||
else:
|
||||
self.next_beat -= 1000.0 / fps
|
||||
self.next_strike -= 1000.0 / fps
|
||||
|
||||
full_spin = pi * 2 / self.get_sides()
|
||||
if self.slashing and time >= self.next_strike:
|
||||
self.next_strike = time + ATTACK_SPEED
|
||||
if self.slashing and self.next_strike <= 0:
|
||||
self.next_strike = ATTACK_SPEED
|
||||
self.spin_queue = randsign() * self.spin_speed
|
||||
self.angle -= sign(self.spin_queue) * full_spin
|
||||
if abs(self.spin_queue) > 0.5:
|
||||
|
@ -97,7 +100,7 @@ class Hero:
|
|||
"""Return the number of sides the hero has. While the hero is
|
||||
generally a trigon, Agent Orange may turn him into a square.
|
||||
"""
|
||||
return 3 if get_ticks() >= self.next_heal else 4
|
||||
return 3 if self.next_heal <= 0 else 4
|
||||
|
||||
def update_angle(self, angle):
|
||||
"""Turn to the given angle if the hero is not busy slashing."""
|
||||
|
@ -106,7 +109,7 @@ class Hero:
|
|||
unit = pi * 2 / self.get_sides() / self.spin_speed
|
||||
if abs(delta) < unit:
|
||||
self.angle, self.spin_queue = angle, 0.0
|
||||
elif get_ticks() >= self.next_strike:
|
||||
elif self.next_strike <= 0:
|
||||
self.spin_queue = delta / unit
|
||||
|
||||
def get_color(self):
|
||||
|
@ -134,7 +137,7 @@ class Enemy:
|
|||
angle (float): angle of the direction the enemy pointing (in radians)
|
||||
color (str): enemy's color name
|
||||
awake (bool): flag indicates if the enemy is active
|
||||
next_strike (int): the tick that the enemy can do the next attack
|
||||
next_strike (float): time until the enemy's next action (in ms)
|
||||
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)
|
||||
|
@ -149,7 +152,7 @@ class Enemy:
|
|||
self.angle, self.color = pi / 4, color
|
||||
|
||||
self.awake = False
|
||||
self.next_strike = 0
|
||||
self.next_strike = 0.0
|
||||
self.move_speed = self.maze.fps / ENEMY_SPEED
|
||||
self.offsetx = self.offsety = 0
|
||||
self.spin_speed = self.maze.fps / ENEMY_HP
|
||||
|
@ -207,11 +210,11 @@ class Enemy:
|
|||
if self.maze.hero.dead: return False
|
||||
x, y = self.get_pos()
|
||||
if (self.maze.get_distance(x, y) > FIRANGE*self.maze.distance
|
||||
or get_ticks() < self.next_strike
|
||||
or self.next_strike > 0
|
||||
or (self.x, self.y) in AROUND_HERO or self.offsetx or self.offsety
|
||||
or randrange((self.maze.hero.slashing+self.maze.isfast()+1) * 3)):
|
||||
return False
|
||||
self.next_strike = get_ticks() + ATTACK_SPEED
|
||||
self.next_strike = ATTACK_SPEED
|
||||
self.maze.bullets.append(
|
||||
Bullet(self.maze.surface, x, y, self.get_angle() + pi, self.color))
|
||||
return True
|
||||
|
@ -224,7 +227,7 @@ class Enemy:
|
|||
if self.offsety:
|
||||
self.offsety -= sign(self.offsety)
|
||||
return True
|
||||
if get_ticks() < self.next_strike: return False
|
||||
if self.next_strike > 0: return False
|
||||
|
||||
self.move_speed = self.maze.fps / speed
|
||||
directions = [(sign(MIDDLE - self.x), 0), (0, sign(MIDDLE - self.y))]
|
||||
|
@ -267,7 +270,7 @@ class Enemy:
|
|||
|
||||
def draw(self):
|
||||
"""Draw the enemy."""
|
||||
if get_ticks() < self.maze.next_move and not self.awake: return
|
||||
if self.maze.next_move > 0 and not self.awake: return
|
||||
radius = self.maze.distance/SQRT2 - self.awake*2
|
||||
square = regpoly(4, radius, self.angle, *self.get_pos())
|
||||
fill_aapolygon(self.maze.surface, square, self.get_color())
|
||||
|
@ -277,6 +280,7 @@ class Enemy:
|
|||
if self.awake:
|
||||
self.spin_speed, tmp = self.maze.fps / ENEMY_HP, self.spin_speed
|
||||
self.spin_queue *= self.spin_speed / tmp
|
||||
self.next_strike -= 1000.0 / self.maze.fps
|
||||
if not self.spin_queue and not self.fire() and not self.move():
|
||||
self.spin_queue = randsign() * self.spin_speed
|
||||
if not self.maze.hero.dead:
|
||||
|
@ -305,25 +309,30 @@ class Chameleon(Enemy):
|
|||
"""Object representing an enemy of Chameleon.
|
||||
|
||||
Additional attributes:
|
||||
visible (int): the tick until which the Chameleon is visible
|
||||
visible (float): time until the Chameleon is visible (in ms)
|
||||
"""
|
||||
def __init__(self, maze, x, y):
|
||||
Enemy.__init__(self, maze, x, y, 'Chameleon')
|
||||
self.visible = 0
|
||||
self.visible = 0.0
|
||||
|
||||
def wake(self):
|
||||
"""Wake the Chameleon up if it can see the hero."""
|
||||
if Enemy.wake(self) is True:
|
||||
self.visible = get_ticks() + 1000//ENEMY_SPEED
|
||||
self.visible = 1000.0 / ENEMY_SPEED
|
||||
|
||||
def draw(self):
|
||||
"""Draw the Chameleon."""
|
||||
if not self.awake or get_ticks() < self.visible or self.spin_queue:
|
||||
if not self.awake or self.visible > 0 or self.spin_queue:
|
||||
Enemy.draw(self)
|
||||
|
||||
def update(self):
|
||||
"""Update the Chameleon."""
|
||||
Enemy.update(self)
|
||||
if self.awake: self.visible -= 1000.0 / self.maze.fps
|
||||
|
||||
def hit(self, wound):
|
||||
"""Handle the Chameleon when it's attacked."""
|
||||
self.visible = get_ticks() + 1000//ENEMY_SPEED
|
||||
self.visible = 1000.0 / ENEMY_SPEED
|
||||
Enemy.hit(self, wound)
|
||||
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ HEAL_SPEED = 1 # HP/s
|
|||
HERO_SPEED = 5 # grid/s
|
||||
ENEMY_SPEED = 6 # grid/s
|
||||
BULLET_SPEED = 15 # grid/s
|
||||
ATTACK_SPEED = 333 # ms/strike
|
||||
ATTACK_SPEED = 333.333 # ms/strike
|
||||
FIRANGE = 6 # grids
|
||||
BULLET_LIFETIME = 1000.0 * FIRANGE / (BULLET_SPEED-HERO_SPEED) # ms
|
||||
EMPTY, WALL, HERO, ENEMY = range(4)
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Brutal Maze. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
__version__ = '0.5.6'
|
||||
__version__ = '0.6.0'
|
||||
|
||||
import re
|
||||
from argparse import ArgumentParser, FileType, RawTextHelpFormatter
|
||||
|
@ -26,13 +26,12 @@ try: # Python 3
|
|||
from configparser import ConfigParser
|
||||
except ImportError: # Python 2
|
||||
from ConfigParser import ConfigParser
|
||||
from math import atan2, degrees, radians, pi
|
||||
from math import atan2, radians, pi
|
||||
from os.path import join, pathsep
|
||||
from socket import socket, SOL_SOCKET, SO_REUSEADDR
|
||||
from sys import stdout
|
||||
from threading import Thread
|
||||
|
||||
|
||||
import pygame
|
||||
from pygame import DOUBLEBUF, KEYDOWN, OPENGL, QUIT, RESIZABLE, VIDEORESIZE
|
||||
from pygame.time import Clock, get_ticks
|
||||
|
@ -147,21 +146,21 @@ class Game:
|
|||
|
||||
def export(self):
|
||||
"""Export maze data to a bytes object."""
|
||||
maze, hero, time = self.maze, self.hero, get_ticks()
|
||||
maze, hero, = self.maze, self.hero
|
||||
lines = deque(['{0} {4} {5} {1} {2:d} {3:d}'.format(
|
||||
COLORS[hero.get_color()], deg(self.hero.angle),
|
||||
hero.next_strike <= time, hero.next_heal <= time,
|
||||
hero.next_strike <= 0, hero.next_heal <= 0,
|
||||
*self.expos(maze.x, maze.y))])
|
||||
|
||||
walls = [[1 if maze.map[x][y] == WALL else 0 for x in maze.rangex]
|
||||
for y in maze.rangey] if maze.next_move <= time else []
|
||||
for y in maze.rangey] if maze.next_move <= 0 else []
|
||||
ne = nb = 0
|
||||
|
||||
for enemy in maze.enemies:
|
||||
if not enemy.awake and walls:
|
||||
walls[enemy.y-maze.rangey[0]][enemy.x-maze.rangex[0]] = WALL
|
||||
continue
|
||||
elif enemy.color == 'Chameleon' and maze.next_move <= time:
|
||||
elif enemy.color == 'Chameleon' and maze.next_move <= 0:
|
||||
continue
|
||||
lines.append('{0} {2} {3} {1:.0f}'.format(
|
||||
COLORS[enemy.get_color()], deg(enemy.angle),
|
||||
|
@ -212,20 +211,18 @@ class Game:
|
|||
self.fps -= 1
|
||||
elif self.fps < self.max_fps and not self.paused:
|
||||
self.fps += 5
|
||||
if not self.paused:
|
||||
self.maze.update(self.fps)
|
||||
if not self.headless: self.maze.draw()
|
||||
if not self.paused: self.maze.update(self.fps)
|
||||
if not self.headless: self.maze.draw()
|
||||
self.clock.tick(self.fps)
|
||||
return True
|
||||
|
||||
def move(self, x, y):
|
||||
"""Command the hero to move faster in the given direction."""
|
||||
x, y = -x, -y # or move the maze in the reverse direction
|
||||
stunned = pygame.time.get_ticks() < self.maze.next_move
|
||||
velocity = self.maze.distance * HERO_SPEED / self.fps
|
||||
accel = velocity * HERO_SPEED / self.fps
|
||||
|
||||
if stunned or not x:
|
||||
if self.maze.next_move > 0 or not x:
|
||||
self.maze.vx -= sign(self.maze.vx) * accel
|
||||
if abs(self.maze.vx) < accel * 2: self.maze.vx = 0.0
|
||||
elif x * self.maze.vx < 0:
|
||||
|
@ -234,7 +231,7 @@ class Game:
|
|||
self.maze.vx += x * accel
|
||||
if abs(self.maze.vx) > velocity: self.maze.vx = x * velocity
|
||||
|
||||
if stunned or not y:
|
||||
if self.maze.next_move > 0 or not y:
|
||||
self.maze.vy -= sign(self.maze.vy) * accel
|
||||
if abs(self.maze.vy) < accel * 2: self.maze.vy = 0.0
|
||||
elif y * self.maze.vy < 0:
|
||||
|
|
|
@ -24,7 +24,6 @@ from math import pi, log
|
|||
from random import choice, getrandbits, uniform
|
||||
|
||||
import pygame
|
||||
from pygame.time import get_ticks
|
||||
|
||||
from .characters import Hero, new_enemy
|
||||
from .constants import (
|
||||
|
@ -75,8 +74,8 @@ class Maze:
|
|||
enemy_weights (dict): probabilities of enemies to be created
|
||||
enemies (list of Enemy): alive enemies
|
||||
hero (Hero): the hero
|
||||
next_move (int): the tick that the hero gets mobilized
|
||||
next_slashfx (int): the tick to play next slash effect of the hero
|
||||
next_move (float): time until the hero gets mobilized (in ms)
|
||||
next_slashfx (float): time until next slash effect of the hero (in ms)
|
||||
slashd (float): minimum distance for slashes to be effective
|
||||
sfx_slash (pygame.mixer.Sound): sound effect of slashed enemy
|
||||
sfx_lose (pygame.mixer.Sound): sound effect to be played when you lose
|
||||
|
@ -107,7 +106,7 @@ class Maze:
|
|||
self.add_enemy()
|
||||
self.hero = Hero(self.surface, fps, size)
|
||||
self.map[MIDDLE][MIDDLE] = HERO
|
||||
self.next_move = self.next_slashfx = 0
|
||||
self.next_move = self.next_slashfx = 0.0
|
||||
self.slashd = self.hero.R + self.distance/SQRT2
|
||||
|
||||
self.sfx_spawn = SFX_SPAWN
|
||||
|
@ -144,7 +143,7 @@ class Maze:
|
|||
def draw(self):
|
||||
"""Draw the maze."""
|
||||
self.surface.fill(BG_COLOR)
|
||||
if get_ticks() >= self.next_move:
|
||||
if self.next_move <= 0:
|
||||
for i in self.rangex:
|
||||
for j in self.rangey:
|
||||
if self.map[i][j] != WALL: continue
|
||||
|
@ -217,13 +216,12 @@ class Maze:
|
|||
"""Handle the hero when he loses HP."""
|
||||
fx = (uniform(0, sum(self.enemy_weights.values()))
|
||||
< self.enemy_weights[color])
|
||||
time = get_ticks()
|
||||
if (color == 'Butter' or color == 'ScarletRed') and fx:
|
||||
self.hero.wound += wound * 2.5
|
||||
elif color == 'Orange' and fx:
|
||||
self.hero.next_heal = max(self.hero.next_heal, time) + wound*1000
|
||||
self.hero.next_heal = max(self.hero.next_heal, 0) + wound*1000
|
||||
elif color == 'SkyBlue' and fx:
|
||||
self.next_move = max(self.next_move, time) + wound*1000
|
||||
self.next_move = max(self.next_move, 0) + wound*1000
|
||||
else:
|
||||
self.hero.wound += wound
|
||||
if self.enemy_weights[color] + wound < MAXW:
|
||||
|
@ -238,10 +236,10 @@ class Maze:
|
|||
for i, enemy in enumerate(self.enemies):
|
||||
d = self.slashd - enemy.get_distance()
|
||||
if d > 0:
|
||||
wound, time = d * SQRT2 / self.distance, get_ticks()
|
||||
if time >= self.next_slashfx:
|
||||
wound = d * SQRT2 / self.distance
|
||||
if self.next_slashfx <= 0:
|
||||
play(self.sfx_slash, wound, enemy.get_angle())
|
||||
self.next_slashfx = time + ATTACK_SPEED
|
||||
self.next_slashfx = ATTACK_SPEED
|
||||
enemy.hit(wound / self.hero.spin_speed)
|
||||
if enemy.wound >= ENEMY_HP:
|
||||
self.score += enemy.wound
|
||||
|
@ -252,21 +250,21 @@ class Maze:
|
|||
|
||||
def track_bullets(self):
|
||||
"""Handle the bullets."""
|
||||
fallen, time = [], get_ticks()
|
||||
fallen = []
|
||||
if (self.hero.firing and not self.hero.slashing
|
||||
and time >= self.hero.next_strike):
|
||||
self.hero.next_strike = time + ATTACK_SPEED
|
||||
and self.hero.next_strike <= 0):
|
||||
self.hero.next_strike = ATTACK_SPEED
|
||||
self.bullets.append(Bullet(self.surface, self.x, self.y,
|
||||
self.hero.angle, 'Aluminium'))
|
||||
for i, bullet in enumerate(self.bullets):
|
||||
wound = float(bullet.fall_time-time) / BULLET_LIFETIME
|
||||
wound = bullet.fall_time / BULLET_LIFETIME
|
||||
bullet.update(self.fps, self.distance)
|
||||
if wound < 0:
|
||||
fallen.append(i)
|
||||
elif bullet.color == 'Aluminium':
|
||||
x = MIDDLE + round2((bullet.x-self.x) / self.distance)
|
||||
y = MIDDLE + round2((bullet.y-self.y) / self.distance)
|
||||
if self.map[x][y] == WALL and time >= self.next_move:
|
||||
if self.map[x][y] == WALL and self.next_move <= 0:
|
||||
fallen.append(i)
|
||||
continue
|
||||
for j, enemy in enumerate(self.enemies):
|
||||
|
@ -282,9 +280,9 @@ class Maze:
|
|||
fallen.append(i)
|
||||
break
|
||||
elif bullet.get_distance(self.x, self.y) < self.distance:
|
||||
if self.hero.spin_queue and time >= self.hero.next_heal:
|
||||
if self.hero.spin_queue and self.hero.next_heal <= 0:
|
||||
self.hero.next_strike = (abs(self.hero.spin_queue*self.fps)
|
||||
+ time + ATTACK_SPEED)
|
||||
+ ATTACK_SPEED)
|
||||
play(bullet.sfx_missed, wound, bullet.angle + pi)
|
||||
else:
|
||||
self.hit_hero(wound, bullet.color)
|
||||
|
@ -319,6 +317,9 @@ class Maze:
|
|||
dy = self.is_valid_move(vy=self.vy)
|
||||
self.centery += dy
|
||||
|
||||
self.next_move -= 1000.0 / self.fps
|
||||
self.next_slashfx -= 1000.0 / self.fps
|
||||
|
||||
if dx or dy:
|
||||
self.rotate()
|
||||
for enemy in self.enemies: enemy.wake()
|
||||
|
@ -370,7 +371,7 @@ class Maze:
|
|||
self.enemy_weights = {color: MINW for color in ENEMIES}
|
||||
self.add_enemy()
|
||||
|
||||
self.next_move = self.next_slashfx = 0
|
||||
self.next_move = self.next_slashfx = 0.0
|
||||
self.hero.next_heal = self.hero.next_strike = 0
|
||||
self.hero.slashing = self.hero.firing = self.hero.dead = False
|
||||
self.hero.spin_queue = self.hero.wound = 0.0
|
||||
|
|
|
@ -21,8 +21,6 @@ __doc__ = 'Brutal Maze module for weapon classes'
|
|||
|
||||
from math import cos, sin
|
||||
|
||||
from pygame.time import get_ticks
|
||||
|
||||
from .constants import (BULLET_LIFETIME, SFX_SHOT_ENEMY, SFX_SHOT_HERO,
|
||||
SFX_MISSED, BULLET_SPEED, ENEMY_HP, TANGO, BG_COLOR)
|
||||
from .misc import regpoly, fill_aapolygon
|
||||
|
@ -36,14 +34,14 @@ class Bullet:
|
|||
x, y (int): coordinates of the center of the bullet (in pixels)
|
||||
angle (float): angle of the direction the bullet pointing (in radians)
|
||||
color (str): bullet's color name
|
||||
fall_time (int): the tick that the bullet will fall down
|
||||
fall_time (int): time until the bullet fall down
|
||||
sfx_hit (pygame.mixer.Sound): sound effect indicating target was hit
|
||||
sfx_missed (pygame.mixer.Sound): sound effect indicating a miss shot
|
||||
"""
|
||||
def __init__(self, surface, x, y, angle, color):
|
||||
self.surface = surface
|
||||
self.x, self.y, self.angle, self.color = x, y, angle, color
|
||||
self.fall_time = get_ticks() + BULLET_LIFETIME
|
||||
self.fall_time = BULLET_LIFETIME
|
||||
if color == 'Aluminium':
|
||||
self.sfx_hit = SFX_SHOT_ENEMY
|
||||
else:
|
||||
|
@ -55,10 +53,11 @@ class Bullet:
|
|||
s = distance * BULLET_SPEED / fps
|
||||
self.x += s * cos(self.angle)
|
||||
self.y += s * sin(self.angle)
|
||||
self.fall_time -= 1000.0 / fps
|
||||
|
||||
def get_color(self):
|
||||
"""Return current color of the enemy."""
|
||||
value = int((1-(self.fall_time-get_ticks())/BULLET_LIFETIME)*ENEMY_HP)
|
||||
value = int((1 - self.fall_time/BULLET_LIFETIME) * ENEMY_HP)
|
||||
try:
|
||||
return TANGO[self.color][value]
|
||||
except IndexError:
|
||||
|
|
2
setup.py
2
setup.py
|
@ -7,7 +7,7 @@ with open('README.rst') as f:
|
|||
|
||||
setup(
|
||||
name='brutalmaze',
|
||||
version='0.5.6',
|
||||
version='0.6.0',
|
||||
description='A minimalist hack and slash game with fast-paced action',
|
||||
long_description=long_description,
|
||||
url='https://github.com/McSinyx/brutalmaze',
|
||||
|
|
2
wiki
2
wiki
|
@ -1 +1 @@
|
|||
Subproject commit 7d455d5a9e1933963a86ea475008582ec961c22d
|
||||
Subproject commit bcb956351f06ad378add2e65f43644fdae110ae9
|
Loading…
Reference in New Issue