about summary refs log tree commit diff homepage
diff options
context:
space:
mode:
authorNguyễn Gia Phong <vn.mcsinyx@gmail.com>2017-10-26 21:37:21 +0700
committerNguyễn Gia Phong <vn.mcsinyx@gmail.com>2017-10-26 21:37:21 +0700
commitba4a575ab52f792f46c2f4d49d44edd7ff8e203d (patch)
tree7c0c910dd486dac69ed7b25706c953ca458103e4
parent3e25eb424c0ed645eb6be437d05bce47aab920b7 (diff)
downloadbrutalmaze-ba4a575ab52f792f46c2f4d49d44edd7ff8e203d.tar.gz
Make enemies smarter by teach them more about the maze
-rw-r--r--brutalmaze/characters.py74
-rw-r--r--brutalmaze/constants.py14
-rw-r--r--brutalmaze/maze.py30
3 files changed, 60 insertions, 58 deletions
diff --git a/brutalmaze/characters.py b/brutalmaze/characters.py
index b64c2fa..2e2294d 100644
--- a/brutalmaze/characters.py
+++ b/brutalmaze/characters.py
@@ -19,12 +19,13 @@
 
 from collections import deque
 from math import atan2, sin, pi
-from random import choice, shuffle
+from random import choice, shuffle, uniform
 
 import pygame
 
 from .constants import *
-from .utils import randsign, regpoly, fill_aapolygon, pos, sign
+from .utils import randsign, regpoly, fill_aapolygon, pos, sign, length
+from .weapons import Bullet
 
 __doc__ = 'brutalmaze module for hero and enemy classes'
 
@@ -82,37 +83,50 @@ class Hero:
 
 class Enemy:
     """Object representing an enemy."""
-    def __init__(self, surface, fps, maze, x, y):
-        self.surface, self.maze = surface, maze
+    def __init__(self, maze, x, y):
+        self.maze = maze
         self.angle, self.color = pi / 4, TANGO[choice(ENEMIES)]
         self.x, self.y = x, y
-        self.maze[x][y] = ENEMY
+        self.maze.map[x][y] = ENEMY
 
         self.awake = False
         self.next_move = 0
-        self.move_speed = fps / MOVE_SPEED
+        self.move_speed = self.maze.fps / ENEMY_SPEED
         self.offsetx = self.offsety = 0
-        self.spin_speed = fps / ENEMY_HP
+        self.spin_speed = self.maze.fps / ENEMY_HP
         self.spin_queue = self.wound = 0.0
 
-    def pos(self, distance, middlex, middley):
+    def pos(self):
         """Return coordinate of the center of the enemy."""
-        x, y = pos(self.x, self.y, distance, middlex, middley)
-        step = distance / self.move_speed
-        return x + self.offsetx*step, y + self.offsety*step
+        x, y = pos(self.x, self.y, self.maze.distance,
+                   self.maze.middlex, self.maze.middley)
+        return x + self.offsetx*self.maze.step, y + self.offsety*self.maze.step
 
     def place(self, x=0, y=0):
         """Move the enemy by (x, y) (in grids)."""
         self.x += x
         self.y += y
-        self.maze[self.x][self.y] = ENEMY
+        self.maze.map[self.x][self.y] = ENEMY
+
+    def fire(self):
+        """Return True if the enemy shot the hero, False otherwise."""
+        x, y = self.pos()
+        if (length(x, y, self.maze.x, self.maze.y) > FIRANGE*self.maze.distance
+            or self.next_move > pygame.time.get_ticks()
+            or (self.x, self.y) in AROUND_HERO or self.offsetx or self.offsety
+            or uniform(-2, 2) < (INIT_SCORE/self.maze.score) ** 2):
+            return False
+        self.next_move = pygame.time.get_ticks() + ATTACK_SPEED
+        self.maze.bullets.append(Bullet(
+            self.maze.surface, x, y,
+            atan2(self.maze.y - y, self.maze.x - x), self.color[0]))
+        return True
 
-    def move(self, fps):
+    def move(self):
         """Handle the movement of the enemy.
 
         Return True if it moved, False otherwise.
         """
-        if self.next_move > pygame.time.get_ticks(): return False
         if self.offsetx:
             self.offsetx -= sign(self.offsetx)
             return True
@@ -120,46 +134,36 @@ class Enemy:
             self.offsety -= sign(self.offsety)
             return True
 
-        self.move_speed = fps / MOVE_SPEED
+        self.move_speed = self.maze.fps / ENEMY_SPEED
         directions = [(sign(MIDDLE - self.x), 0), (0, sign(MIDDLE - self.y))]
         shuffle(directions)
+        directions.append(choice(CROSS))
         for x, y in directions:
-            if (x or y) and self.maze[self.x + x][self.y + y] == EMPTY:
+            if (x or y) and self.maze.map[self.x + x][self.y + y] == EMPTY:
                 self.offsetx = round(x * (1 - self.move_speed))
                 self.offsety = round(y * (1 - self.move_speed))
-                self.maze[self.x][self.y] = EMPTY
+                self.maze.map[self.x][self.y] = EMPTY
                 self.place(x, y)
                 return True
         return False
 
-    def update(self, fps, distance, middlex, middley):
+    def update(self):
         """Update the enemy."""
         if self.awake:
-            self.spin_speed, old_speed = fps / ENEMY_HP, self.spin_speed
+            self.spin_speed, old_speed = self.maze.fps / ENEMY_HP, self.spin_speed
             self.spin_queue *= self.spin_speed / old_speed
-            if not self.spin_queue and not self.move(fps):
+            if not self.spin_queue and not self.fire() and not self.move():
                 self.spin_queue = randsign() * self.spin_speed
             if abs(self.spin_queue) > 0.5:
                 self.angle += sign(self.spin_queue) * pi / 2 / self.spin_speed
                 self.spin_queue -= sign(self.spin_queue)
             else:
                 self.angle, self.spin_queue = pi / 4, 0.0
-        radious = distance/SQRT2 - self.awake*2
-        square = regpoly(4, radious, self.angle,
-                         *self.pos(distance, middlex, middley))
+        radious = self.maze.distance/SQRT2 - self.awake*2
+        square = regpoly(4, radious, self.angle, *self.pos())
         color = self.color[int(self.wound)] if self.awake else FG_COLOR
-        fill_aapolygon(self.surface, square, color)
-
-    def firable(self):
-        """Return True if the enemies should shoot the hero,
-        False otherwise.
-        """
-        if (not self.awake or self.spin_queue or self.offsetx or self.offsety
-            or (self.x, self.y) in SURROUND_HERO):
-            return False
-        self.next_move = pygame.time.get_ticks() + ATTACK_SPEED
-        return True
+        fill_aapolygon(self.maze.surface, square, color)
 
     def die(self):
         """Handle the enemy's death."""
-        self.maze[self.x][self.y] = EMPTY if self.awake else WALL
+        self.maze.map[self.x][self.y] = EMPTY if self.awake else WALL
diff --git a/brutalmaze/constants.py b/brutalmaze/constants.py
index 0835738..defdfb1 100644
--- a/brutalmaze/constants.py
+++ b/brutalmaze/constants.py
@@ -36,16 +36,20 @@ CELL_WIDTH = ROAD_WIDTH * 2     # grids
 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  # grid/s
-BULLET_SPEED = 10   # grid/s
 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
-BULLET_LIFETIME = 1000  # ms
+FIRANGE = 6     # grids
+BULLET_LIFETIME = 1000.0 * FIRANGE / (BULLET_SPEED-HERO_SPEED)  # ms
+FIRE_DAM = 1# / SQRT2    # HP
 
 EMPTY, WALL, HERO, ENEMY = range(4)
 ADJACENT_GRIDS = (1, 0), (0, 1), (-1, 0), (0, -1)
-SURROUND_HERO = set((MIDDLE + x, MIDDLE + y) for x, y in
-                    ADJACENT_GRIDS + ((1, 1), (-1, 1), (-1, -1), (1, -1)))
+CROSS = ADJACENT_GRIDS + ((0, 0),)
+AROUND_HERO = set((MIDDLE + x, MIDDLE + y) for x, y in
+                  ADJACENT_GRIDS + ((1, 1), (-1, 1), (-1, -1), (1, -1)))
 
 TANGO = {'Butter': ((252, 233, 79), (237, 212, 0), (196, 160, 0)),
          'Orange': ((252, 175, 62), (245, 121, 0), (206, 92, 0)),
diff --git a/brutalmaze/maze.py b/brutalmaze/maze.py
index 01dd6de..022c8aa 100644
--- a/brutalmaze/maze.py
+++ b/brutalmaze/maze.py
@@ -19,7 +19,7 @@
 
 from collections import deque
 from math import pi, atan, atan2, log
-from random import choice, getrandbits, uniform
+from random import choice, getrandbits
 
 import pygame
 from pygame import RESIZABLE
@@ -56,7 +56,7 @@ class Maze:
     """Object representing the maze, including the characters."""
     def __init__(self, size, fps):
         self.w, self.h = size
-        self.fps, self.speed = fps, fps / MOVE_SPEED
+        self.fps, self.speed = fps, fps / HERO_SPEED
         self.surface = pygame.display.set_mode(size, RESIZABLE)
         self.distance = (self.w * self.h / 416) ** 0.5
         self.step = self.distance / self.speed
@@ -87,7 +87,7 @@ class Maze:
             x, y = choice(walls)
             if all(self.map[x + a][y + b] == WALL for a, b in ADJACENT_GRIDS):
                 continue
-            self.enemies.append(Enemy(self.surface, self.fps, self.map, x, y))
+            self.enemies.append(Enemy(self, x, y))
             walls.remove((x, y))
 
     def draw(self):
@@ -165,7 +165,7 @@ class Maze:
         """Handle close-ranged attacks."""
         for enemy in self.enemies:
             if not enemy.spin_queue: continue
-            x, y = enemy.pos(self.distance, self.middlex, self.middley)
+            x, y = enemy.pos()
             d = self.slashd - length(x, y, self.x, self.y)
             if d >= 0:
                 self.hero.wound += d / self.hero.R / enemy.spin_speed
@@ -173,7 +173,7 @@ class Maze:
         if not self.hero.spin_queue: return
         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)
+            x, y = enemy.pos()
             d = length(x, y, self.x, self.y)
             if d <= self.slashd:
                 enemy.wound += (self.slashd-d) / unit
@@ -187,12 +187,6 @@ class Maze:
     def track_bullets(self):
         """Handle the bullets."""
         fallen, time = [], pygame.time.get_ticks()
-        for enemy in self.enemies:
-            # Chance that an enemy fire increase from 25% to 50%
-            if uniform(-2, 2) > (INIT_SCORE/self.score)**2 and enemy.firable():
-                x, y = enemy.pos(self.distance, self.middlex, self.middley)
-                angle, color = atan2(self.y - y, self.x - x), enemy.color[0]
-                self.bullets.append(Bullet(self.surface, x, y, angle, color))
         if (self.hero.firing and not self.hero.slashing
             and time >= self.hero.next_strike):
             self.hero.next_strike = time + ATTACK_SPEED
@@ -210,9 +204,9 @@ class Maze:
                     fallen.append(i)
                     continue
                 for j, enemy in enumerate(self.enemies):
-                    x, y = enemy.pos(self.distance, self.middlex, self.middley)
+                    x, y = enemy.pos()
                     if length(bullet.x, bullet.y, x, y) < self.distance:
-                        enemy.wound += wound
+                        enemy.wound += FIRE_DAM
                         if enemy.wound >= ENEMY_HP:
                             self.score += enemy.wound
                             enemy.die()
@@ -220,7 +214,7 @@ class Maze:
                         fallen.append(i)
                         break
             elif length(bullet.x, bullet.y, self.x, self.y) < self.distance:
-                if not self.hero.spin_queue: self.hero.wound += wound
+                if not self.hero.spin_queue: self.hero.wound += FIRE_DAM
                 fallen.append(i)
         for i in reversed(fallen): self.bullets.pop(i)
 
@@ -229,12 +223,13 @@ class Maze:
         if self.paused: return
         self.offsetx *= fps / self.fps
         self.offsety *= fps / self.fps
-        self.fps, self.speed = fps, fps / MOVE_SPEED
+        self.fps, self.speed = fps, fps / HERO_SPEED
         self.step = self.distance / self.speed
 
         d = self.distance*1.5 - self.hero.R
-        dx, dy = sign(self.right) - sign(self.left), sign(self.down) - sign(self.up)
+        dx = sign(self.right) - sign(self.left)
         self.offsetx += dx
+        dy = sign(self.down) - sign(self.up)
         self.offsety += dy
         x, y = MIDDLE - sign(self.offsetx)*2, MIDDLE - sign(self.offsety)*2
         if ((self.map[x][MIDDLE - 1] != EMPTY
@@ -262,8 +257,7 @@ class Maze:
             for bullet in self.bullets: bullet.place(dx, dy, self.step)
 
         self.draw()
-        for enemy in self.enemies:
-            enemy.update(fps, self.distance, self.middlex, self.middley)
+        for enemy in self.enemies: enemy.update()
         self.hero.update(fps)
         self.slash()
         self.track_bullets()