From b5039285d5a95d1921d1d4428032c16fe94da897 Mon Sep 17 00:00:00 2001 From: Nguyễn Gia Phong Date: Tue, 6 Mar 2018 21:01:27 +0700 Subject: Retain game state after pauses --- brutalmaze/characters.py | 59 ++++++++++++++++++++++++++++-------------------- brutalmaze/constants.py | 2 +- brutalmaze/game.py | 23 ++++++++----------- brutalmaze/maze.py | 39 ++++++++++++++++---------------- brutalmaze/weapons.py | 9 ++++---- setup.py | 2 +- wiki | 2 +- 7 files changed, 71 insertions(+), 65 deletions(-) diff --git a/brutalmaze/characters.py b/brutalmaze/characters.py index 2f17b42..daa97bc 100644 --- a/brutalmaze/characters.py +++ b/brutalmaze/characters.py @@ -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) diff --git a/brutalmaze/constants.py b/brutalmaze/constants.py index 8b0acf8..cb65d88 100644 --- a/brutalmaze/constants.py +++ b/brutalmaze/constants.py @@ -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) diff --git a/brutalmaze/game.py b/brutalmaze/game.py index 963b55c..f6acb99 100644 --- a/brutalmaze/game.py +++ b/brutalmaze/game.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU Affero General Public License # along with Brutal Maze. If not, see . -__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: diff --git a/brutalmaze/maze.py b/brutalmaze/maze.py index b25662e..c3d1ff7 100644 --- a/brutalmaze/maze.py +++ b/brutalmaze/maze.py @@ -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 diff --git a/brutalmaze/weapons.py b/brutalmaze/weapons.py index f99d694..7eae843 100644 --- a/brutalmaze/weapons.py +++ b/brutalmaze/weapons.py @@ -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: diff --git a/setup.py b/setup.py index 91f480c..ba3e88a 100755 --- a/setup.py +++ b/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', diff --git a/wiki b/wiki index 7d455d5..bcb9563 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 7d455d5a9e1933963a86ea475008582ec961c22d +Subproject commit bcb956351f06ad378add2e65f43644fdae110ae9 -- cgit 1.4.1