From ba4a575ab52f792f46c2f4d49d44edd7ff8e203d Mon Sep 17 00:00:00 2001 From: Nguyễn Gia Phong Date: Thu, 26 Oct 2017 21:37:21 +0700 Subject: Make enemies smarter by teach them more about the maze --- brutalmaze/characters.py | 74 +++++++++++++++++++++++++----------------------- brutalmaze/constants.py | 14 +++++---- brutalmaze/maze.py | 30 ++++++++------------ 3 files changed, 60 insertions(+), 58 deletions(-) diff --git a/brutalmaze/characters.py b/brutalmaze/characters.py index b64c2fa..2e2294d 100644 --- a/brutalmaze/characters.py +++ b/brutalmaze/characters.py @@ -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 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, fps): + 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 diff --git a/brutalmaze/constants.py b/brutalmaze/constants.py index 0835738..defdfb1 100644 --- a/brutalmaze/constants.py +++ b/brutalmaze/constants.py @@ -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)), diff --git a/brutalmaze/maze.py b/brutalmaze/maze.py index 01dd6de..022c8aa 100644 --- a/brutalmaze/maze.py +++ b/brutalmaze/maze.py @@ -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() -- cgit 1.4.1