Make enemies smarter by teach them more about the maze

This commit is contained in:
Nguyễn Gia Phong 2017-10-26 21:37:21 +07:00
parent 3e25eb424c
commit ba4a575ab5
3 changed files with 60 additions and 58 deletions

View File

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

View File

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

View File

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