class StateManager(object): def __init__(self): self.quit = False self._init_window() self._init_game() self.mode = "SPLASH" # Prevent bouncing on switching game modes self.debounce_timer = DEBOUNCE # Create a window for the game def _init_window(self): # Window object represents the game's window self.window = pyglet.window.Window(WIDTH, HEIGHT) # Keys holds a handler that keeps track of keyboard state, part of pyglet self.keys = pyglet.window.key.KeyStateHandler() self.window.push_handlers(self.keys) # Stage the game or return it to its initial state def _init_game(self): self.hud = HUD() self.entities = [] self.spawn_player() self.exhaust = ParticleSpawner( self.player.pos.getCopy(), self.player.angle + math.pi, math.pi / 4, .01, ParticleFactory(speed=20, color=(255, 0, 0)), True) self.entities.append(self.exhaust) #Create a new instance of the Player class at the center of the screen def spawn_player(self): self.player = Player(Vect2(x=self.window.width/2, y=self.window.height/2)) self.entities.append(self.player) # This function runs when the look is in game mode, and has all the updating/drawing logic def game_loop(self, dt): #Clear frame before looping self.window.clear() #print(pyglet.gl.get_current_context()) # On a proper engine the controller would probably be its own class. # That level of abstraction makes it easier to use keyboards, mice, and # other controllers the user may have controller = { 'acc': self.keys[key.W], 'left': self.keys[key.A], 'right': self.keys[key.D], 'fire': self.keys[key.SPACE], 'quit': self.keys[key.ESCAPE], 'pause': self.keys[key.P] } self.quit = controller['quit'] if controller['pause'] and self.debounce_timer <= 0: self.mode = "PAUSE" self.debounce_timer = DEBOUNCE self.player.input(controller) #turn on thrust effect if ship is accelerating self.exhaust.active = controller['acc'] self.exhaust.angle = (self.player.angle + math.pi) self.exhaust.pos = self.player.pos.getCopy() self.spawn_bullets() self.spawn_asteroids() self.detect_collisions() for e in self.entities: e.update(dt) #for e in self.entities: # print(e) batch = pyglet.graphics.Batch() for e in self.entities: # batch.add expects a series of arguments # most easily delivered as a tuple. # * is the untuple argument. batch.add(*e.draw()) # Filter out any dead objects self.entities[:] = [e for e in self.entities if e.isAlive()] # Draw objects to the frame batch.draw() self.hud.drawHUD() # Determine if a bullet should be spawned, and then spawns a bullet def spawn_bullets(self): if self.player.isFiring(): self.entities.append( Bullet( self.player.pos.getCopy(), self.player.angle ) ) # Maintain a minimum asteroid population def spawn_asteroids(self): # Asteroid Spawning asteroids = [e for e in self.entities if isinstance(e, Asteroid)] if len(asteroids) < targetNo: newAsteroid = Asteroid(3, Vect2(0, 0)) self.entities.append(newAsteroid) # This function determines if any objects are colliding in a meaningful way for the game def detect_collisions(self): asteroids = [e for e in self.entities if isinstance(e, Asteroid)] for asteroid in asteroids: if self.player.overlaps(asteroid.hit_radius, asteroid.pos.getCopy()): self.player.kill() # Check if player is actually dead, it may be in invuln # period if (self.player.isAlive() != True): if (self.hud.has_lives()): self.spawn_player() self.hud.kill() else: self.mode = "GAMEOVER" # Process asteroid/bullet collisions for bullet in [e for e in self.entities if isinstance(e, Bullet)]: for asteroid in asteroids: if bullet.overlaps( asteroid.hit_radius, asteroid.pos.getCopy()): asteroid.kill() self.entities.append( AsteroidDebris( asteroid.pos.getCopy())) if asteroid.size > 1: # add two baby asteroids! self.entities.append( Asteroid( asteroid.size - 1, asteroid.pos.getCopy())) self.entities.append( Asteroid( asteroid.size - 1, asteroid.pos.getCopy())) # Remove bullet bullet.kill() # Log the points self.hud.hit() # Inform the main function if the player requested to quit def is_quit(self): return self.quit # Dispatch loop to the right function def loop(self, dt): if self.debounce_timer > 0: self.debounce_timer -= dt if self.mode == "GAME": self.game_loop(dt) elif self.mode == "PAUSE": self.pause_loop(dt) elif self.mode == "SPLASH": self.splash_loop(dt) elif self.mode == "GAMEOVER": self.game_over_loop(dt) else: self.quit == True print("Error: Debug: state.mode == Invalid state!") # Pause screen def pause_loop(self, dt): self.window.clear() label = pyglet.text.Label("Game Paused: Press p to unpause, or ESC to quit", font_size=24, x=WIDTH//2, y=HEIGHT//2, anchor_x = 'center', anchor_y = 'center') label.draw() if self.keys[key.P] and self.debounce_timer <= 0: self.mode = "GAME" self.debounce_timer = DEBOUNCE elif self.keys[key.ESCAPE]: self.quit = True # Splash screen def splash_loop(self, dt): label = pyglet.text.Label("Rocks in Space: Press s to start", font_size=38, x=WIDTH//2, y=HEIGHT//2, anchor_x = 'center', anchor_y = 'center') label.draw() if self.keys[key.S]: self.mode = "GAME" elif self.keys[key.ESCAPE]: self.quit = True # Game over screen def game_over_loop(self, dt): self.window.clear() label = pyglet.text.Label("Game over! Press S to restart, or ESC to quit", font_size=24, x=WIDTH//2, y=HEIGHT//2, anchor_x = 'center', anchor_y = 'center') label.draw() if self.keys[key.S]: self.mode = "GAME" self._init_game() elif self.keys[key.ESCAPE]: self.quit = True
class Map: def __init__(self, width, height): self.windowWidth = width self.windowHeight = height self.widthNumber = 0 self.heightNumber = 0 self.map = [] # Contains all the cells that make up the map self.path = [ ] # Ordered list of cells that make up the path for the enemies self.walls = [] # List containing all the cells that make up the walls self.enemies = [] # List of all enemies currently on the field self.level = 0 # Incrementing level variable, increases difficulty self.towers = [] # List of all towers currently on the map self.bullets = [] # List of all bullets self.start = 0 self.end = 0 self.startCell = None self.endCell = None self.hud = HUD(width, height) def createEnemy(self): enemy1 = Enemy(0, 0, self.path) self.enemies.append(enemy1) def createTower(self, cell, id): if (cell.id is 1): # The cell is a wall so create the tower tower1 = Tower(cell, id) self.towers.append(tower1) else: # The cell is not a tower, so don't create it print("Error: Can only build towers on a wall") # Generate a map of cells # widthNo - the number of cells in width # height - the number of cells in height # fills self.map and self.path def generateMap(self, widthNo, heightNo): # Create random start and end points self.widthNumber = widthNo self.heightNumber = heightNo bottomBarHeight = 100 cellWidth = round(self.windowWidth / widthNo) cellHeight = round((self.windowHeight - bottomBarHeight) / heightNo) # Create a map of wall cells for i in range(0, heightNo): for j in range(0, widthNo): color = [0, 0, 0] # HSV Array containing the cell color randomInt = random.randint(1, 30) color[0] = randomInt color[1] = randomInt color[2] = randomInt newCell = Cell(j * cellWidth, i * cellHeight, cellWidth, cellHeight, 0, color) # find all the neighbors of the cell self.map.append(newCell) self.findNeighbors() # Defining the start and end cells # Start cell, top left quarter of map # End cell, bottom right quarter of map self.start = [ random.randint(0, round(widthNo / 4)), random.randint(0, round(heightNo / 4)) ] self.end = [ random.randint(round(3 * widthNo / 4), widthNo), random.randint(round(3 * heightNo / 4), heightNo) ] # print("This is start: " + str(self.start)) # print("This is end: " + str(self.end)) startPosition = [ self.start[0] * self.map[0].width, self.start[1] * self.map[0].height ] self.startCell = self.findCursorCell(startPosition) endPosition = [ self.end[0] * self.map[0].width, self.end[1] * self.map[0].height ] self.endCell = self.findCursorCell(endPosition) # Debugging print statement in order to print out the current map # self.printArrayCellContents() # Create a path from the start to the end, make the path a little interesting def generatePath(self, widthNo, heightNo): path = [] # contains all the cells that make up the path # Create multiple different waypoints and connect them with paths # Waypoint for the top right quarter waypoint1 = [ random.randint(round(widthNo / 4), round(3 * widthNo / 4)), random.randint(0, round(heightNo / 2)) ] # Waypoint for the bottom left corner waypoint2 = [ random.randint(0, round(widthNo / 2)), random.randint(round(heightNo / 4), round(3 * heightNo / 4)) ] way1Position = [ waypoint1[0] * self.map[0].width, waypoint1[1] * self.map[0].height ] self.waypoint1Cell = self.findCursorCell(way1Position) way2Position = [ waypoint2[0] * self.map[0].width, waypoint2[1] * self.map[0].height ] self.waypoint2Cell = self.findCursorCell(way2Position) self.waypoint1Cell.id = 2 self.waypoint2Cell.id = 2 self.waypoint1Cell.redoColor() self.waypoint2Cell.redoColor() path1 = self.greedyBFS(self.startCell, self.waypoint1Cell) path2 = self.greedyBFS(self.waypoint1Cell, self.waypoint2Cell) path3 = self.greedyBFS(self.waypoint2Cell, self.endCell) for cell in path1: path.append(cell) for cell in path2: path.append(cell) for cell in path3: path.append(cell) # Find the corresponding waypoint cells for cell in path: # Update the cell ids in the path cell.id = 2 cell.redoColor() self.path = path self.generateWall() self.createEnemy() self.createTower(self.walls[0], 0) def greedyBFS(self, startCell, endCell): evaluated = [] # HEAP # stores tuples # FIRST - fCost of the cell # SECOND - cell itself notEvaluated = [] # Keep this as a heap with heapify notEvaluatedCheck = [ ] # Array to make sure everything stays on the same page # Dictionary containing the path previousCells = [] # Calculate the euclidian distance to the cell startFCost = startCell.calcDistance(endCell) heapq.heappush(notEvaluated, (startFCost, startCell)) notEvaluatedCheck.append(startCell) while not len(notEvaluatedCheck) <= 0: heapq.heapify(notEvaluated) currentCell = heapq.heappop(notEvaluated)[1] notEvaluatedCheck.remove(currentCell) if currentCell.isSameLocation(endCell): # print("Found a path") return previousCells # print("Iterated") evaluated.append(currentCell) previousCells.append(currentCell) for neighbor in currentCell.neighbors: if neighbor in evaluated or neighbor is None: continue if not notEvaluatedCheck.__contains__(neighbor): neighborFCost = neighbor.calcDistance(endCell) heapq.heappush(notEvaluated, (neighborFCost, neighbor)) notEvaluatedCheck.append(neighbor) # print("Neighbor added" + str(len(notEvaluatedCheck))) print("\n\nPATH NOT FOUND\n\n") def printArrayCellContents(self): for cell in self.map: print("X: " + str(cell.x)) print("Y: " + str(cell.y)) print("width: " + str(cell.width)) print("height: " + str(cell.height)) print("id: " + str(cell.id)) print("color: " + str(cell.color)) # find all the neighbors of a cell # cell - the cell of which the neighbors need to be found # @return - an array of all the neighbors def findNeighbors(self): map = self.map for i in range(0, len(map)): neighbors = [] # Check the upper if i - self.widthNumber > 0: neighbors.append(map[i - self.widthNumber]) else: neighbors.append(None) # Check the lower if i + self.widthNumber < (self.widthNumber * self.heightNumber): neighbors.append(map[i + self.widthNumber]) else: neighbors.append(None) # Check the left if ((i) % self.widthNumber) is not 0 and i - 1 > 0: neighbors.append(map[i - 1]) else: neighbors.append(None) # Check the right if i + 1 < (self.widthNumber * self.heightNumber) and ( (i + 1) % self.widthNumber) is not 0: neighbors.append(map[i + 1]) else: neighbors.append(None) map[i].neighbors = neighbors # Designate a certain number of empty cells along the path as walls which can house buildings def generateWall(self): numberPath = [] self.walls = [] for cell in self.path: numberOfWalls = random.randint(0, 2) # Create between 0 and 2 walls numberPath.append(numberOfWalls) for i in range(0, len(self.path)): cell = self.path[i] neighbors = cell.neighbors limitWalls = numberPath[i] currentWalls = 0 for i in range(0, len(neighbors)): neighborCell = neighbors[i] if limitWalls <= currentWalls: if neighborCell is not None and not neighborCell.id is 2: neighborCell.id = 1 neighborCell.redoColor() self.walls.append(neighborCell) currentWalls = currentWalls + 1 # Update each enemy position based on where it is in the path def update(self, screen, deltaT): # Draw the hud on the screen self.hud.drawHUD(screen) for tower in self.towers: tower.drawTower(screen) # Update the tower and bullets tower.updateTower(screen, self.enemies, self.path, self.windowWidth, self.windowHeight, deltaT) for enemy in self.enemies: enemy.updatePosition() enemy.drawEnemy(screen) if enemy.goalReached: # The enemy has arrived at the goal self.enemies.remove(enemy) self.hud.baseHealth -= 1 # Find the closest cell based on a position entered def findCursorCell(self, position): maxDistance = 1000000000 xCur = position[0] - (self.windowWidth / self.widthNumber) / 2 yCur = position[1] - (self.windowHeight / self.heightNumber) / 2 closestCell = self.map[0] for i in range(0, len(self.map)): cell = self.map[i] x = (cell.x) y = (cell.y) # print("Mouse x: " + str(xCur)) # print("Mouse x: " + str(yCur)) # print("Cell x: " + str(x)) # print("Cell y: " + str(y)) distance = math.sqrt((x - xCur)**2 + (y - yCur)**2) # print("Distance: " + str(distance)) if (distance <= maxDistance): # print("Mouse x: " + str(xCur)) # print("Mouse x: " + str(yCur)) # print("Cell x: " + str(x)) # print("Cell y: " + str(y)) # print("distance: " + str(distance)) maxDistance = distance closestCell = cell return closestCell