Add dynamic framerate

This commit is contained in:
Nguyễn Gia Phong 2017-10-19 15:24:56 +07:00
parent e879dcf58d
commit bc72fbf25f
4 changed files with 80 additions and 62 deletions

View File

@ -62,16 +62,16 @@ def sign(n):
class Hero:
"""Object representing the hero."""
def __init__(self, surface):
def __init__(self, surface, fps):
self.surface = surface
w, h = self.surface.get_width(), self.surface.get_height()
self.x, self.y = w >> 1, h >> 1
self.angle, self.color = pi / 4, TANGO['Aluminium']
self.R = int((w * h / sin(pi*2/3) / 624) ** 0.5)
self.wound = 0.0
self.speed = FPS // len(self.color)
self.spin_speed = int(round(fps / len(self.color)))
self.spin_queue, self.slashing = deque(), False
self.wound = 0.0
def get_color(self):
"""Return the color of the hero based on the amount of wounds."""
@ -82,25 +82,25 @@ class Hero:
each spin.
"""
if self.slashing and not self.spin_queue:
if hold: self.spin_queue.extend([0] * (self.speed >> 1))
self.spin_queue.extend([randsign()] * self.speed)
if hold: self.spin_queue.extend([0] * (self.spin_speed >> 1))
self.spin_queue.extend([randsign()] * self.spin_speed)
def draw(self, color=None):
"""Draw the hero."""
trigon = regpoly(3, self.R, self.angle, self.x, self.y)
fill_aapolygon(self.surface, trigon, color or self.get_color())
def update(self):
def update(self, fps):
"""Update the hero."""
self.wound -= HEAL_SPEED / len(self.color) / self.speed
self.spin_speed = int(round(fps / (len(self.color)-self.wound)))
self.wound -= HEAL_SPEED / len(self.color) / (self.spin_speed or 1)
if self.wound < 0: self.wound = 0.0
self.speed = int(FPS / (len(self.color)-self.wound))
self.slash(hold=True)
direction = self.spin_queue.popleft() if self.spin_queue else 0
self.draw(color=BG_COLOR)
if direction:
self.angle += direction * pi * 2 / 3 / self.speed
self.angle += direction * pi * 2 / 3 / self.spin_speed
else:
# Follow the mouse cursor
x, y = pygame.mouse.get_pos()
@ -116,27 +116,28 @@ class Hero:
class Enemy:
"""Object representing an enemy."""
def __init__(self, surface, maze, kind, x, y):
def __init__(self, surface, fps, maze, kind, x, y):
self.surface, self.maze = surface, maze
self.angle, self.color = pi / 4, TANGO[kind]
self.x, self.y = x, y
self.maze[x][y] = ENEMY
self.awake = False
self.move_speed = fps / MOVE_SPEED
self.offsetx = self.offsety = 0
self.spin_speed = int(round(fps / len(self.color)))
self.spin_queue = []
self.speed = FPS // len(self.color)
self.wound = 0.0
def pos(self, distance, middlex, middley):
"""Return coordinate of the center of the enemy."""
x, y = pos(self.x, self.y, distance, middlex, middley)
step = distance / MOVE_SPEED
step = distance / self.move_speed
return x + self.offsetx*step, y + self.offsety*step
def draw(self, distance, middlex, middley, color):
"""Draw the enemy, given distance between grids and the middle grid."""
radious = distance/SQRT2 - (self.awake and 2)
radious = distance/SQRT2 - self.awake*2
square = regpoly(4, radious, self.angle,
*self.pos(distance, middlex, middley))
fill_aapolygon(self.surface, square, color)
@ -149,7 +150,7 @@ class Enemy:
self.y %= len(self.maze)
self.maze[self.x][self.y] = ENEMY
def move(self):
def move(self, fps):
"""Handle the movement of the enemy.
Return True if it moved, False otherwise.
@ -161,25 +162,27 @@ class Enemy:
self.offsety -= sign(self.offsety)
return True
self.move_speed = fps / MOVE_SPEED
directions = [(sign(MIDDLE - self.x), 0), (0, sign(MIDDLE - self.y))]
shuffle(directions)
for x, y in directions:
if (x or y) and self.maze[self.x + x][self.y + y] == EMPTY:
self.offsetx = x * (1-MOVE_SPEED)
self.offsety = y * (1-MOVE_SPEED)
self.offsetx = round(x * (1 - self.move_speed))
self.offsety = round(y * (1 - self.move_speed))
self.maze[self.x][self.y] = EMPTY
self.place(x, y)
return True
return False
def update(self, distance, middlex, middley):
def update(self, fps, distance, middlex, middley):
"""Update the enemy."""
if self.awake:
self.draw(distance, middlex, middley, BG_COLOR)
if not self.spin_queue and not self.move():
self.spin_queue.extend([randsign()] * self.speed)
if not self.spin_queue and not self.move(fps):
self.spin_speed = int(round(fps / len(self.color)))
self.spin_queue.extend([randsign()] * self.spin_speed)
if self.spin_queue:
self.angle += self.spin_queue.pop() * pi / 2 / self.speed
self.angle += self.spin_queue.pop() * pi / 2 / self.spin_speed
self.draw(distance, middlex, middley,
self.color[int(self.wound)] if self.awake else FG_COLOR)

View File

@ -28,15 +28,15 @@ ICON = image.load(resource_filename('brutalmaze', 'icon.png'))
SQRT2 = 2 ** 0.5
GOLDEN_MEAN = 5**0.5/2 + 0.5
FPS = 30
SIZE = 400, 400
MAZE_SIZE = 10
INIT_FPS = 30.0
SIZE = 640, 480
MAZE_SIZE = 12
ROAD_WIDTH = 5
CELL_WIDTH = ROAD_WIDTH * 2
MIDDLE = (MAZE_SIZE + MAZE_SIZE%2 - 1)*ROAD_WIDTH + ROAD_WIDTH//2
LAST_ROW = (MAZE_SIZE-1) * ROAD_WIDTH * 2
INIT_SCORE = 208.2016
MOVE_SPEED = 5 # step/grid
MOVE_SPEED = 5 # grid/s
HEAL_SPEED = 1.0 # HP/s
EMPTY, WALL, HERO, ENEMY = range(4)

View File

@ -17,6 +17,8 @@
#
# Copyright (C) 2017 Nguyễn Gia Phong
from collections import deque
import pygame
from .constants import *
@ -28,7 +30,8 @@ def main():
pygame.init()
pygame.display.set_icon(ICON)
pygame.fastevent.init()
maze, going, clock = Maze(SIZE), True, pygame.time.Clock()
maze, clock = Maze(SIZE, INIT_FPS), pygame.time.Clock()
fps, flash_time, going = INIT_FPS, deque(), True
while going:
events = pygame.fastevent.get()
for event in events:
@ -66,6 +69,11 @@ def main():
maze.hero.slashing = False
elif event.type == VIDEORESIZE:
maze.resize(event.w, event.h)
maze.update()
clock.tick(FPS)
if len(flash_time) > 10:
new_fps = 10000.0 / (flash_time[-1] - flash_time[0])
fps += -1 if new_fps < fps else 5
flash_time.popleft()
maze.update(fps)
flash_time.append(pygame.time.get_ticks())
clock.tick(fps)
pygame.quit()

View File

@ -63,24 +63,25 @@ def new_column():
class Maze:
"""Object representing the maze, including the characters."""
def __init__(self, size):
def __init__(self, size, fps):
self.w, self.h = size
self.fps, self.speed = fps, fps / MOVE_SPEED
self.surface = pygame.display.set_mode(size, RESIZABLE)
self.distance = (self.w * self.h / 416) ** 0.5
self.step = self.distance / MOVE_SPEED
self.step = self.distance / self.speed
self.middlex, self.middley = self.x, self.y = self.w >> 1, self.h >> 1
w, h = (int((i/self.distance+2) / 2) for i in size)
self.rangex = range(MIDDLE - w, MIDDLE + w + 1)
self.rangey = range(MIDDLE - h, MIDDLE + h + 1)
self.right = self.down = self.offsetx = self.offsety = 0
self.offsetx = self.offsety = 0.0
self.score = INIT_SCORE
self.map = deque()
for _ in range(MAZE_SIZE): self.map.extend(new_column())
self.rotatex = self.rotatey = 0
self.right = self.down = self.rotatex = self.rotatey = 0
self.enemies = []
self.add_enemy()
self.hero = Hero(self.surface)
self.hero = Hero(self.surface, fps)
self.map[MIDDLE][MIDDLE] = HERO
self.slashd = self.hero.R + self.distance/SQRT2
self.draw()
@ -96,7 +97,7 @@ class Maze:
if all(self.map[x + a][y + b] == WALL for a, b in ADJACENT_GRIDS):
continue
self.enemies.append(
Enemy(self.surface, self.map, choice(ENEMIES), x, y))
Enemy(self.surface, self.fps, self.map, choice(ENEMIES), x, y))
walls.remove((x, y))
def draw(self):
@ -130,11 +131,11 @@ class Maze:
"""Rotate the maze by (x, y)."""
for enemy in self.enemies: self.map[enemy.x][enemy.y] = EMPTY
if x:
self.offsetx = 0
self.offsetx = 0.0
self.map.rotate(x)
self.rotatex += x
if y:
self.offsety = 0
self.offsety = 0.0
for d in self.map: d.rotate(y)
self.rotatey += y
@ -169,7 +170,7 @@ class Maze:
def slash(self):
"""Slash the enemies."""
unit, killist = self.distance/SQRT2 * self.hero.speed, []
unit, killist = self.distance/SQRT2 * self.hero.spin_speed, []
for i, enemy in enumerate(self.enemies):
x, y = enemy.pos(self.distance, self.middlex, self.middley)
d = length(x, y, self.x, self.y)
@ -182,33 +183,39 @@ class Maze:
for i in reversed(killist): self.enemies.pop(i)
self.add_enemy()
def update(self):
def update(self, fps):
"""Update the maze."""
modified, d = False, self.distance*1.5 - self.hero.R
self.offsetx += self.right
s = sign(self.offsetx) * 2
if ((self.map[MIDDLE - s][MIDDLE - 1]
or self.map[MIDDLE - s][MIDDLE]
or self.map[MIDDLE - s][MIDDLE + 1])
and abs(self.offsetx*self.step) > d):
self.offsetx -= self.right
else:
modified = True
self.offsetx *= fps / self.fps
self.offsety *= fps / self.fps
self.fps, self.speed = fps, fps / MOVE_SPEED
self.step = self.distance / self.speed
self.offsety += self.down
s = sign(self.offsety) * 2
if ((self.map[MIDDLE - 1][MIDDLE - s]
or self.map[MIDDLE][MIDDLE - s]
or self.map[MIDDLE + 1][MIDDLE - s])
and abs(self.offsety*self.step) > d):
self.offsety -= self.down
else:
modified = True
modified, d = False, self.distance*1.5 - self.hero.R
if self.right:
self.offsetx += self.right
s = sign(self.offsetx) * 2
if ((self.map[MIDDLE - s][MIDDLE - 1]
or self.map[MIDDLE - s][MIDDLE]
or self.map[MIDDLE - s][MIDDLE + 1])
and abs(self.offsetx*self.step) > d):
self.offsetx -= self.right
else:
modified = True
if self.down:
self.offsety += self.down
s = sign(self.offsety) * 2
if ((self.map[MIDDLE - 1][MIDDLE - s]
or self.map[MIDDLE][MIDDLE - s]
or self.map[MIDDLE + 1][MIDDLE - s])
and abs(self.offsety*self.step) > d):
self.offsety -= self.down
else:
modified = True
if modified:
self.map[MIDDLE][MIDDLE] = EMPTY
self.rotate(sign(self.offsetx) * (abs(self.offsetx)==MOVE_SPEED),
sign(self.offsety) * (abs(self.offsety)==MOVE_SPEED))
self.rotate(sign(self.offsetx) * (abs(self.offsetx)>=self.speed),
sign(self.offsety) * (abs(self.offsety)>=self.speed))
self.map[MIDDLE][MIDDLE] = HERO
self.middlex = self.x + self.offsetx*self.step
self.middley = self.y + self.offsety*self.step
@ -217,15 +224,15 @@ class Maze:
if not enemy.awake: self.wake(enemy)
for enemy in self.enemies:
enemy.update(self.distance, self.middlex, self.middley)
self.hero.update()
enemy.update(fps, self.distance, self.middlex, self.middley)
self.hero.update(fps)
if self.hero.slashing: self.slash()
for enemy in self.enemies:
if not enemy.spin_queue: continue
x, y = enemy.pos(self.distance, self.middlex, self.middley)
d = length(x, y, self.x, self.y)
if d <= self.slashd:
self.hero.wound += (self.slashd-d) / self.hero.R / enemy.speed
self.hero.wound += (self.slashd-d) / self.hero.R / enemy.spin_speed
pygame.display.flip()
pygame.display.set_caption('Brutal Maze - Score: {}'.format(
int(self.score - INIT_SCORE)))
@ -238,7 +245,7 @@ class Maze:
self.hero.resize()
self.distance = (w * h / 416) ** 0.5
self.step = self.distance / MOVE_SPEED
self.step = self.distance / self.speed
self.middlex = self.x + self.offsetx*self.step
self.middley = self.y + self.offsety*self.step
self.x, self.y = w >> 1, h >> 1