-
Notifications
You must be signed in to change notification settings - Fork 0
/
enemy_group.py
230 lines (177 loc) · 8.17 KB
/
enemy_group.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
"""
Invader Shootan v0.6
Created by Andrew Otto 4/19/2011
enemy_group.py
Contains the EnemyGroup class which represents the enemy formation,
as well as the Enemy class which is an individual enemy sprite
"""
import random
import pygame
from pygame.sprite import Group
from asprite import ASprite
from options import *
from vector import *
from bullet import Bullet
class EnemyGroup(Group):
"""Special Group class that handles unison behavior for the enemy waves"""
def __init__(self, bullets):
"""Initializes the group and makes a wave of enemies
window_size is a tuple with the window dimensions (width, height)
rows, cols are the height and width resp. of enemies in the waves
gap is the number of pixels between each sprite
speed is the number of pixels moved at once
move_delay is the number of frames waited between movements
shoot_delay is the number of frames between enemy shots
bullets is a Group that will hold the enemies' bullets
sprite_filename is the sprite file for the ships
"""
Group.__init__(self)
self.window_size = window_size
self.rows = wave_rows
self.cols = wave_cols
self.gap = wave_gap
self.speed = enemy_speed
self.move_delay = enemy_delay
self.shoot_delay = enemy_shoot_delay
self.bullets = bullets
self.sprite_filename = enemy_filename
self.build_wave()
def build_wave(self):
"""Creates the rectangular formation of enemies
Enemy sprites are stored in the inherited Group and the instance formation
"""
#build the formation and store in a 2d array indexed col, row
self.formation = []
for current_col in range(0, self.cols):
col_list = []
for current_row in range(0, self.rows):
new_enemy = Enemy(self.window_size, self.sprite_filename, self.speed, current_col, current_row)
left_pos = current_col * (self.gap + new_enemy.rect.width) + self.gap
top_pos = current_row * (self.gap + new_enemy.rect.height) + self.gap
new_enemy.rect = new_enemy.image.get_rect(left = left_pos, top = top_pos)
col_list.append(new_enemy)
Group.add(self, new_enemy)
self.formation.append(col_list)
self.leftmost_col = 0 #the left- and rightmost cols with living enemies
self.rightmost_col = self.cols - 1
self.move_delay_step = 0 #counts the frames between moves
self.shoot_delay_step = 0 #counts the frames between shots
self.current_vector = EAST #the wave always starts by moving right
def update(self):
"""Instead of updating each sprite independently,
this method is changed to move the enemies in unison
movement happens once every <move_delay> frames
New: a random bottom row enemy will shoot once every <shoot_delay> frames
"""
#movement
self.move_delay_step += 1
if self.move_delay_step == self.move_delay:
self.move_delay_step = 0
movement_vector = self.check_vector()
for current_sprite in Group.sprites(self):
current_sprite.move(movement_vector)
self.shoot_delay_step += 1
if self.shoot_delay_step == self.shoot_delay:
self.shoot_delay_step = 0
self.shoot()
def check_vector(self):
"""Helper function for update
Checks whether the formation can move in the current direction
if border collision will happen, returns a down-pointing vector
otherwise returns the current direction vector
"""
#returns proper vector for when formation is moving right
if self.current_vector == EAST:
if self.living_sprite(self.rightmost_col).rect.right >= self.window_size[0]:
self.current_vector = WEST
return SOUTH
else:
return self.current_vector
#returns proper vector for when formation is moving left
if self.current_vector == WEST:
if self.living_sprite(self.leftmost_col).rect.left <= 0:
self.current_vector = EAST
return SOUTH
else:
return self.current_vector
def living_sprite(self, col):
"""Helper function
returns the first living sprite in the given column index
if none encountered, return None
"""
for current_sprite in self.formation[col]:
if current_sprite is not None:
return current_sprite
return None
def bottom_sprite(self, col):
"""Helper function for shoot
returns the bottom living sprite in the given column index
if none encountered, return None
"""
bottom = None
for current_sprite in self.formation[col]:
if current_sprite is not None:
bottom = current_sprite
return bottom
def shoot(self):
"""Chooses a random bottom enemy to shoot
the bullet is added to the self.bullets group
"""
#pick a random living enemy, then make the bottom living enemy of that column shoot
r = random.randint(0, len(self)-1)
##print ("Random = ", r)
##print ("Enemies = ", len(self))
col = Group.sprites(self)[r].col
shooter = self.bottom_sprite(col)
new_bullet = Bullet(window_size, bullet_filename, bullet_speed, shooter.rect.center, SOUTH)
new_bullet.add(self.bullets)
def adjust_borders(self):
"""Helper function for remove
checks and updates the left- and rightmost column markers
(makes sure that they represent columns with living enemies)
"""
#adjust leftmost_col
while self.living_sprite(self.leftmost_col) is None:
if self.leftmost_col == self.rightmost_col:
return
self.leftmost_col += 1
#adjust rightmost_col
while self.living_sprite(self.rightmost_col) is None:
if self.leftmost_col == self.rightmost_col:
return
self.rightmost_col -= 1
def add(self, *sprites):
"""EnemyGroup doesn't support adding.
Enemies are generated by calling build_wave
"""
pass
def remove(self, *sprites):
"""Removes as normal but also updates the formation array"""
Group.remove(self, *sprites)
for current_sprite in sprites:
row = current_sprite.row
col = current_sprite.col
self.formation[col][row] = None
if col == self.rightmost_col or col == self.leftmost_col:
self.adjust_borders()
class Enemy(ASprite):
"""The Enemy sprite is essentially a placeholder
All actual behavior for the sprites is determined in the EnemyGroup class
"""
def __init__(self, window_size, sprite_filename, speed, x, y):
"""Creates the enemy and places in the default upper left position
window_size is a tuple with the window dimensions (width, height)
sprite_filename is the sprite for the ship
speed is the number of pixels it can move at once
x, y are the sprite's column and row (resp.) in the formation
"""
ASprite.__init__(self, sprite_filename, speed)
self.window_size = window_size
self.col = x
self.row = y
def kill(self):
"""Overridden kill function
just calls the EnemyGroup function that handles proper removal
"""
for current_group in self.groups():
current_group.remove(self)