summary refs log tree commit diff homepage
diff options
context:
space:
mode:
authorNguyễn Gia Phong <vn.mcsinyx@gmail.com>2017-11-12 21:08:14 +0700
committerNguyễn Gia Phong <vn.mcsinyx@gmail.com>2017-11-12 21:08:14 +0700
commit1174c778361ec098782d001b68fc769d97ad6e13 (patch)
tree5fef6235b35609651e27c4b629b6bec9a3748e9d
parentf177f5aef0fac0fed90279b5c64a591f14d777eb (diff)
downloadbrutalmaze-0.1.0.tar.gz
Make Chameleons able to camouflage 0.1.0
-rw-r--r--brutalmaze/characters.py121
-rw-r--r--brutalmaze/constants.py9
-rw-r--r--brutalmaze/maze.py19
-rw-r--r--brutalmaze/utils.py13
-rw-r--r--brutalmaze/weapons.py12
-rwxr-xr-xsetup.py2
6 files changed, 141 insertions, 35 deletions
diff --git a/brutalmaze/characters.py b/brutalmaze/characters.py
index f7c8521..752ea21 100644
--- a/brutalmaze/characters.py
+++ b/brutalmaze/characters.py
@@ -22,11 +22,12 @@ __doc__ = 'brutalmaze module for hero and enemy classes'
 from collections import deque
 from math import atan, atan2, sin, pi
 from random import choice, randrange, shuffle
+from sys import modules
 
 import pygame
 
 from .constants import *
-from .utils import sign, cosin, randsign, regpoly, fill_aapolygon
+from .utils import sign, cosin, randsign, regpoly, fill_aapolygon, choices
 from .weapons import Bullet
 
 
@@ -52,7 +53,7 @@ class Hero:
         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.R = (w * h / sin(pi*2/3) / 624) ** 0.5
 
         self.next_strike = 0
         self.slashing = self.firing = self.dead = False
@@ -103,7 +104,7 @@ class Enemy:
         maze (Maze): the maze
         x, y (int): coordinates of the center of the enemy (in grids)
         angle (float): angle of the direction the enemy pointing (in radians)
-        color (tuple of pygame.Color): colors of the enemy on different HPs
+        color (str): enemy's color name
         awake (bool): flag indicates if the enemy is active
         next_strike (int): the tick that the enemy can do the next attack
         move_speed (float): speed of movement (in frames per grid)
@@ -112,11 +113,11 @@ class Enemy:
         spin_queue (float): frames left to finish spinning
         wound (float): amount of wound
     """
-    def __init__(self, maze, x, y):
+    def __init__(self, maze, x, y, color):
         self.maze = maze
         self.x, self.y = x, y
         self.maze.map[x][y] = ENEMY
-        self.angle, self.color = pi / 4, TANGO[choice(ENEMIES)]
+        self.angle, self.color = pi / 4, color
 
         self.awake = False
         self.next_strike = 0
@@ -138,8 +139,12 @@ class Enemy:
         self.maze.map[self.x][self.y] = ENEMY
 
     def wake(self):
-        """Wake the enemy up if it can see the hero."""
-        if self.awake: return
+        """Wake the enemy up if it can see the hero.
+
+        Return None if the enemy is already awake, True if the function
+        has just woken it, False otherwise.
+        """
+        if self.awake: return None
         startx = starty = MIDDLE
         stopx, stopy, distance = self.x, self.y, self.maze.distance
         if startx > stopx: startx, stopx = stopx, startx
@@ -152,11 +157,13 @@ class Enemy:
             for j in range(starty, stopy + 1):
                 if self.maze.map[i][j] != WALL: continue
                 x, y = self.maze.pos(i, j)
-                if length(x - self.maze.x, y - self.maze.y) <= mind: return
+                if length(x - self.maze.x, y - self.maze.y) <= mind:
+                    return False
         self.awake = True
+        return True
 
     def fire(self):
-        """Return True if the enemy shot the hero, False otherwise."""
+        """Return True if the enemy has just fired, False otherwise."""
         x, y = self.pos()
         if (self.maze.length(x, y) > FIRANGE*self.maze.distance
             or self.next_strike > pygame.time.get_ticks()
@@ -166,14 +173,11 @@ class Enemy:
         self.next_strike = 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]))
+            atan2(self.maze.y - y, self.maze.x - x), self.color))
         return True
 
     def move(self):
-        """Handle the movement of the enemy.
-
-        Return True if it moved, False otherwise.
-        """
+        """Return True if it has just moved, False otherwise."""
         if self.offsetx:
             self.offsetx -= sign(self.offsetx)
             return True
@@ -195,6 +199,13 @@ class Enemy:
                 return True
         return False
 
+    def draw(self):
+        """Draw the enemy."""
+        radious = self.maze.distance/SQRT2 - self.awake*2
+        square = regpoly(4, radious, self.angle, *self.pos())
+        color = TANGO[self.color][int(self.wound)] if self.awake else FG_COLOR
+        fill_aapolygon(self.maze.surface, square, color)
+
     def update(self):
         """Update the enemy."""
         if self.awake:
@@ -207,15 +218,85 @@ class Enemy:
                 self.spin_queue -= sign(self.spin_queue)
             else:
                 self.angle, self.spin_queue = pi / 4, 0.0
-        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.maze.surface, square, color)
+        self.draw()
 
     def hit(self, wound):
-        """Handle the enemy when it's hit by a bullet."""
+        """Handle the enemy when it's attacked."""
         self.wound += wound
 
     def die(self):
         """Handle the enemy's death."""
-        self.maze.map[self.x][self.y] = EMPTY if self.awake else WALL
+        if self.awake:
+            self.maze.map[self.x][self.y] = EMPTY
+            if self.maze.enemy_weights[self.color] > INIT_WEIGHT:
+                self.maze.enemy_weights[self.color] -= 1
+        else:
+            self.maze.map[self.x][self.y] = WALL
+
+
+class Butter(Enemy):
+    """Object representing an enemy of Butter type."""
+    def __init__(self, maze, x, y):
+        Enemy.__init__(self, maze, x, y, 'Butter')
+
+
+class Orange(Enemy):
+    """Object representing an enemy of Orange type."""
+    def __init__(self, maze, x, y):
+        Enemy.__init__(self, maze, x, y, 'Orange')
+
+
+class Chocolate(Enemy):
+    """Object representing an enemy of Chocolate type."""
+    def __init__(self, maze, x, y):
+        Enemy.__init__(self, maze, x, y, 'Chocolate')
+
+
+class Chameleon(Enemy):
+    """Object representing an enemy of Chameleon type.
+
+    Additional attributes:
+        visible (int): the tick until which the Chameleon is visible
+    """
+    def __init__(self, maze, x, y):
+        Enemy.__init__(self, maze, x, y, 'Chameleon')
+        self.visible = 0
+
+    def wake(self):
+        """Wake the Chameleon up if it can see the hero."""
+        if Enemy.wake(self) is True:
+            self.visible = pygame.time.get_ticks() + 1000//ENEMY_SPEED
+
+    def draw(self):
+        """Draw the Chameleon."""
+        if not self.awake or pygame.time.get_ticks() <= self.visible:
+            Enemy.draw(self)
+
+    def hit(self, wound):
+        """Handle the Chameleon when it's hit by a bullet."""
+        self.visible = pygame.time.get_ticks() + 1000//ENEMY_SPEED
+        self.wound += wound
+
+
+class SkyBlue(Enemy):
+    """Object representing an enemy of Sky Blue type."""
+    def __init__(self, maze, x, y):
+        Enemy.__init__(self, maze, x, y, 'SkyBlue')
+
+
+class Plum(Enemy):
+    """Object representing an enemy of Plum type."""
+    def __init__(self, maze, x, y):
+        Enemy.__init__(self, maze, x, y, 'Plum')
+
+
+class ScarletRed(Enemy):
+    """Object representing an enemy of Scarlet Red type."""
+    def __init__(self, maze, x, y):
+        Enemy.__init__(self, maze, x, y, 'ScarletRed')
+
+
+def new_enemy(maze, x, y):
+    """Return an enemy of a random type in the grid (x, y)."""
+    color = choices(maze.enemy_weights)
+    return getattr(modules[__name__], color)(maze, x, y)
diff --git a/brutalmaze/constants.py b/brutalmaze/constants.py
index ecb797c..9d6cacf 100644
--- a/brutalmaze/constants.py
+++ b/brutalmaze/constants.py
@@ -57,13 +57,14 @@ TANGO = {'Butter': ((252, 233, 79), (237, 212, 0), (196, 160, 0)),
          'Orange': ((252, 175, 62), (245, 121, 0), (206, 92, 0)),
          'Chocolate': ((233, 185, 110), (193, 125, 17), (143, 89, 2)),
          'Chameleon': ((138, 226, 52), (115, 210, 22), (78, 154, 6)),
-         'Sky Blue': ((114, 159, 207), (52, 101, 164), (32, 74, 135)),
+         'SkyBlue': ((114, 159, 207), (52, 101, 164), (32, 74, 135)),
          'Plum': ((173, 127, 168), (117, 80, 123), (92, 53, 102)),
-         'Scarlet Red': ((239, 41, 41), (204, 0, 0), (164, 0, 0)),
+         'ScarletRed': ((239, 41, 41), (204, 0, 0), (164, 0, 0)),
          'Aluminium': ((238, 238, 236), (211, 215, 207), (186, 189, 182),
                        (136, 138, 133), (85, 87, 83), (46, 52, 54))}
-ENEMIES = ('Butter', 'Orange', 'Chocolate', 'Chameleon',
-           'Sky Blue', 'Plum', 'Scarlet Red')
+ENEMIES = ['Butter', 'Orange', 'Chocolate', 'Chameleon',
+           'SkyBlue', 'Plum', 'ScarletRed']
+INIT_WEIGHT = 11.25
 ENEMY_HP = 3
 HERO_HP = 6
 BG_COLOR = TANGO['Aluminium'][-1]
diff --git a/brutalmaze/maze.py b/brutalmaze/maze.py
index df83ff0..c7c7102 100644
--- a/brutalmaze/maze.py
+++ b/brutalmaze/maze.py
@@ -26,7 +26,7 @@ from random import choice, getrandbits
 import pygame
 from pygame import RESIZABLE
 
-from .characters import Hero, Enemy
+from .characters import Hero, new_enemy
 from .constants import *
 from .utils import round2, sign, regpoly, fill_aapolygon
 from .weapons import Bullet
@@ -69,6 +69,7 @@ class Maze:
         vx, vy (float): velocity of the maze movement (in pixels per frame)
         rotatex, rotatey: grids rotated
         bullets (list of Bullet): bullets flying
+        enemy_weights (dict): probabilities of enemies to be created
         enemies (list of Enemy): alive enemies
         hero (Hero): the hero
         slashd (float): minimum distance for slashes to be effective
@@ -90,6 +91,7 @@ class Maze:
         self.vx = self.vy = 0.0
         self.rotatex = self.rotatey = 0
         self.bullets, self.enemies = [], []
+        self.enemy_weights = {color: INIT_WEIGHT for color in ENEMIES}
         self.add_enemy()
         self.hero = Hero(self.surface, fps)
         self.map[MIDDLE][MIDDLE] = HERO
@@ -105,7 +107,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, x, y))
+            self.enemies.append(new_enemy(self, x, y))
             walls.remove((x, y))
 
     def pos(self, x, y):
@@ -176,6 +178,11 @@ class Maze:
         """
         return ((self.x-x)**2 + (self.y-y)**2)**0.5
 
+    def hit(self, wound, color):
+        """Handle the hero when he loses HP."""
+        self.hero.wound += wound
+        self.enemy_weights[color] += wound
+
     def slash(self):
         """Handle close-range attacks."""
         for enemy in self.enemies:
@@ -183,7 +190,7 @@ class Maze:
             x, y = enemy.pos()
             d = self.slashd - self.length(x, y)
             if d >= 0:
-                self.hero.wound += d / self.hero.R / enemy.spin_speed
+                self.hit(d / self.hero.R / enemy.spin_speed, enemy.color)
 
         if not self.hero.spin_queue: return
         unit, killist = self.distance/SQRT2 * self.hero.spin_speed, []
@@ -206,13 +213,13 @@ class Maze:
             and time >= self.hero.next_strike):
             self.hero.next_strike = time + ATTACK_SPEED
             self.bullets.append(Bullet(self.surface, self.x, self.y,
-                                       self.hero.angle, FG_COLOR))
+                                       self.hero.angle, 'Aluminium'))
         for i, bullet in enumerate(self.bullets):
             wound = float(bullet.fall_time-time) / BULLET_LIFETIME
             bullet.update(self.fps, self.distance)
             if wound < 0:
                 fallen.append(i)
-            elif bullet.color == FG_COLOR:
+            elif bullet.color == 'Aluminium':
                 x = MIDDLE + round2((bullet.x-self.x) / self.distance)
                 y = MIDDLE + round2((bullet.y-self.y) / self.distance)
                 if self.map[x][y] == WALL:
@@ -229,7 +236,7 @@ class Maze:
                         fallen.append(i)
                         break
             elif bullet.length(self.x, self.y) < self.distance:
-                if not self.hero.spin_queue: self.hero.wound += wound
+                if not self.hero.spin_queue: self.hit(wound, bullet.color)
                 fallen.append(i)
         for i in reversed(fallen): self.bullets.pop(i)
 
diff --git a/brutalmaze/utils.py b/brutalmaze/utils.py
index fdf893f..387fea3 100644
--- a/brutalmaze/utils.py
+++ b/brutalmaze/utils.py
@@ -22,6 +22,7 @@ __doc__ = 'brutalmaze module for hero and enemy classes'
 from functools import reduce
 from math import cos, sin, pi
 from operator import or_
+from random import uniform
 
 import pygame
 from pygame.gfxdraw import filled_polygon, aapolygon
@@ -68,3 +69,15 @@ def sign(n):
 def cosin(x):
     """Return the sum of cosine and sine of x (measured in radians)."""
     return cos(x) + sin(x)
+
+
+def choices(d):
+    """Choose a random key from a dict which has values being relative
+    weights of the coresponding keys.
+    """
+    population, weights = tuple(d.keys()), tuple(d.values())
+    cum_weights = [weights[0]]
+    for weight in weights[1:]: cum_weights.append(cum_weights[-1] + weight)
+    num = uniform(0, cum_weights[-1])
+    for i, w in enumerate(cum_weights):
+        if num <= w: return population[i]
diff --git a/brutalmaze/weapons.py b/brutalmaze/weapons.py
index 7504457..1112c2b 100644
--- a/brutalmaze/weapons.py
+++ b/brutalmaze/weapons.py
@@ -23,7 +23,7 @@ from math import cos, sin
 
 from pygame.time import get_ticks
 
-from .constants import BULLET_LIFETIME, BULLET_SPEED
+from .constants import BULLET_LIFETIME, BULLET_SPEED, ENEMY_HP, TANGO
 from .utils import regpoly, fill_aapolygon
 
 
@@ -34,7 +34,7 @@ class Bullet:
         surface (pygame.Surface): the display to draw on
         x, y (int): coordinates of the center of the bullet (in pixels)
         angle (float): angle of the direction the bullet pointing (in radians)
-        color (pygame.Color): color of the bullet
+        color (str): bullet's color name
         fall_time (int): the tick that the bullet will fall down
     """
     def __init__(self, surface, x, y, angle, color):
@@ -47,8 +47,12 @@ class Bullet:
         s = distance * BULLET_SPEED / fps
         self.x += s * cos(self.angle)
         self.y += s * sin(self.angle)
-        hexagon = regpoly(5, distance // 4, self.angle, self.x, self.y)
-        fill_aapolygon(self.surface, hexagon, self.color)
+        pentagon = regpoly(5, distance // 4, self.angle, self.x, self.y)
+        value = int((1-(self.fall_time-get_ticks())/BULLET_LIFETIME)*ENEMY_HP)
+        try:
+            fill_aapolygon(self.surface, pentagon, TANGO[self.color][value])
+        except IndexError:
+            pass
 
     def place(self, x, y):
         """Move the bullet by (x, y) (in pixels)."""
diff --git a/setup.py b/setup.py
index a72ea9c..24b057d 100755
--- a/setup.py
+++ b/setup.py
@@ -7,7 +7,7 @@ with open('README.rst') as f:
 
 setup(
     name='brutalmaze',
-    version='0.0.4',
+    version='0.1.0',
     description='A hash and slash game with fast-paced action and a minimalist art style',
     long_description=long_description,
     url='https://github.com/McSinyx/brutalmaze',