about summary refs log tree commit diff homepage
diff options
context:
space:
mode:
authorRaphael McSinyx <vn.mcsinyx@gmail.com>2017-10-19 15:24:56 +0700
committerRaphael McSinyx <vn.mcsinyx@gmail.com>2017-10-19 15:24:56 +0700
commitbc72fbf25fa151bce1fd526fe5ca4d98c6bac293 (patch)
tree4f28f3b0ee607196d491776c68abdbb5377b86d2
parente879dcf58dc03ae6730d925675ae6ee6f9733e71 (diff)
downloadbrutalmaze-bc72fbf25fa151bce1fd526fe5ca4d98c6bac293.tar.gz
Add dynamic framerate
-rw-r--r--brutalmaze/characters.py43
-rw-r--r--brutalmaze/constants.py8
-rw-r--r--brutalmaze/main.py14
-rw-r--r--brutalmaze/maze.py77
4 files changed, 80 insertions, 62 deletions
diff --git a/brutalmaze/characters.py b/brutalmaze/characters.py
index 8dc3209..250d9be 100644
--- a/brutalmaze/characters.py
+++ b/brutalmaze/characters.py
@@ -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)
 
diff --git a/brutalmaze/constants.py b/brutalmaze/constants.py
index 427226e..e87a33b 100644
--- a/brutalmaze/constants.py
+++ b/brutalmaze/constants.py
@@ -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)
diff --git a/brutalmaze/main.py b/brutalmaze/main.py
index 40b4116..73d4427 100644
--- a/brutalmaze/main.py
+++ b/brutalmaze/main.py
@@ -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()
diff --git a/brutalmaze/maze.py b/brutalmaze/maze.py
index 4b2abdb..8e3a5ad 100644
--- a/brutalmaze/maze.py
+++ b/brutalmaze/maze.py
@@ -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