""" roids.py ======== .. figure:: ../_static/roids.jpg :align: center Test for polygons. Asteroids style game demo using polygons. .. note:: This example requires the following modules: .. hlist:: :columns: 3 - `st7789py` - `tft_config` - `tft_buttons` """ import math import random import utime import tft_config import tft_buttons as Buttons import st7789py as st7789 tft = tft_config.config(tft_config.WIDE) buttons = Buttons.Buttons() def main(): ''' Game on! ''' class Poly(): ''' Poly class to keep track of a polygon based sprite ''' def __init__( self, # list (x,y) tuples of convex polygon, must be closed polygon, x=None, # x location of polygon y=None, # y location of polygon v_x=None, # velocity in x axis v_y=None, # velocity in y axis angle=None, # angle in radians polygon is facing spin=None, # spin in radians per frame_time scale=None, # scale factor for polygon radius=None, # radius of polygon for collision detection max_velocity=10, # max velocity of polygon counter=0): # scale the polygon if scale was given self.polygon = ( polygon if scale is None else [(int(scale*x[0]), int(scale*x[1])) for x in polygon]) # if no location given assign a random location self.x = random.randint(0, width) if x is None else x self.y = random.randint(0, width) if y is None else y # set angle if given self.angle = float(0) if angle is None else angle # set random spin unless one was given self.spin = random.randint(-3, 3) / 16 if spin is None else spin # set random velocity unless one was given self.velocity_x = random.uniform( 0.50, 0.99)*6-3 + 0.75 if v_x is None else v_x self.velocity_y = random.uniform( 0.50, 0.99)*6-3 + 0.75 if v_y is None else v_y # set radius, max_velocity and radius counter self.radius = radius self.max_velocity = max_velocity self.counter = counter def rotate(self, rad): ''' Rotate polygon in radians ''' self.angle += rad if self.angle > rad_max: self.angle = 0 elif self.angle < 0: self.angle = rad_max def move(self): ''' Rotate and move polygon velocity distance. ''' if self.spin != 0: self.rotate(self.spin) self.x += int(self.velocity_x) self.y += int(self.velocity_y) self.x %= width self.y %= height def draw(self, color): ''' Draw the polygon ''' tft.polygon(self.polygon, self.x, self.y, color, self.angle, 0, 0) def collision(self, poly): ''' Detect collisions using overlapping radiuses. Returns True on collision. ''' return abs( ((self.x - poly.x) * (self.x - poly.x) + (self.y - poly.y) * (self.y - poly.y)) < (self.radius + poly.radius) * (self.radius + poly.radius)) def create_roid(size, x=None, y=None, v_x=None, v_y=None): ''' Create a new roid with the given parameters. ''' return Poly( roid_poly, x=x, y=y, v_x=v_x, v_y=v_y, scale=roid_scale[size], radius=roid_radius[size], counter=size) def update_missiles(): ''' Update active missiles and handle asteroid hits. ''' for missile in list(missiles): # for each missile missile.draw(st7789.BLACK) # erase old missile if missile.counter > 0: # if counter > 0 missile is active missile.move() # update missile position for roid in list(roids): # for each roid if missile.collision(roid): # check if missile collides with roid roid.draw(st7789.BLACK) # erase the roid if roid.counter > 0: # if roid is not the smallest size roids.append( # add first smaller roid create_roid( roid.counter-1, x=roid.x, y=roid.y, v_x=roid.velocity_x, v_y=roid.velocity_y)) roids.append( # add second smaller roid create_roid( roid.counter-1, x=roid.x, y=roid.y, v_x=-roid.velocity_x, v_y=-roid.velocity_y)) roids.remove(roid) # remove the roid that was hit missile.counter = 0 if missile.counter > 0: # if the missile has life left missile.draw(st7789.WHITE) # draw missile missile.counter -= 1 # reduce missile life else: missiles.remove(missile) # remove exploded missile else: missiles.remove(missile) # remove expired missile def update_ship(): ''' Update ship velocity and limit to max_velocity ''' # apply drag to velocity of ship so it will eventually slow to a stop ship.velocity_x -= ship.velocity_x * ship_drag_frame ship.velocity_y -= ship.velocity_y * ship_drag_frame if ship.velocity_x > ship.max_velocity: # Limit velocity to +/- max_velocity ship.velocity_x = ship.max_velocity elif ship.velocity_x < -ship.max_velocity: ship.velocity_x = -ship.max_velocity if ship.velocity_y > ship.max_velocity: ship.velocity_y = ship.max_velocity elif ship.velocity_y < -ship.max_velocity: ship.velocity_y = -ship.max_velocity if abs(ship.velocity_x) < 0.1: # if ship is moving very slowly, stop it ship.velocity_x = 0.0 if abs(ship.velocity_y) < 0.1: ship.velocity_y = 0.0 ship.move() # move the ship and draw it ship.draw(st7789.WHITE) def update_roids(): ''' Update roid positions handle ship collisions Returns True if not hit, False if hit ''' not_hit = True for roid in roids: # for each roid, erase, move then draw roid.draw(st7789.BLACK) roid.move() roid.draw(st7789.WHITE) if roid.collision(ship): # check for roid/ship collision ship.draw(st7789.BLACK) # erase ship ship.velocity_x = 0.0 # stop movement ship.velocity_y = 0.0 not_hit = False return not_hit def explode_ship(): ''' Increment explosion step and alternate between drawing explosion poly and explosion poly rotated 45 degrees Returns True when explosion is finished ''' ship.counter += 1 if ship.counter % 2 == 0: tft.polygon(explosion_poly, ship.x, ship.y, st7789.BLACK, 0.785398) tft.polygon(explosion_poly, ship.x, ship.y, st7789.WHITE) else: tft.polygon(explosion_poly, ship.x, ship.y, st7789.WHITE, 0.785398) tft.polygon(explosion_poly, ship.x, ship.y, st7789.BLACK) if ship.counter > 25: # erase explosion, move ship to center and stop explosion tft.polygon(explosion_poly, ship.x, ship.y, st7789.BLACK) # move ship to center ship.x = width >> 1 ship.y = height >> 1 ship.counter = 0 return True return False # enable display and clear screen tft.fill(st7789.BLACK) width = tft.width height = tft.height rad_max = 2 * math.pi # 360' in radians # ship variables ship_alive = True ship_radius = 7 ship_rad_frame = rad_max / 16 # turning rate per frame ship_accel_frame = 0.6 # acceleration per frame ship_drag_frame = 0.015 # drag factor per frame ship_poly = [(-7, -7), (7, 0), (-7, 7), (-3, 0), (-7, -7)] ship = Poly( ship_poly, x=width >> 1, y=height >> 1, v_x=0, v_y=0, radius=ship_radius, spin=0.0) explosion_poly = [(-4, -4), (-4, 4), (4, 4), (4, -4), (-4, -4)] # asteroid variables roid_radius = [5, 10, 16] roid_scale = [0.33, 0.66, 1.0] roid_poly = [ (-5, -15), (-2, -13), (11, -14), (15, -7), (14, 0), (16, 5), (11, 16), (7, 16), (-7, 14), (-14, 7), (-13, 1), (-14, -8), (-11, -15), (-5, -15)] roids = [] # missile variables missile_velocity = 8 missile_max = 8 missile_life = 20 missile_rate = 200 missile_last = utime.ticks_ms() missile_poly = [(-1, -1), (1, -1), (1, 1), (-1, 1), (-1, -1)] missiles = [] frame_time = 60 # target frame rate delay # game loop while True: last_frame = utime.ticks_ms() # add roids if there are none if len(roids) == 0: roids = [create_roid(2), create_roid(2)] update_missiles() # Erase the ship ship.draw(st7789.BLACK) if ship_alive: # if left button pressed if buttons.left and buttons.left.value() == 0: # rotate ship counter clockwise ship.rotate(-ship_rad_frame) # if right button pressed if buttons.right and buttons.right.value() == 0: # rotate ship clockwise ship.rotate(ship_rad_frame) # if hyperspace button pressed move ship to random location if buttons.hyper and buttons.hyper.value() == 0: diameter = ship.radius * 2 ship.x = random.randint(diameter, width - diameter) ship.y = random.randint(diameter, height - diameter) # stop movement ship.velocity_x = 0.0 ship.velocity_y = 0.0 # if thrust button pressed if buttons.thrust and buttons.thrust.value() == 0: # accelerate ship in the direction the ship is facing d_y = math.sin(ship.angle) * ship_accel_frame d_x = math.cos(ship.angle) * ship_accel_frame ship.velocity_x += d_x ship.velocity_y += d_y # if the fire button is pressed and less than missile_max active missles if buttons.fire and buttons.fire.value() == 0 and len(missiles) < missile_max: # limit missiles firing to once every missile_rate ms if last_frame - missile_last > missile_rate: # fire missile in direction ship in facing v_y = math.sin(ship.angle) * missile_velocity v_x = math.cos(ship.angle) * missile_velocity # create new missile missile = Poly( missile_poly, x=ship.x, y=ship.y, v_x=v_x, v_y=v_y, angle=ship.angle, radius=1, spin=0.0, counter=missile_life) # add to to missile list and save last fire time missiles.append(missile) missile_last = last_frame update_ship() else: # explosion animation until returns True ship_alive = explode_ship() # update roids and return if ship was not hit not_hit = update_roids() if ship_alive: ship_alive = not_hit # wait until frame time expires while utime.ticks_ms() - last_frame < frame_time: pass main()