Make enemies smarter by teach them more about the maze
This commit is contained in:
parent
3e25eb424c
commit
ba4a575ab5
|
@ -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 move(self, fps):
|
||||
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):
|
||||
"""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
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue