Make Chameleons able to camouflage
This commit is contained in:
parent
f177f5aef0
commit
1174c77836
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)."""
|
||||
|
|
Loading…
Reference in New Issue