brutalmaze/brutalmaze/maze.py

531 lines
22 KiB
Python
Raw Permalink Normal View History

# maze.py - module for the maze class
2020-01-21 09:18:19 +01:00
# Copyright (C) 2017-2020 Nguyễn Gia Phong
2017-10-12 15:29:55 +02:00
#
# This file is part of Brutal Maze.
2017-10-12 15:29:55 +02:00
#
# Brutal Maze is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# Brutal Maze is distributed in the hope that it will be useful,
2017-10-12 15:29:55 +02:00
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
2017-10-12 15:29:55 +02:00
#
# 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/>.
2017-10-12 15:29:55 +02:00
__doc__ = 'Brutal Maze module for the maze class'
2017-11-02 15:39:06 +01:00
import json
2020-09-19 06:12:54 +02:00
from collections import defaultdict, deque
from math import log, pi
from os import path
from random import choice, sample
2017-10-12 15:29:55 +02:00
import pygame
2017-11-12 15:08:14 +01:00
from .characters import Hero, new_enemy
2020-09-19 06:12:54 +02:00
from .constants import (ADJACENTS, ATTACK_SPEED, BG_COLOR,
BULLET_LIFETIME, CELL_NODES, CELL_WIDTH, COLORS,
EMPTY, ENEMIES, ENEMY, ENEMY_HP, FG_COLOR, HERO,
HERO_HP, HERO_SPEED, INIT_SCORE, JSON_SEPARATORS,
MAX_WOUND, MAZE_SIZE, MIDDLE, ROAD_WIDTH,
SFX_LOSE, SFX_MISSED, SFX_SLASH_ENEMY, SFX_SPAWN,
SQRT2, TANGO_VALUES, WALL, WALL_WIDTH)
from .misc import around, deg, fill_aapolygon, json_rec, play, regpoly, sign
2018-10-07 16:59:39 +02:00
from .weapons import LockOn
2017-10-12 15:29:55 +02:00
class Maze:
2017-11-04 15:43:05 +01:00
"""Object representing the maze, including the characters.
Attributes:
w, h (int): width and height of the display (in px)
fps (float): current frame rate
2017-11-04 15:43:05 +01:00
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)
2018-02-26 15:02:11 +01:00
rangex, rangey (list): range of the index of the grids on display
2017-11-04 15:43:05 +01:00
score (float): current score
map (deque of deque): map of grids representing objects on the maze
vx, vy (float): velocity of the maze movement (in pixels per frame)
rotatex, rotatey (int): grids rotated
bullets (list of .weapons.Bullet): flying bullets
2017-11-04 15:43:05 +01:00
enemies (list of Enemy): alive enemies
hero (Hero): the hero
2018-05-20 15:33:50 +02:00
destx, desty (int): the grid the hero is moving to
stepx, stepy (int): direction the maze is moving
2018-10-07 16:59:39 +02:00
target (Enemy or LockOn): target to automatically aim at
2018-03-06 15:01:27 +01:00
next_move (float): time until the hero gets mobilized (in ms)
glitch (float): time that the maze remain flashing colors (in ms)
2018-03-06 15:01:27 +01:00
next_slashfx (float): time until next slash effect of the hero (in ms)
2017-11-04 15:43:05 +01:00
slashd (float): minimum distance for slashes to be effective
export (list of defaultdict): records of game states
export_dir (str): directory containing records of game states
export_rate (float): milliseconds per snapshot
next_export (float): time until next snapshot (in ms)
2017-11-04 15:43:05 +01:00
"""
def __init__(self, fps, size, headless, export_dir, export_rate):
2017-11-02 15:39:06 +01:00
self.fps = fps
self.w, self.h = size
if headless:
self.surface = None
else:
2018-03-07 10:13:34 +01:00
self.surface = pygame.display.set_mode(size, pygame.RESIZABLE)
2018-08-08 15:05:21 +02:00
self.export_dir = path.abspath(export_dir) if export_dir else ''
self.next_export = self.export_rate = export_rate
self.export = []
self.distance = (self.w * self.h / 416) ** 0.5
2017-11-04 15:43:05 +01:00
self.x, self.y = self.w // 2, self.h // 2
2020-01-21 09:18:19 +01:00
self.centerx, self.centery = self.w / 2, self.h / 2
2018-03-06 03:58:52 +01:00
w, h = (int(i/self.distance/2 + 1) for i in size)
2018-02-26 15:02:11 +01:00
self.rangex = list(range(MIDDLE - w, MIDDLE + w + 1))
self.rangey = list(range(MIDDLE - h, MIDDLE + h + 1))
self.score = INIT_SCORE
self.new_map()
2017-10-12 15:29:55 +02:00
self.vx = self.vy = 0.0
self.rotatex = self.rotatey = 0
self.bullets, self.enemies = [], []
self.add_enemy()
self.hero = Hero(self.surface, fps, size)
2018-10-07 16:59:39 +02:00
self.target = LockOn(MIDDLE, MIDDLE, retired=True)
self.next_move = self.glitch = self.next_slashfx = 0.0
2018-01-21 14:38:51 +01:00
self.slashd = self.hero.R + self.distance/SQRT2
self.sfx_spawn = SFX_SPAWN
self.sfx_slash = SFX_SLASH_ENEMY
self.sfx_lose = SFX_LOSE
def new_cell(self, x, y):
"""Draw on the map a newly created cell
whose coordinates are given.
"""
def draw_bit(bit, dx=0, dy=0):
startx, starty = x + CELL_NODES[dx], y + CELL_NODES[dy]
height = ROAD_WIDTH if dy else WALL_WIDTH
for i in range(ROAD_WIDTH if dx else WALL_WIDTH):
for j in range(height): self.map[startx + i][starty + j] = bit
x, y = x * CELL_WIDTH, y * CELL_WIDTH
draw_bit(WALL)
walls = set(sample(ADJACENTS, 2))
walls.add(choice(ADJACENTS))
for i, j in ADJACENTS:
draw_bit((WALL if (i, j) in walls else EMPTY), i, j)
def isdisplayed(self, x, y):
"""Return True if the grid (x, y) is in the displayable part
of the map, False otherwise.
"""
return (self.rangex[0] <= x <= self.rangex[-1]
and self.rangey[0] <= y <= self.rangey[-1])
def new_map(self):
"""Generate a new map."""
self.map = deque(deque(EMPTY for _ in range(MAZE_SIZE * CELL_WIDTH))
for _ in range(MAZE_SIZE * CELL_WIDTH))
for x in range(MAZE_SIZE):
for y in range(MAZE_SIZE): self.new_cell(x, y)
# Regenerate if the hero is trapped. This can only reach
# maximum recursion depth is there's a flaw with the system's entropy.
room, visited = [(MIDDLE, MIDDLE)], set()
while room:
bit = room.pop()
if bit not in visited:
if not self.isdisplayed(*bit): break
visited.add(bit)
for x, y in around(*bit):
if self.map[x][y] == EMPTY: room.append((x, y))
else:
self.new_map()
self.map[MIDDLE][MIDDLE] = HERO
self.destx = self.desty = MIDDLE
self.stepx = self.stepy = 0
def add_enemy(self):
"""Add enough enemies."""
2019-07-24 07:45:25 +02:00
self.enemies = [e for e in self.enemies if e.alive]
2017-11-21 12:01:32 +01:00
walls = [(i, j) for i in self.rangex for j in self.rangey
if self.map[i][j] == WALL]
plums = [e for e in self.enemies if e.color == 'Plum' and e.awake]
plum = choice(plums) if plums else None
2018-01-21 14:38:51 +01:00
num = log(self.score, INIT_SCORE)
while walls and len(self.enemies) < num:
x, y = choice(walls)
2018-05-20 15:33:50 +02:00
if all(self.map[x + a][y + b] == WALL for a, b in ADJACENTS):
continue
2017-11-19 09:00:24 +01:00
enemy = new_enemy(self, x, y)
self.enemies.append(enemy)
if plum is None or not plum.clone(enemy): walls.remove((x, y))
2017-10-12 15:29:55 +02:00
2017-11-20 16:29:56 +01:00
def get_pos(self, x, y):
2017-11-02 15:39:06 +01:00
"""Return coordinate of the center of the grid (x, y)."""
2017-11-04 15:43:05 +01:00
return (self.centerx + (x - MIDDLE)*self.distance,
self.centery + (y - MIDDLE)*self.distance)
2017-11-02 15:39:06 +01:00
def get_grid(self, x, y):
"""Return the grid containing the point (x, y)."""
2020-01-21 09:18:19 +01:00
return (MIDDLE + round((x-self.centerx) / self.distance),
MIDDLE + round((y-self.centery) / self.distance))
2018-10-07 16:59:39 +02:00
def get_target(self, x, y):
"""Return shooting target the grid containing the point (x, y).
If the grid is the hero, return a retired target.
"""
gridx, gridy = self.get_grid(x, y)
if gridx == gridy == MIDDLE: return LockOn(gridx, gridy, True)
for enemy in self.enemies:
if not enemy.isunnoticeable(gridx, gridy): return enemy
return LockOn(gridx, gridy)
def get_score(self):
"""Return the current score."""
return int(self.score - INIT_SCORE)
def get_color(self):
"""Return color of a grid."""
2018-05-22 15:44:22 +02:00
return choice(TANGO_VALUES)[0] if self.glitch > 0 else FG_COLOR
2017-10-12 15:29:55 +02:00
def draw(self):
"""Draw the maze."""
self.surface.fill(BG_COLOR)
2018-03-06 15:01:27 +01:00
if self.next_move <= 0:
for i in self.rangex:
for j in self.rangey:
if self.map[i][j] != WALL: continue
x, y = self.get_pos(i, j)
square = regpoly(4, self.distance / SQRT2, pi / 4, x, y)
fill_aapolygon(self.surface, square, self.get_color())
for enemy in self.enemies: enemy.draw()
2018-02-26 15:02:11 +01:00
if not self.hero.dead: self.hero.draw()
bullet_radius = self.distance / 4
for bullet in self.bullets: bullet.draw(bullet_radius)
pygame.display.flip()
pygame.display.set_caption(
'Brutal Maze - Score: {}'.format(self.get_score()))
2017-10-12 15:29:55 +02:00
2017-11-02 15:39:06 +01:00
def rotate(self):
"""Rotate the maze if needed."""
2017-11-04 15:43:05 +01:00
x = int((self.centerx-self.x) * 2 / self.distance)
y = int((self.centery-self.y) * 2 / self.distance)
2017-11-02 15:39:06 +01:00
if x == y == 0: return
for enemy in self.enemies:
if self.map[enemy.x][enemy.y] == ENEMY:
self.map[enemy.x][enemy.y] = EMPTY
2018-05-20 15:33:50 +02:00
2017-11-02 15:39:06 +01:00
self.map[MIDDLE][MIDDLE] = EMPTY
2018-05-20 15:33:50 +02:00
self.centerx -= x * self.distance
self.map.rotate(x)
self.rotatex += x
self.centery -= y * self.distance
for d in self.map: d.rotate(y)
self.rotatey += y
2017-11-02 15:39:06 +01:00
self.map[MIDDLE][MIDDLE] = HERO
2018-05-20 15:33:50 +02:00
if self.map[self.destx][self.desty] != HERO:
self.destx += x
self.desty += y
self.stepx = self.stepy = 0
# Respawn the enemies that fall off the display
for i, enemy in enumerate(self.enemies):
enemy.place(x, y)
2018-10-07 16:59:39 +02:00
if not self.isdisplayed(enemy.x, enemy.y):
self.score += enemy.wound
enemy.die()
self.add_enemy()
2018-10-07 16:59:39 +02:00
# LockOn target is not yet updated.
if isinstance(self.target, LockOn):
self.target.place(x, y, self.isdisplayed)
# Regenerate the maze
if abs(self.rotatex) == CELL_WIDTH:
self.rotatex = 0
for i in range(CELL_WIDTH): self.map[i].rotate(-self.rotatey)
for i in range(MAZE_SIZE): self.new_cell(0, i)
for i in range(CELL_WIDTH): self.map[i].rotate(self.rotatey)
if abs(self.rotatey) == CELL_WIDTH:
self.rotatey = 0
self.map.rotate(-self.rotatex)
for i in range(MAZE_SIZE): self.new_cell(i, 0)
self.map.rotate(self.rotatex)
2017-11-20 16:29:56 +01:00
def get_distance(self, x, y):
"""Return the distance from the center of the maze to the point
(x, y).
2017-11-02 15:39:06 +01:00
"""
return ((self.x-x)**2 + (self.y-y)**2)**0.5
2018-01-21 14:38:51 +01:00
def hit_hero(self, wound, color):
2017-11-12 15:08:14 +01:00
"""Handle the hero when he loses HP."""
if color == 'Orange':
2018-07-01 16:53:14 +02:00
# If called by close-range attack, this is FPS-dependant, although
# in playable FPS (24 to infinity), the difference within 2%.
self.hero.next_heal = abs(self.hero.next_heal * (1 - wound))
elif choice(ENEMIES) == color:
self.hero.next_heal = -1.0 # what doesn't kill you heals you
if color == 'Butter' or color == 'ScarletRed':
wound *= ENEMY_HP
2018-07-01 16:53:14 +02:00
elif color == 'Chocolate':
self.hero.highness += wound
wound = 0
elif color == 'SkyBlue':
self.next_move = max(self.next_move, 0) + wound*1000
wound = 0
if wound and sum(self.hero.wounds) < MAX_WOUND:
self.hero.wounds[-1] += wound
2017-11-12 15:08:14 +01:00
2017-10-15 11:20:14 +02:00
def slash(self):
2017-11-04 15:43:05 +01:00
"""Handle close-range attacks."""
2017-11-14 02:51:18 +01:00
for enemy in self.enemies: enemy.slash()
2017-10-21 16:22:06 +02:00
if not self.hero.spin_queue: return
2019-07-24 07:45:25 +02:00
for enemy in filter(lambda e: e.awake, self.enemies):
2018-10-09 16:20:38 +02:00
d = self.slashd - enemy.distance
2018-01-21 14:38:51 +01:00
if d > 0:
2018-03-06 15:01:27 +01:00
wound = d * SQRT2 / self.distance
if self.next_slashfx <= 0:
play(SFX_SLASH_ENEMY, enemy.x, enemy.y, wound)
2018-03-06 15:01:27 +01:00
self.next_slashfx = ATTACK_SPEED
2018-01-21 14:38:51 +01:00
enemy.hit(wound / self.hero.spin_speed)
if enemy.wound >= ENEMY_HP:
self.score += enemy.wound
2017-10-15 11:20:14 +02:00
enemy.die()
self.add_enemy()
2017-10-15 11:20:14 +02:00
def track_bullets(self):
"""Handle the bullets."""
2018-10-09 16:20:38 +02:00
self.bullets.extend(self.hero.shots)
fallen = []
block = (self.hero.spin_queue and self.hero.next_heal < 0
and self.hero.next_strike > self.hero.spin_queue / self.fps)
for i, bullet in enumerate(self.bullets):
2018-03-06 15:01:27 +01:00
wound = bullet.fall_time / BULLET_LIFETIME
bullet.update(self.fps, self.distance)
gridx, gridy = self.get_grid(bullet.x, bullet.y)
2018-10-07 16:59:39 +02:00
if wound <= 0 or not self.isdisplayed(gridx, gridy):
fallen.append(i)
2017-11-12 15:08:14 +01:00
elif bullet.color == 'Aluminium':
active_enemies = [e for e in self.enemies if e.awake]
if self.map[gridx][gridy] == WALL and self.next_move <= 0:
fallen.append(i)
if not active_enemies: continue
self.glitch = wound * 1000
enemy = new_enemy(self, gridx, gridy)
enemy.awake = True
self.map[gridx][gridy] = ENEMY
play(SFX_SPAWN, enemy.x, enemy.y)
enemy.hit(wound)
self.enemies.append(enemy)
continue
2019-07-24 07:45:25 +02:00
for enemy in active_enemies:
2018-10-09 16:20:38 +02:00
if bullet.get_distance(*enemy.pos) < self.distance:
2017-11-02 15:39:06 +01:00
enemy.hit(wound)
if enemy.wound >= ENEMY_HP:
self.score += enemy.wound
enemy.die()
2019-07-24 07:45:25 +02:00
self.add_enemy()
play(bullet.sfx_hit, gridx, gridy, wound)
fallen.append(i)
break
2017-11-20 16:29:56 +01:00
elif bullet.get_distance(self.x, self.y) < self.distance:
if block:
self.hero.next_strike = (abs(self.hero.spin_queue/self.fps)
2018-03-06 15:01:27 +01:00
+ ATTACK_SPEED)
play(SFX_MISSED, gain=wound)
2018-01-21 14:38:51 +01:00
else:
self.hit_hero(wound, bullet.color)
play(bullet.sfx_hit, gain=wound)
fallen.append(i)
for i in reversed(fallen): self.bullets.pop(i)
2017-11-21 15:49:35 +01:00
def is_valid_move(self, vx=0.0, vy=0.0):
"""Return dx or dy if it it valid to move the maze in that
velocity, otherwise return 0.0.
2017-11-02 15:39:06 +01:00
"""
d = self.distance/2 + self.hero.R
herox, heroy, dx, dy = self.x - vx, self.y - vy, sign(vx), sign(vy)
2017-11-09 09:22:39 +01:00
for gridx in range(MIDDLE - dx - 1, MIDDLE - dx + 2):
for gridy in range(MIDDLE - dy - 1, MIDDLE - dy + 2):
2017-11-20 16:29:56 +01:00
x, y = self.get_pos(gridx, gridy)
2017-11-09 09:22:39 +01:00
if (max(abs(herox - x), abs(heroy - y)) < d
and self.map[gridx][gridy] == WALL):
return 0.0
2017-11-09 09:22:39 +01:00
for enemy in self.enemies:
2017-11-20 16:29:56 +01:00
x, y = self.get_pos(enemy.x, enemy.y)
2018-05-20 15:33:50 +02:00
if max(abs(herox - x), abs(heroy - y)) * 2 < self.distance:
2017-11-09 09:22:39 +01:00
return 0.0
return vx or vy
2017-11-02 15:39:06 +01:00
def expos(self, x, y):
"""Return position of the given coordinates in rounded percent."""
cx = len(self.rangex)*50 + (x - self.centerx)/self.distance*100
cy = len(self.rangey)*50 + (y - self.centery)/self.distance*100
2020-01-21 09:18:19 +01:00
return round(cx), round(cy)
def update_export(self, forced=False):
"""Update the maze's data export and return the last record."""
if self.next_export > 0 and not forced or self.hero.dead: return
export = defaultdict(list)
export['s'] = self.get_score()
if self.next_move <= 0:
for y in self.rangey:
export['m'].append(''.join(
COLORS[self.get_color()] if self.map[x][y] == WALL else '0'
for x in self.rangex))
x, y = self.expos(self.x, self.y)
export['h'] = [
COLORS[self.hero.get_color()], x, y, deg(self.hero.angle),
int(self.hero.next_strike <= 0), int(self.hero.next_heal <= 0)]
for enemy in self.enemies:
if enemy.isunnoticeable(): continue
2018-10-09 16:20:38 +02:00
x, y = self.expos(*enemy.pos)
color, angle = COLORS[enemy.get_color()], deg(enemy.angle)
export['e'].append([color, x, y, angle])
for bullet in self.bullets:
x, y = self.expos(bullet.x, bullet.y)
color, angle = COLORS[bullet.get_color()], deg(bullet.angle)
if color != '0': export['b'].append([color, x, y, angle])
if self.next_export <= 0:
2020-01-21 09:18:19 +01:00
export['t'] = round(self.export_rate - self.next_export)
self.export.append(export)
self.next_export = self.export_rate
return export
2017-10-19 10:24:56 +02:00
def update(self, fps):
2017-10-12 15:29:55 +02:00
"""Update the maze."""
self.fps = fps
2018-05-20 15:33:50 +02:00
self.vx = self.is_valid_move(vx=self.vx)
self.centerx += self.vx
self.vy = self.is_valid_move(vy=self.vy)
self.centery += self.vy
2017-10-12 15:29:55 +02:00
2020-01-21 09:18:19 +01:00
self.next_move -= 1000 / fps
self.glitch -= 1000 / fps
self.next_slashfx -= 1000 / fps
self.next_export -= 1000 / fps
2018-03-06 15:01:27 +01:00
self.rotate()
if self.vx or self.vy or self.hero.firing or self.hero.slashing:
2017-11-04 15:43:05 +01:00
for enemy in self.enemies: enemy.wake()
2018-05-20 15:33:50 +02:00
for bullet in self.bullets: bullet.place(self.vx, self.vy)
2017-10-12 15:29:55 +02:00
for enemy in self.enemies: enemy.update()
self.track_bullets()
2018-01-26 14:26:14 +01:00
if not self.hero.dead:
self.hero.update(fps)
self.slash()
if self.hero.wound >= HERO_HP: self.lose()
self.update_export()
2017-10-13 10:10:16 +02:00
def resize(self, size):
2017-10-13 10:10:16 +02:00
"""Resize the maze."""
2018-02-02 17:33:22 +01:00
self.w, self.h = size
2018-03-07 10:13:34 +01:00
self.surface = pygame.display.set_mode(size, pygame.RESIZABLE)
self.hero.resize(size)
2017-10-13 10:10:16 +02:00
2017-11-04 15:43:05 +01:00
offsetx = (self.centerx-self.x) / self.distance
offsety = (self.centery-self.y) / self.distance
2018-02-08 14:41:08 +01:00
self.distance = (self.w * self.h / 416) ** 0.5
self.x, self.y = self.w // 2, self.h // 2
2017-11-04 15:43:05 +01:00
self.centerx = self.x + offsetx*self.distance
self.centery = self.y + offsety*self.distance
2018-03-06 03:58:52 +01:00
w, h = int(self.w/self.distance/2 + 1), int(self.h/self.distance/2 + 1)
self.rangex = list(range(MIDDLE - w, MIDDLE + w + 1))
self.rangey = list(range(MIDDLE - h, MIDDLE + h + 1))
2017-10-19 15:28:56 +02:00
self.slashd = self.hero.R + self.distance/SQRT2
2017-10-13 10:10:16 +02:00
def set_step(self, check=(lambda x, y: True)):
2018-10-07 16:59:39 +02:00
"""Work out next step on the shortest path to the destination.
Return whether target is impossible to reach and hero should
shoot toward it instead.
"""
2018-05-20 15:33:50 +02:00
if self.stepx or self.stepy and self.vx == self.vy == 0.0:
x, y = MIDDLE - self.stepx, MIDDLE - self.stepy
if self.stepx and not self.stepy:
nextx = x - self.stepx
n = self.map[x][y - 1] == EMPTY == self.map[nextx][y - 1]
s = self.map[x][y + 1] == EMPTY == self.map[nextx][y + 1]
self.stepy = n - s
elif not self.stepx and self.stepy:
nexty = y - self.stepy
w = self.map[x - 1][y] == EMPTY == self.map[x - 1][nexty]
e = self.map[x + 1][y] == EMPTY == self.map[x + 1][nexty]
self.stepx = w - e
2018-10-07 16:59:39 +02:00
return False
# Shoot WALL and ENEMY instead
if self.map[self.destx][self.desty] != EMPTY:
self.stepx = self.stepy = 0
return True
2018-05-20 15:33:50 +02:00
2019-10-09 12:20:57 +02:00
# Forest Fire algorithm
queue, visited = deque([(self.destx, self.desty)]), set()
while queue:
x, y = queue.pop()
if (x, y) in visited: continue
visited.add((x, y))
2018-05-20 15:33:50 +02:00
dx, dy = MIDDLE - x, MIDDLE - y
if dx**2 + dy**2 <= 2:
2019-10-09 12:20:57 +02:00
# Succeeded on finding a path
2018-05-20 15:33:50 +02:00
self.stepx, self.stepy = dx, dy
2018-10-07 16:59:39 +02:00
return False
2018-05-20 15:33:50 +02:00
for i, j in around(x, y):
if self.map[i][j] == EMPTY and check(i, j):
2019-10-09 12:20:57 +02:00
queue.appendleft((i, j))
2018-10-07 16:59:39 +02:00
# Failed to find way to move to target
self.stepx = self.stepy = 0
return True
2018-05-20 15:33:50 +02:00
2017-11-09 09:22:39 +01:00
def isfast(self):
"""Return if the hero is moving faster than HERO_SPEED."""
return (self.vx**2+self.vy**2)**0.5*self.fps > HERO_SPEED*self.distance
def dump_records(self):
"""Dump JSON records."""
if self.export_dir:
with open(json_rec(self.export_dir), 'w') as f:
json.dump(self.export, f, separators=JSON_SEPARATORS)
2017-10-15 11:20:14 +02:00
def lose(self):
"""Handle loses."""
2017-12-17 11:05:17 +01:00
self.hero.dead = True
self.hero.wound = HERO_HP
2017-12-17 11:05:17 +01:00
self.hero.slashing = self.hero.firing = False
2018-05-20 15:33:50 +02:00
self.destx = self.desty = MIDDLE
self.stepx = self.stepy = 0
self.vx = self.vy = 0.0
play(SFX_LOSE)
self.dump_records()
def reinit(self):
"""Open new game."""
2020-01-21 09:18:19 +01:00
self.centerx, self.centery = self.w / 2, self.h / 2
self.score, self.export = INIT_SCORE, []
self.new_map()
self.vx = self.vy = 0.0
self.rotatex = self.rotatey = 0
self.bullets, self.enemies = [], []
self.add_enemy()
self.next_move = self.next_slashfx = self.hero.next_strike = 0.0
2018-10-07 16:59:39 +02:00
self.target = LockOn(MIDDLE, MIDDLE, retired=True)
self.hero.next_heal = -1.0
2018-07-01 16:53:14 +02:00
self.hero.highness = 0.0
self.hero.slashing = self.hero.firing = self.hero.dead = False
self.hero.spin_queue = self.hero.wound = 0.0
self.hero.wounds = deque([0.0])