import math
import sys
import base
import pygame
from pygame.constants import K_w, K_s
from utils.vec2d import vec2d
class Block(pygame.sprite.Sprite):
def __init__(self, pos_init, speed, SCREEN_WIDTH, SCREEN_HEIGHT):
pygame.sprite.Sprite.__init__(self)
self.pos = vec2d(pos_init)
self.width = int(SCREEN_WIDTH*0.1)
self.height = int(SCREEN_HEIGHT*0.2)
self.speed = speed
self.SCREEN_WIDTH = SCREEN_WIDTH
self.SCREEN_HEIGHT = SCREEN_HEIGHT
image = pygame.Surface((self.width, self.height))
image.fill((0, 0, 0, 0))
image.set_colorkey((0,0,0))
pygame.draw.rect(
image,
(120, 240, 80),
(0, 0, self.width, self.height),
0
)
self.image = image
self.rect = self.image.get_rect()
self.rect.center = pos_init
def update(self, dt):
self.pos.x -= self.speed*dt
self.rect.center = (self.pos.x, self.pos.y)
class HelicopterPlayer(pygame.sprite.Sprite):
def __init__(self, speed, SCREEN_WIDTH, SCREEN_HEIGHT):
pygame.sprite.Sprite.__init__(self)
pos_init = ( int( SCREEN_WIDTH*0.35 ), SCREEN_HEIGHT/2 )
self.pos = vec2d(pos_init)
self.speed = speed
self.climb_speed = speed*-0.875 #-0.0175
self.fall_speed = speed*0.09 #0.0019
self.momentum = 0
self.width = SCREEN_WIDTH*0.05
self.height = SCREEN_HEIGHT*0.05
image = pygame.Surface((self.width, self.height))
image.fill((0, 0, 0, 0))
image.set_colorkey((0,0,0))
pygame.draw.rect(
image,
(255, 255, 255),
(0, 0, self.width, self.height),
0
)
self.image = image
self.rect = self.image.get_rect()
self.rect.center = pos_init
def update(self, is_climbing, dt):
self.momentum += (self.climb_speed if is_climbing else self.fall_speed)*dt
self.momentum *= 0.99
self.pos.y += self.momentum
self.rect.center = (self.pos.x, self.pos.y)
class Terrain(pygame.sprite.Sprite):
def __init__(self, pos_init, speed, SCREEN_WIDTH, SCREEN_HEIGHT):
pygame.sprite.Sprite.__init__(self)
self.pos = vec2d(pos_init)
self.speed = speed
self.width = int(SCREEN_WIDTH*0.1)
image = pygame.Surface((self.width, SCREEN_HEIGHT*1.5))
image.fill((0, 0, 0, 0))
image.set_colorkey((0,0,0))
color = (120, 240, 80)
#top rect
pygame.draw.rect(
image,
color,
(0, 0, self.width, SCREEN_HEIGHT*0.5),
0
)
#bot rect
pygame.draw.rect(
image,
color,
(0, SCREEN_HEIGHT*1.05, self.width, SCREEN_HEIGHT*0.5),
0
)
self.image = image
self.rect = self.image.get_rect()
self.rect.center = pos_init
def update(self, dt):
self.pos.x -= self.speed*dt
self.rect.center = (self.pos.x, self.pos.y)
[docs]class Pixelcopter(base.Game):
"""
Parameters
----------
width : int
Screen width.
height : int
Screen height, recommended to be same dimension as width.
"""
def __init__(self, width=48, height=48):
actions = {
"up": K_w
}
base.Game.__init__(self, width, height, actions=actions)
self.is_climbing = False
self.speed = 0.0004*width
def _handle_player_events(self):
self.is_climbing = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
key = event.key
if key == self.actions['up']:
self.is_climbing = True
[docs] def getGameState(self):
"""
Gets a non-visual state representation of the game.
Returns
-------
dict
* player y position.
* player velocity.
* player distance to floor.
* player distance to ceiling.
* next block x distance to player.
* next blocks top y location,
* next blocks bottom y location.
See code for structure.
"""
min_dist = 999
min_block = None
for b in self.block_group: #Groups do not return in order
dist_to = b.pos.x - self.player.pos.x
if dist_to > 0 and dist_to < min_dist:
min_block = b
min_dist = dist_to
current_terrain = pygame.sprite.spritecollide(self.player, self.terrain_group, False)[0]
state = {
"player_y": self.player.pos.y,
"player_vel": self.player.momentum,
"player_dist_to_ceil": self.player.pos.y-(current_terrain.pos.y-self.height*0.25),
"player_dist_to_floor": (current_terrain.pos.y+self.height*0.25)-self.player.pos.y,
"next_gate_dist_to_player": min_dist,
"next_gate_block_top": min_block.pos.y,
"next_gate_block_bottom": min_block.pos.y+min_block.height
}
return state
def getScreenDims(self):
return self.screen_dim
def getActions(self):
return self.actions.values()
def getScore(self):
return self.score
def game_over(self):
return self.lives <= 0.0
def init(self):
self.score = 0.0
self.lives = 1.0
self.player = HelicopterPlayer(
self.speed,
self.width,
self.height
)
self.player_group = pygame.sprite.Group()
self.player_group.add(self.player)
self.block_group = pygame.sprite.Group()
self._add_blocks()
self.terrain_group = pygame.sprite.Group()
self._add_terrain(0, self.width*4)
def _add_terrain(self, start, end):
w = int(self.width*0.1)
steps = range(start+(w/2), end+(w/2), w) #each block takes up 10 units.
y_jitter = []
freq = 4.5/self.width + self.rng.uniform(-0.01, 0.01)
for step in steps:
jitter = (self.height*0.125)*math.sin( freq*step + self.rng.uniform(0.0, 0.5) )
y_jitter.append(jitter)
y_pos = [ int( (self.height/2.0)+y_jit ) for y_jit in y_jitter ]
for i in range(0, len(steps)):
self.terrain_group.add(Terrain(
(steps[i], y_pos[i]),
self.speed,
self.width,
self.height
)
)
def _add_blocks(self):
x_pos = self.rng.randint(self.width, int(self.width*1.5))
y_pos = self.rng.randint(
int(self.height*0.25),
int(self.height*0.75)
)
self.block_group.add(
Block(
(x_pos, y_pos),
self.speed,
self.width,
self.height
)
)
def reset(self):
self.init()
def step(self, dt):
self.screen.fill((0,0,0))
self._handle_player_events()
self.score += self.rewards["tick"]
self.player.update(self.is_climbing, dt)
self.block_group.update(dt)
self.terrain_group.update(dt)
hits = pygame.sprite.spritecollide(self.player, self.block_group, False)
for creep in hits:
self.lives -= 1
hits = pygame.sprite.spritecollide(self.player, self.terrain_group, False)
for t in hits:
if self.player.pos.y-self.player.height <= t.pos.y-self.height*0.25:
self.lives -= 1
if self.player.pos.y >= t.pos.y+self.height*0.25:
self.lives -= 1
for b in self.block_group:
if b.pos.x <= self.player.pos.x and len(self.block_group) == 1:
self.score += self.rewards["positive"]
self._add_blocks()
if b.pos.x <= -b.width:
b.kill()
for t in self.terrain_group:
if t.pos.x <= -t.width:
self.score += self.rewards["positive"]
t.kill()
if self.player.pos.y < self.height*0.125: #its above
self.lives -= 1
if self.player.pos.y > self.height*0.875: #its below the lowest possible block
self.lives -= 1
if len(self.terrain_group) <= (10+3): #10% per terrain, offset of ~2 with 1 extra
self._add_terrain(self.width, self.width*5)
if self.lives <= 0.0:
self.score += self.rewards["loss"]
self.player_group.draw(self.screen)
self.block_group.draw(self.screen)
self.terrain_group.draw(self.screen)
if __name__ == "__main__":
import numpy as np
pygame.init()
game = Pixelcopter(width=256, height=256)
game.screen = pygame.display.set_mode( game.getScreenDims(), 0, 32)
game.clock = pygame.time.Clock()
game.rng = np.random.RandomState(24)
game.init()
while True:
if game.game_over():
game.reset()
dt = game.clock.tick_busy_loop(30)
game.step(dt)
pygame.display.update()