Implement touch-friendly control
This commit is contained in:
parent
865a3e3b71
commit
377dda3db0
|
@ -45,9 +45,9 @@ class Hero:
|
||||||
next_beat (float): time until next heart beat (in ms)
|
next_beat (float): time until next heart beat (in ms)
|
||||||
next_strike (float): time until the hero can do the next attack (in ms)
|
next_strike (float): time until the hero can do the next attack (in ms)
|
||||||
highness (float): likelihood that the hero shoots toward other angles
|
highness (float): likelihood that the hero shoots toward other angles
|
||||||
slashing (bool): flag indicates if the hero is doing close-range attack
|
slashing (bool): flag indicating if the hero is doing close-range attack
|
||||||
firing (bool): flag indicates if the hero is doing long-range attack
|
firing (bool): flag indicating if the hero is doing long-range attack
|
||||||
dead (bool): flag indicates if the hero is dead
|
dead (bool): flag indicating if the hero is dead
|
||||||
spin_speed (float): speed of spinning (in frames per slash)
|
spin_speed (float): speed of spinning (in frames per slash)
|
||||||
spin_queue (float): frames left to finish spinning
|
spin_queue (float): frames left to finish spinning
|
||||||
wound (float): amount of wound
|
wound (float): amount of wound
|
||||||
|
@ -159,7 +159,7 @@ class Enemy:
|
||||||
x, y (int): coordinates of the center of the enemy (in grids)
|
x, y (int): coordinates of the center of the enemy (in grids)
|
||||||
angle (float): angle of the direction the enemy pointing (in radians)
|
angle (float): angle of the direction the enemy pointing (in radians)
|
||||||
color (str): enemy's color name
|
color (str): enemy's color name
|
||||||
awake (bool): flag indicates if the enemy is active
|
awake (bool): flag indicating if the enemy is active
|
||||||
next_strike (float): time until the enemy's next action (in ms)
|
next_strike (float): time until the enemy's next action (in ms)
|
||||||
move_speed (float): speed of movement (in frames per grid)
|
move_speed (float): speed of movement (in frames per grid)
|
||||||
offsetx, offsety (integer): steps moved from the center of the grid
|
offsetx, offsety (integer): steps moved from the center of the grid
|
||||||
|
@ -290,8 +290,14 @@ class Enemy:
|
||||||
"""Return current color of the enemy."""
|
"""Return current color of the enemy."""
|
||||||
return TANGO[self.color][int(self.wound)]
|
return TANGO[self.color][int(self.wound)]
|
||||||
|
|
||||||
def isunnoticeable(self):
|
def isunnoticeable(self, x=None, y=None):
|
||||||
"""Return whether the enemy can be noticed."""
|
"""Return whether the enemy can be noticed.
|
||||||
|
|
||||||
|
Only search within column x and row y if these coordinates
|
||||||
|
are provided.
|
||||||
|
"""
|
||||||
|
if x is not None and self.x != x: return True
|
||||||
|
if y is not None and self.y != y: return True
|
||||||
return not self.awake or self.wound >= ENEMY_HP
|
return not self.awake or self.wound >= ENEMY_HP
|
||||||
|
|
||||||
def draw(self):
|
def draw(self):
|
||||||
|
@ -321,6 +327,18 @@ class Enemy:
|
||||||
"""Handle the enemy when it's attacked."""
|
"""Handle the enemy when it's attacked."""
|
||||||
self.wound += wound
|
self.wound += wound
|
||||||
|
|
||||||
|
@property
|
||||||
|
def retired(self):
|
||||||
|
"""Provide compatibility with LockOn object."""
|
||||||
|
try:
|
||||||
|
return self._retired
|
||||||
|
except AttributeError:
|
||||||
|
return self.wound >= ENEMY_HP
|
||||||
|
|
||||||
|
@retired.setter
|
||||||
|
def retired(self, value):
|
||||||
|
self._retired = value
|
||||||
|
|
||||||
def die(self):
|
def die(self):
|
||||||
"""Handle the enemy's death."""
|
"""Handle the enemy's death."""
|
||||||
if self.awake:
|
if self.awake:
|
||||||
|
@ -346,9 +364,13 @@ class Chameleon(Enemy):
|
||||||
if Enemy.wake(self) is True:
|
if Enemy.wake(self) is True:
|
||||||
self.visible = 1000.0 / ENEMY_SPEED
|
self.visible = 1000.0 / ENEMY_SPEED
|
||||||
|
|
||||||
def isunnoticeable(self):
|
def isunnoticeable(self, x=None, y=None):
|
||||||
"""Return whether the enemy can be noticed."""
|
"""Return whether the enemy can be noticed.
|
||||||
return (Enemy.isunnoticeable(self)
|
|
||||||
|
Only search within column x and row y if these coordinates
|
||||||
|
are provided.
|
||||||
|
"""
|
||||||
|
return (Enemy.isunnoticeable(self, x, y)
|
||||||
or self.visible <= 0 and not self.spin_queue
|
or self.visible <= 0 and not self.spin_queue
|
||||||
and self.maze.next_move <= 0)
|
and self.maze.next_move <= 0)
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with Brutal Maze. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
__version__ = '0.8.21'
|
__version__ = '0.8.22'
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from argparse import ArgumentParser, FileType, RawTextHelpFormatter
|
from argparse import ArgumentParser, FileType, RawTextHelpFormatter
|
||||||
|
@ -32,7 +32,7 @@ from sys import stdout
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
from pygame import KEYDOWN, QUIT, VIDEORESIZE
|
from pygame import KEYDOWN, MOUSEBUTTONUP, QUIT, VIDEORESIZE
|
||||||
from pygame.time import Clock, get_ticks
|
from pygame.time import Clock, get_ticks
|
||||||
from appdirs import AppDirs
|
from appdirs import AppDirs
|
||||||
|
|
||||||
|
@ -49,7 +49,6 @@ class ConfigReader:
|
||||||
('Toggle mute', 'mute'),
|
('Toggle mute', 'mute'),
|
||||||
('Move left', 'left'), ('Move right', 'right'),
|
('Move left', 'left'), ('Move right', 'right'),
|
||||||
('Move up', 'up'), ('Move down', 'down'),
|
('Move up', 'up'), ('Move down', 'down'),
|
||||||
('Auto move', 'autove'),
|
|
||||||
('Long-range attack', 'shot'),
|
('Long-range attack', 'shot'),
|
||||||
('Close-range attack', 'slash'))
|
('Close-range attack', 'slash'))
|
||||||
WEIRD_MOUSE_ERR = '{}: Mouse is not a suitable control'
|
WEIRD_MOUSE_ERR = '{}: Mouse is not a suitable control'
|
||||||
|
@ -71,6 +70,7 @@ class ConfigReader:
|
||||||
self.muted = self.config.getboolean('Sound', 'Muted')
|
self.muted = self.config.getboolean('Sound', 'Muted')
|
||||||
self.musicvol = self.config.getfloat('Sound', 'Music volume')
|
self.musicvol = self.config.getfloat('Sound', 'Music volume')
|
||||||
self.space = self.config.getboolean('Sound', 'Space theme')
|
self.space = self.config.getboolean('Sound', 'Space theme')
|
||||||
|
self.touch = self.config.getboolean('Control', 'Touch')
|
||||||
self.export_dir = self.config.get('Record', 'Directory')
|
self.export_dir = self.config.get('Record', 'Directory')
|
||||||
self.export_rate = self.config.getint('Record', 'Frequency')
|
self.export_rate = self.config.getint('Record', 'Frequency')
|
||||||
self.server = self.config.getboolean('Server', 'Enable')
|
self.server = self.config.getboolean('Server', 'Enable')
|
||||||
|
@ -84,7 +84,7 @@ class ConfigReader:
|
||||||
for cmd, alias in self.CONTROL_ALIASES:
|
for cmd, alias in self.CONTROL_ALIASES:
|
||||||
i = self.config.get('Control', cmd)
|
i = self.config.get('Control', cmd)
|
||||||
if re.match('mouse[1-3]$', i.lower()):
|
if re.match('mouse[1-3]$', i.lower()):
|
||||||
if alias not in ('autove', 'shot', 'slash'):
|
if alias not in ('shot', 'slash'):
|
||||||
raise ValueError(self.WEIRD_MOUSE_ERR.format(cmd))
|
raise ValueError(self.WEIRD_MOUSE_ERR.format(cmd))
|
||||||
self.mouse[alias] = int(i[-1]) - 1
|
self.mouse[alias] = int(i[-1]) - 1
|
||||||
continue
|
continue
|
||||||
|
@ -98,9 +98,9 @@ class ConfigReader:
|
||||||
|
|
||||||
def read_args(self, arguments):
|
def read_args(self, arguments):
|
||||||
"""Read and parse a ArgumentParser.Namespace."""
|
"""Read and parse a ArgumentParser.Namespace."""
|
||||||
for option in (
|
for option in ('size', 'max_fps', 'muted', 'musicvol', 'space',
|
||||||
'size', 'max_fps', 'muted', 'musicvol', 'space', 'export_dir',
|
'touch', 'export_dir', 'export_rate', 'server',
|
||||||
'export_rate', 'server', 'host', 'port', 'timeout', 'headless'):
|
'host', 'port', 'timeout', 'headless'):
|
||||||
value = getattr(arguments, option)
|
value = getattr(arguments, option)
|
||||||
if value is not None: setattr(self, option, value)
|
if value is not None: setattr(self, option, value)
|
||||||
|
|
||||||
|
@ -134,6 +134,7 @@ class Game:
|
||||||
# self.fps is a float to make sure floordiv won't be used in Python 2
|
# self.fps is a float to make sure floordiv won't be used in Python 2
|
||||||
self.max_fps, self.fps = config.max_fps, float(config.max_fps)
|
self.max_fps, self.fps = config.max_fps, float(config.max_fps)
|
||||||
self.musicvol = config.musicvol
|
self.musicvol = config.musicvol
|
||||||
|
self.touch = config.touch
|
||||||
self.key, self.mouse = config.key, config.mouse
|
self.key, self.mouse = config.key, config.mouse
|
||||||
self.maze = Maze(config.max_fps, config.size, config.headless,
|
self.maze = Maze(config.max_fps, config.size, config.headless,
|
||||||
config.export_dir, 1000.0 / config.export_rate)
|
config.export_dir, 1000.0 / config.export_rate)
|
||||||
|
@ -175,6 +176,19 @@ class Game:
|
||||||
self.maze.reinit()
|
self.maze.reinit()
|
||||||
elif event.key == self.key['pause'] and not self.hero.dead:
|
elif event.key == self.key['pause'] and not self.hero.dead:
|
||||||
self.paused ^= True
|
self.paused ^= True
|
||||||
|
elif event.type == MOUSEBUTTONUP and self.touch:
|
||||||
|
# We're careless about which mouse button is clicked.
|
||||||
|
maze = self.maze
|
||||||
|
if self.hero.dead:
|
||||||
|
maze.reinit()
|
||||||
|
else:
|
||||||
|
x, y = pygame.mouse.get_pos()
|
||||||
|
maze.destx, maze.desty = maze.get_grid(x, y)
|
||||||
|
if maze.set_step(maze.isdisplayed):
|
||||||
|
maze.target = maze.get_target(x, y)
|
||||||
|
self.hero.firing = not maze.target.retired
|
||||||
|
if maze.stepx == maze.stepy == 0:
|
||||||
|
maze.destx = maze.desty = MIDDLE
|
||||||
|
|
||||||
# Compare current FPS with the average of the last 10 frames
|
# Compare current FPS with the average of the last 10 frames
|
||||||
new_fps = self.clock.get_fps()
|
new_fps = self.clock.get_fps()
|
||||||
|
@ -187,7 +201,7 @@ class Game:
|
||||||
self.clock.tick(self.fps)
|
self.clock.tick(self.fps)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def move(self, x, y):
|
def move(self, x=0, y=0):
|
||||||
"""Command the hero to move faster in the given direction."""
|
"""Command the hero to move faster in the given direction."""
|
||||||
maze = self.maze
|
maze = self.maze
|
||||||
velocity = maze.distance * HERO_SPEED / self.fps
|
velocity = maze.distance * HERO_SPEED / self.fps
|
||||||
|
@ -265,41 +279,37 @@ class Game:
|
||||||
connection.close()
|
connection.close()
|
||||||
if not self.hero.dead: self.maze.lose()
|
if not self.hero.dead: self.maze.lose()
|
||||||
|
|
||||||
|
def touch_control(self):
|
||||||
|
"""Handle touch control."""
|
||||||
|
maze, hero = self.maze, self.hero
|
||||||
|
if maze.target.retired: hero.firing = False
|
||||||
|
if hero.firing:
|
||||||
|
x, y = maze.get_pos(maze.target.x, maze.target.y)
|
||||||
|
else:
|
||||||
|
x, y = pygame.mouse.get_pos()
|
||||||
|
hero.update_angle(atan2(y - hero.y, x - hero.x))
|
||||||
|
self.move()
|
||||||
|
|
||||||
def user_control(self):
|
def user_control(self):
|
||||||
"""Handle direct control from user's mouse and keyboard."""
|
"""Handle direct control from user's mouse and keyboard."""
|
||||||
if not self.hero.dead:
|
if self.hero.dead: return
|
||||||
keys = pygame.key.get_pressed()
|
keys = pygame.key.get_pressed()
|
||||||
right = keys[self.key['right']] - keys[self.key['left']]
|
buttons = pygame.mouse.get_pressed()
|
||||||
down = keys[self.key['down']] - keys[self.key['up']]
|
|
||||||
|
|
||||||
buttons = pygame.mouse.get_pressed()
|
right = keys[self.key['right']] - keys[self.key['left']]
|
||||||
try:
|
down = keys[self.key['down']] - keys[self.key['up']]
|
||||||
autove = keys[self.key['autove']]
|
x, y = pygame.mouse.get_pos()
|
||||||
except KeyError:
|
angle = atan2(y - self.hero.y, x - self.hero.x)
|
||||||
autove = buttons[self.mouse['autove']]
|
|
||||||
try:
|
|
||||||
firing = keys[self.key['shot']]
|
|
||||||
except KeyError:
|
|
||||||
firing = buttons[self.mouse['shot']]
|
|
||||||
try:
|
|
||||||
slashing = keys[self.key['slash']]
|
|
||||||
except KeyError:
|
|
||||||
slashing = buttons[self.mouse['slash']]
|
|
||||||
|
|
||||||
# Follow the mouse cursor
|
try:
|
||||||
x, y = pygame.mouse.get_pos()
|
firing = keys[self.key['shot']]
|
||||||
maze = self.maze
|
except KeyError:
|
||||||
if right or down:
|
firing = buttons[self.mouse['shot']]
|
||||||
maze.destx = maze.desty = MIDDLE
|
try:
|
||||||
maze.stepx = maze.stepy = 0
|
slashing = keys[self.key['slash']]
|
||||||
elif autove:
|
except KeyError:
|
||||||
maze.destx, maze.desty = maze.get_grid(x, y)
|
slashing = buttons[self.mouse['slash']]
|
||||||
maze.set_step(maze.is_displayed)
|
self.control(right, down, angle, firing, slashing)
|
||||||
if maze.stepx == maze.stepy == 0:
|
|
||||||
maze.destx = maze.desty = MIDDLE
|
|
||||||
|
|
||||||
angle = atan2(y - self.hero.y, x - self.hero.x)
|
|
||||||
self.control(right, down, angle, firing, slashing)
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback):
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
if self.server is not None: self.server.close()
|
if self.server is not None: self.server.close()
|
||||||
|
@ -349,6 +359,12 @@ def main():
|
||||||
help='use space music background (fallback: {})'.format(config.space))
|
help='use space music background (fallback: {})'.format(config.space))
|
||||||
parser.add_argument('--default-music', action='store_false', dest='space',
|
parser.add_argument('--default-music', action='store_false', dest='space',
|
||||||
help='use default music background')
|
help='use default music background')
|
||||||
|
parser.add_argument(
|
||||||
|
'--touch', action='store_true', default=None,
|
||||||
|
help='enable touch-friendly control (fallback: {})'.format(
|
||||||
|
config.touch))
|
||||||
|
parser.add_argument('--no-touch', action='store_false', dest='touch',
|
||||||
|
help='disable touch-friendly control')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--record-dir', metavar='DIR', dest='export_dir',
|
'--record-dir', metavar='DIR', dest='export_dir',
|
||||||
help='directory to write game records (fallback: {})'.format(
|
help='directory to write game records (fallback: {})'.format(
|
||||||
|
@ -397,5 +413,7 @@ def main():
|
||||||
socket_thread.daemon = True # make it disposable
|
socket_thread.daemon = True # make it disposable
|
||||||
socket_thread.start()
|
socket_thread.start()
|
||||||
while game.update(): game.control(*game.sockinp)
|
while game.update(): game.control(*game.sockinp)
|
||||||
|
elif config.touch:
|
||||||
|
while game.update(): game.touch_control()
|
||||||
else:
|
else:
|
||||||
while game.update(): game.user_control()
|
while game.update(): game.user_control()
|
||||||
|
|
|
@ -36,6 +36,7 @@ from .constants import (
|
||||||
BULLET_LIFETIME, JSON_SEPARATORS)
|
BULLET_LIFETIME, JSON_SEPARATORS)
|
||||||
from .misc import (
|
from .misc import (
|
||||||
round2, sign, deg, around, regpoly, fill_aapolygon, play, json_rec)
|
round2, sign, deg, around, regpoly, fill_aapolygon, play, json_rec)
|
||||||
|
from .weapons import LockOn
|
||||||
|
|
||||||
|
|
||||||
class Maze:
|
class Maze:
|
||||||
|
@ -59,6 +60,7 @@ class Maze:
|
||||||
hero (Hero): the hero
|
hero (Hero): the hero
|
||||||
destx, desty (int): the grid the hero is moving to
|
destx, desty (int): the grid the hero is moving to
|
||||||
stepx, stepy (int): direction the maze is moving
|
stepx, stepy (int): direction the maze is moving
|
||||||
|
target (Enemy or LockOn): target to automatically aim at
|
||||||
next_move (float): time until the hero gets mobilized (in ms)
|
next_move (float): time until the hero gets mobilized (in ms)
|
||||||
glitch (float): time that the maze remain flashing colors (in ms)
|
glitch (float): time that the maze remain flashing colors (in ms)
|
||||||
next_slashfx (float): time until next slash effect of the hero (in ms)
|
next_slashfx (float): time until next slash effect of the hero (in ms)
|
||||||
|
@ -102,6 +104,7 @@ class Maze:
|
||||||
self.map[MIDDLE][MIDDLE] = HERO
|
self.map[MIDDLE][MIDDLE] = HERO
|
||||||
self.destx = self.desty = MIDDLE
|
self.destx = self.desty = MIDDLE
|
||||||
self.stepx = self.stepy = 0
|
self.stepx = self.stepy = 0
|
||||||
|
self.target = LockOn(MIDDLE, MIDDLE, retired=True)
|
||||||
self.next_move = self.glitch = self.next_slashfx = 0.0
|
self.next_move = self.glitch = self.next_slashfx = 0.0
|
||||||
self.slashd = self.hero.R + self.distance/SQRT2
|
self.slashd = self.hero.R + self.distance/SQRT2
|
||||||
|
|
||||||
|
@ -152,6 +155,17 @@ class Maze:
|
||||||
return (MIDDLE + round2((x-self.centerx) / self.distance),
|
return (MIDDLE + round2((x-self.centerx) / self.distance),
|
||||||
MIDDLE + round2((y-self.centery) / self.distance))
|
MIDDLE + round2((y-self.centery) / self.distance))
|
||||||
|
|
||||||
|
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):
|
def get_score(self):
|
||||||
"""Return the current score."""
|
"""Return the current score."""
|
||||||
return int(self.score - INIT_SCORE)
|
return int(self.score - INIT_SCORE)
|
||||||
|
@ -179,7 +193,7 @@ class Maze:
|
||||||
pygame.display.set_caption(
|
pygame.display.set_caption(
|
||||||
'Brutal Maze - Score: {}'.format(self.get_score()))
|
'Brutal Maze - Score: {}'.format(self.get_score()))
|
||||||
|
|
||||||
def is_displayed(self, x, y):
|
def isdisplayed(self, x, y):
|
||||||
"""Return True if the grid (x, y) is in the displayable part
|
"""Return True if the grid (x, y) is in the displayable part
|
||||||
of the map, False otherwise.
|
of the map, False otherwise.
|
||||||
"""
|
"""
|
||||||
|
@ -212,13 +226,17 @@ class Maze:
|
||||||
killist = []
|
killist = []
|
||||||
for i, enemy in enumerate(self.enemies):
|
for i, enemy in enumerate(self.enemies):
|
||||||
enemy.place(x, y)
|
enemy.place(x, y)
|
||||||
if not self.is_displayed(enemy.x, enemy.y):
|
if not self.isdisplayed(enemy.x, enemy.y):
|
||||||
self.score += enemy.wound
|
self.score += enemy.wound
|
||||||
enemy.die()
|
enemy.die()
|
||||||
killist.append(i)
|
killist.append(i)
|
||||||
for i in reversed(killist): self.enemies.pop(i)
|
for i in reversed(killist): self.enemies.pop(i)
|
||||||
self.add_enemy()
|
self.add_enemy()
|
||||||
|
|
||||||
|
# LockOn target is not yet updated.
|
||||||
|
if isinstance(self.target, LockOn):
|
||||||
|
self.target.place(x, y, self.isdisplayed)
|
||||||
|
|
||||||
# Regenerate the maze
|
# Regenerate the maze
|
||||||
if abs(self.rotatex) == CELL_WIDTH:
|
if abs(self.rotatex) == CELL_WIDTH:
|
||||||
self.rotatex = 0
|
self.rotatex = 0
|
||||||
|
@ -290,7 +308,7 @@ class Maze:
|
||||||
wound = bullet.fall_time / BULLET_LIFETIME
|
wound = bullet.fall_time / BULLET_LIFETIME
|
||||||
bullet.update(self.fps, self.distance)
|
bullet.update(self.fps, self.distance)
|
||||||
gridx, gridy = self.get_grid(bullet.x, bullet.y)
|
gridx, gridy = self.get_grid(bullet.x, bullet.y)
|
||||||
if wound <= 0 or not self.is_displayed(gridx, gridy):
|
if wound <= 0 or not self.isdisplayed(gridx, gridy):
|
||||||
fallen.append(i)
|
fallen.append(i)
|
||||||
elif bullet.color == 'Aluminium':
|
elif bullet.color == 'Aluminium':
|
||||||
if self.map[gridx][gridy] == WALL and self.next_move <= 0:
|
if self.map[gridx][gridy] == WALL and self.next_move <= 0:
|
||||||
|
@ -430,7 +448,11 @@ class Maze:
|
||||||
self.slashd = self.hero.R + self.distance/SQRT2
|
self.slashd = self.hero.R + self.distance/SQRT2
|
||||||
|
|
||||||
def set_step(self, check=(lambda x, y: True)):
|
def set_step(self, check=(lambda x, y: True)):
|
||||||
"""Return direction on the shortest path to the destination."""
|
"""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.
|
||||||
|
"""
|
||||||
if self.stepx or self.stepy and self.vx == self.vy == 0.0:
|
if self.stepx or self.stepy and self.vx == self.vy == 0.0:
|
||||||
x, y = MIDDLE - self.stepx, MIDDLE - self.stepy
|
x, y = MIDDLE - self.stepx, MIDDLE - self.stepy
|
||||||
if self.stepx and not self.stepy:
|
if self.stepx and not self.stepy:
|
||||||
|
@ -443,7 +465,12 @@ class Maze:
|
||||||
w = self.map[x - 1][y] == EMPTY == self.map[x - 1][nexty]
|
w = self.map[x - 1][y] == EMPTY == self.map[x - 1][nexty]
|
||||||
e = 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
|
self.stepx = w - e
|
||||||
return
|
return False
|
||||||
|
|
||||||
|
# Shoot WALL and ENEMY instead
|
||||||
|
if self.map[self.destx][self.desty] != EMPTY:
|
||||||
|
self.stepx = self.stepy = 0
|
||||||
|
return True
|
||||||
|
|
||||||
# Forest Fire algorithm with step count
|
# Forest Fire algorithm with step count
|
||||||
queue = defaultdict(list, {0: [(self.destx, self.desty)]})
|
queue = defaultdict(list, {0: [(self.destx, self.desty)]})
|
||||||
|
@ -460,12 +487,15 @@ class Maze:
|
||||||
dx, dy = MIDDLE - x, MIDDLE - y
|
dx, dy = MIDDLE - x, MIDDLE - y
|
||||||
if dx**2 + dy**2 <= 2:
|
if dx**2 + dy**2 <= 2:
|
||||||
self.stepx, self.stepy = dx, dy
|
self.stepx, self.stepy = dx, dy
|
||||||
return
|
return False
|
||||||
for i, j in around(x, y):
|
for i, j in around(x, y):
|
||||||
if self.map[i][j] == EMPTY and check(i, j):
|
if self.map[i][j] == EMPTY and check(i, j):
|
||||||
queue[distance + 1].append((i, j))
|
queue[distance + 1].append((i, j))
|
||||||
count += 1
|
count += 1
|
||||||
self.stepx, self.stepy = 0, 0
|
|
||||||
|
# Failed to find way to move to target
|
||||||
|
self.stepx = self.stepy = 0
|
||||||
|
return True
|
||||||
|
|
||||||
def isfast(self):
|
def isfast(self):
|
||||||
"""Return if the hero is moving faster than HERO_SPEED."""
|
"""Return if the hero is moving faster than HERO_SPEED."""
|
||||||
|
@ -506,6 +536,7 @@ class Maze:
|
||||||
self.add_enemy()
|
self.add_enemy()
|
||||||
|
|
||||||
self.next_move = self.next_slashfx = self.hero.next_strike = 0.0
|
self.next_move = self.next_slashfx = self.hero.next_strike = 0.0
|
||||||
|
self.target = LockOn(MIDDLE, MIDDLE, retired=True)
|
||||||
self.hero.next_heal = -1.0
|
self.hero.next_heal = -1.0
|
||||||
self.hero.highness = 0.0
|
self.hero.highness = 0.0
|
||||||
self.hero.slashing = self.hero.firing = self.hero.dead = False
|
self.hero.slashing = self.hero.firing = self.hero.dead = False
|
||||||
|
|
|
@ -12,6 +12,8 @@ Music volume: 1.0
|
||||||
Space theme: no
|
Space theme: no
|
||||||
|
|
||||||
[Control]
|
[Control]
|
||||||
|
# Touch-friendly control
|
||||||
|
Touch: yes
|
||||||
# Input values should be either from Mouse1 to Mouse3 or a keyboard key
|
# Input values should be either from Mouse1 to Mouse3 or a keyboard key
|
||||||
# and they are case-insensitively read.
|
# and they are case-insensitively read.
|
||||||
# Aliases for special keys are listed here (without the K_ part):
|
# Aliases for special keys are listed here (without the K_ part):
|
||||||
|
@ -24,10 +26,8 @@ Move left: a
|
||||||
Move right: d
|
Move right: d
|
||||||
Move up: w
|
Move up: w
|
||||||
Move down: s
|
Move down: s
|
||||||
# Move hero using mouse
|
|
||||||
Auto move: Mouse3
|
|
||||||
Long-range attack: Mouse1
|
Long-range attack: Mouse1
|
||||||
Close-range attack: Space
|
Close-range attack: Mouse3
|
||||||
|
|
||||||
[Record]
|
[Record]
|
||||||
# Directory to write record of game states, leave blank to disable.
|
# Directory to write record of game states, leave blank to disable.
|
||||||
|
|
|
@ -76,3 +76,22 @@ class Bullet:
|
||||||
def get_distance(self, x, y):
|
def get_distance(self, x, y):
|
||||||
"""Return the from the center of the bullet to the point (x, y)."""
|
"""Return the from the center of the bullet to the point (x, y)."""
|
||||||
return ((self.x-x)**2 + (self.y-y)**2)**0.5
|
return ((self.x-x)**2 + (self.y-y)**2)**0.5
|
||||||
|
|
||||||
|
|
||||||
|
class LockOn:
|
||||||
|
"""Lock-on device to assist hero's aiming.
|
||||||
|
This is used as a mutable object to represent a grid of wall.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
x, y (int): coordinates of the target (in grids)
|
||||||
|
destroyed (bool): flag indicating if the target is destroyed
|
||||||
|
"""
|
||||||
|
def __init__(self, x, y, retired=False):
|
||||||
|
self.x, self.y = x, y
|
||||||
|
self.retired = retired
|
||||||
|
|
||||||
|
def place(self, x, y, isdisplayed):
|
||||||
|
"""Move the target by (x, y) (in grids)."""
|
||||||
|
self.x += x
|
||||||
|
self.y += y
|
||||||
|
if not isdisplayed(self.x, self.y): self.retired = True
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -7,7 +7,7 @@ with open('README.rst') as f:
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='brutalmaze',
|
name='brutalmaze',
|
||||||
version='0.8.21',
|
version='0.8.22',
|
||||||
description='A minimalist TPS game with fast-paced action',
|
description='A minimalist TPS game with fast-paced action',
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
url='https://github.com/McSinyx/brutalmaze',
|
url='https://github.com/McSinyx/brutalmaze',
|
||||||
|
|
Loading…
Reference in New Issue