Make Chameleons able to camouflage

This commit is contained in:
Nguyễn Gia Phong 2017-11-12 21:08:14 +07:00
parent f177f5aef0
commit 1174c77836
6 changed files with 141 additions and 35 deletions

View File

@ -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)

View File

@ -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]

View File

@ -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)

View File

@ -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]

View File

@ -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)."""

View File

@ -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',