Toggle Navigation
Hatchery
Eggs
Gravitris
__init__.py
Users
Badges
Login
Register
MCH2022 badge?
go to mch2022.badge.team
__init__.py
raw
Content
""" ------------------------- Gravitris by @royrobotiks ------------------------- This game is based on Tetris on the Card10 by Peace-Maker [@jhartung10} and uses BMP picture viewer code by [deurknop] """ import buttons import color import display import leds import math import urandom import utime import vibra import bhi160 import os import struct urandom.seed(utime.time_ms()) #-------------------- gravity assist sensor = 0 sensors = [{"sensor": bhi160.BHI160Orientation(sample_rate=8), "name": "Orientation"}] #-------------------- tetris variables # 160x80 display. we use it vertically. # 10 tetriminos per row -> 80 / 10 = 8 pixels per tetrimino TETRIMINO_SIZE = 10 FIELD_WIDTH = 8#8 FIELD_HEIGHT = 16 # Move the tetrimino down every x ticks. TETRIMINO_MOVE_DOWN_INTERVAL = 35 MAX_LEVEL = 30 LEVEL_SPEEDUP_STEP = 1 TETRIMINO_NONE = 0 TETRIMINO_SQUARE = 1 TETRIMINO_T = 2 TETRIMINO_Z = 3 TETRIMINO_S = 4 TETRIMINO_J = 5 TETRIMINO_L = 6 TETRIMINO_I = 7 NUMBER_OF_TERIMINOS = 7 TETRIMINO_COLORS = [ (60, 60, 60), # None (255, 255, 0), # Square (153, 0, 255), # T (255, 0, 0), # Z (0, 255, 0), # S (0, 0, 255), # J (255, 165, 0), # L (0, 255, 255), # I ] HILIGHT_COLORS = [ (60, 60, 60), # None (128,128, 0), # Square (75, 0, 128), # T (128, 0, 0), # Z (0, 128, 0), # S (0, 0, 128), # J (128, 80, 0), # L (0, 128, 128), # I ] ROTATION_0 = 0 ROTATION_90 = 1 ROTATION_180 = 2 ROTATION_270 = 3 TETRIMINO_GEOMETRY = [ (), # None ( # Square # == # == ( # The same in all rotations.. (1, 1), (1, 1), ), ( (1, 1), (1, 1), ), ( (1, 1), (1, 1), ), ( (1, 1), (1, 1), ), ), ( # T # === # = ( # ROTATION_0 (1, 1, 1), (0, 1, 0), ), # = # == # = ( # ROTATION_90 (0, 1), (1, 1), (0, 1), ), # = # === ( # ROTATION_180 (0, 1, 0), (1, 1, 1), ), # = # == # = ( # ROTATION_270 (1, 0), (1, 1), (1, 0), ), ), ( # Z # == # == ( # ROTATION_0 (1, 1, 0), (0, 1, 1), ), # = # == # = ( # ROTATION_90 (0, 1), (1, 1), (1, 0), ), # == # == ( # ROTATION_180 (1, 1, 0), (0, 1, 1), ), # = # == # = ( # ROTATION_270 (0, 1), (1, 1), (1, 0), ), ), ( # S # == # == ( # ROTATION_0 (0, 1, 1), (1, 1, 0), ), # = # == # = ( # ROTATION_90 (1, 0), (1, 1), (0, 1), ), # == # == ( # ROTATION_180 (0, 1, 1), (1, 1, 0), ), # = # == # = ( # ROTATION_270 (1, 0), (1, 1), (0, 1), ), ), ( # J # == # = # = ( # ROTATION_0 (1, 1), (1, 0), (1, 0), ), # === # = ( # ROTATION_90 (1, 1, 1), (0, 0, 1), ), # = # = # == ( # ROTATION_180 (0, 1), (0, 1), (1, 1), ), # = # === ( # ROTATION_270 (1, 0, 0), (1, 1, 1), ), ), ( # L # == # = # = ( # ROTATION_0 (1, 1), (0, 1), (0, 1), ), # = # === ( # ROTATION_90 (0, 0, 1), (1, 1, 1), ), # = # = # == ( # ROTATION_180 (1, 0), (1, 0), (1, 1), ), # === # = ( # ROTATION_270 (1, 1, 1), (1, 0, 0), ), ), ( # I # = # = # = # = ( # ROTATION_0 [1], [1], [1], [1], ), # ==== ( # ROTATION_90 (1, 1, 1, 1), ), # = # = # = # = ( # ROTATION_180 [1], [1], [1], [1], ), # ==== ( # ROTATION_270 (1, 1, 1, 1), ), ), ] #-------------------------------------------------------------- image player code def draw_bmp(disp, filename, x_offset, transparency): with open(filename, "rb+") as bmpfile: header, filesize, _, _, data_offset = struct.unpack("<HIHHI", bmpfile.read(14)) if header != 0x4D42: raise ValueError("Only Windows BMP supported") bmpinfo = bmpfile.read(data_offset - 14) width, height, _, bpp, compr = struct.unpack("<IIHHI", bmpinfo[4:20]) if bpp != 24: raise ValueError("Only 24 bpp BMP supported") if compr != 0: raise ValueError("CompressValueError not supported") pad = (width * -3) % 4 if width > 161: raise ValueError("Maximum width 161 supported") if height > 81: raise ValueError("Maximum height 81 supported") data = bmpfile.read(512) i = 2 for y in range(height - 1, -1, -1): for x in range(width): if i >= len(data): i = 2 + (i - len(data)) data = data[-2:] + bmpfile.read(512) if (transparency==False or data[i]<255 or data[i-1]<255 or data[i-2]<255): disp.pixel(x+x_offset, y, col=(data[i], data[i - 1], data[i - 2])) i += 3 i += pad # skip padding disp.update() #-------------------------------------------------------------- tetris code def get_tetrimino_minmax(typ, rotation): if typ == TETRIMINO_SQUARE: return {'x': 2, 'y': 2} elif typ in [TETRIMINO_T, TETRIMINO_S, TETRIMINO_Z]: if rotation in [ROTATION_0, ROTATION_180]: return {'x': 3, 'y': 2} else: return {'x': 2, 'y': 3} elif typ in [TETRIMINO_L, TETRIMINO_J]: if rotation in [ROTATION_0, ROTATION_180]: return {'x': 2, 'y': 3} else: return {'x': 3, 'y': 2} elif typ == TETRIMINO_I: if rotation in [ROTATION_0, ROTATION_180]: return {'x': 1, 'y': 4} else: return {'x': 4, 'y': 1} return {'x': 0, 'y': 0} def get_tetrimino_default_rotation(typ): if typ in [TETRIMINO_SQUARE, TETRIMINO_S, TETRIMINO_Z]: return ROTATION_0 elif typ in [TETRIMINO_L, TETRIMINO_I]: return ROTATION_90 elif typ == TETRIMINO_T: return ROTATION_180 elif typ == TETRIMINO_J: return ROTATION_270 class GameField: def __init__(self): self.gamefield = [[TETRIMINO_NONE] * FIELD_WIDTH for _ in range(FIELD_HEIGHT)] def field(self, x, y): return self.gamefield[y][x] def reset(self): for y in range(FIELD_HEIGHT): for x in range(FIELD_WIDTH): self.gamefield[y][x] = TETRIMINO_NONE def put_tetrimino(self, typ, rotation, position): shape = TETRIMINO_GEOMETRY[typ][rotation] for y in range(len(shape)): for x in range(len(shape[y])): # print(x, y, shape[y][x], y + position['y'], x + position['x']) if shape[y][x] == 1: # Draw it like it's shown in the defining code above. Not upside down. self.gamefield[((len(shape)-1) - y) + position['y']][x + position['x']] = typ def collides_with(self, other): for y in range(FIELD_HEIGHT): for x in range(FIELD_WIDTH): if self.gamefield[y][x] != TETRIMINO_NONE and other.gamefield[y][x] != TETRIMINO_NONE: return True return False def remove_row(self, row): self.gamefield = self.gamefield[:row] + self.gamefield[row+1:] self.gamefield.append([TETRIMINO_NONE] * FIELD_WIDTH) class Tetris: def __init__(self): self.new_game() def new_game(self): self.tick = 0 self.gameover = False self.down_interval = TETRIMINO_MOVE_DOWN_INTERVAL # Do we need to redraw the gamefield? self.dirty = True # gamefield[y][x] self.gamefield = GameField() self.temp_gamefield = GameField() self.current_tetrimino = 0 self.select_random_tetrimino() self.desired_direction = 0 # left = -1, none = 0, right = 1 self.desire_rotation = False self.rotation_button_pressed = False # Scoring self.lines_cleared = 0 self.score = 0 self.level = 1 self.combo = 0 # Reset the leds. self.update_leds() #gravity self.xg=0 self.yg=0 self.tetrimino_move_x_interval = 20 self.lock_xg = False #lock the x-movement by pushing the bottom left button self.gravity = 0 self.last_drop_tick=0 # remembers at which tick the tetrimino dropped the last time self.last_sideways_tick =0 # remembers at which tick the tetrimino was moved l/r the last time # rotation interface self.rotation_direction = 0 self.p_top_right = 0 self.top_right = 0 self.p_bottom_right = 0 self.bottom_right = 0 def loop(self): # ------------------------------------------------------------------------------------------------ splash screen with display.open() as disp: color_index=0 for y in range(0,79,10): color_index+=1 color_index=(color_index%7)+1 disp.rect(0, y, 159, y+10, col=TETRIMINO_COLORS[color_index], filled=True) draw_bmp(disp, "apps/gravitris/splashscreen.bmp", 0, False) # display splash screen vibra.vibrate(100) # Start a new game on button press. b = 0 while b == 0: b = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT) utime.sleep_ms(50) vibra.vibrate(300) while True: # ---------------------------------------------------------------------------------------- main loop while not self.gameover: self.think(disp) utime.sleep_ms(10) # ---------------------------------------------------------------------------------------- Game Over vibra.vibrate(500) draw_bmp(disp, "apps/gravitris/gameover.bmp",74, True) # display game over at x80 with transparent bgnd vibra.vibrate(300) # Start a new game on button press. b = 0 while b == 0: b = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT) utime.sleep_ms(50) self.new_game() def select_random_tetrimino(self): # randint doesn't seem to obey the range all the time. self.current_tetrimino = 0 while self.current_tetrimino <= 0 or self.current_tetrimino > NUMBER_OF_TERIMINOS: self.current_tetrimino = urandom.randint(1, NUMBER_OF_TERIMINOS) self.current_rotation = get_tetrimino_default_rotation(self.current_tetrimino) self.cursor = {} minmax = get_tetrimino_minmax(self.current_tetrimino, self.current_rotation) self.cursor['x'] = (FIELD_WIDTH // 2) - (minmax['x'] - 1) // 2 self.cursor['y'] = FIELD_HEIGHT - minmax['y'] def move_current_tetrimino_leftright(self): ##################move temp_cursor = {} temp_cursor['x'] = self.cursor['x'] temp_cursor['y'] = self.cursor['y'] # Player wants to move left. if self.desired_direction == -1: temp_cursor['x'] -= 1 else: temp_cursor['x'] += 1 # Player has to press the button again in the next time window. self.desired_direction = 0 # Still inside the gamefield? if temp_cursor['x'] < 0: return minmax = get_tetrimino_minmax(self.current_tetrimino, self.current_rotation) if temp_cursor['x'] + minmax['x'] - 1 >= FIELD_WIDTH: return # Something in the way? self.temp_gamefield.reset() self.temp_gamefield.put_tetrimino(self.current_tetrimino, self.current_rotation, temp_cursor) if self.temp_gamefield.collides_with(self.gamefield): return self.cursor['x'] = temp_cursor['x'] self.dirty = True def rotate_tetrimino(self): temp_rotation = (self.current_rotation + self.rotation_direction) % 4 temp_cursor = {} temp_cursor['x'] = self.cursor['x'] temp_cursor['y'] = self.cursor['y'] # Keep the cursor centered below the tetrimino. # based on the initial cursor position using minmax of tetrimino: # # Keep the center the same visually. # The tetrimino shape is always drawn in it's full 4x4 box, # so move the cursor in such a way, that the center # appears in the same square. # Like # =x= # c=o # # Becomes after rotation: # o=o # =xo # c=o # So move the cursor one up. if self.current_tetrimino == TETRIMINO_T: # Center: # =c= # = if temp_rotation == ROTATION_0: temp_cursor['x'] -= 1 elif temp_rotation == ROTATION_180: temp_cursor['y'] += 1 elif temp_rotation == ROTATION_270: temp_cursor['x'] += 1 temp_cursor['y'] -= 1 # == # c= elif self.current_tetrimino in [TETRIMINO_S, TETRIMINO_Z]: if temp_rotation in [ROTATION_0, ROTATION_180]: temp_cursor['x'] -= 1 temp_cursor['y'] += 1 elif temp_rotation in [ROTATION_90, ROTATION_270]: temp_cursor['x'] += 1 temp_cursor['y'] -= 1 # == # c # = if self.current_tetrimino == TETRIMINO_J: if temp_rotation == ROTATION_0: temp_cursor['x'] += 1 temp_cursor['y'] -= 1 elif temp_rotation == ROTATION_90: temp_cursor['x'] -= 1 elif temp_rotation == ROTATION_270: temp_cursor['y'] += 1 # == # c # = if self.current_tetrimino == TETRIMINO_L: if temp_rotation == ROTATION_90: temp_cursor['y'] += 1 elif temp_rotation == ROTATION_180: temp_cursor['x'] += 1 temp_cursor['y'] -= 1 elif temp_rotation == ROTATION_270: temp_cursor['x'] -= 1 # = = = = # c elif self.current_tetrimino == TETRIMINO_I: # o=oo # o=oo # o=oo # o=oo if temp_rotation == ROTATION_0: temp_cursor['x'] += 1 temp_cursor['y'] -= 1 # oooo # ==== # oooo # oooo elif temp_rotation == ROTATION_90: temp_cursor['x'] -= 1 temp_cursor['y'] += 2 # oo=o # oo=o # oo=o # oo=o elif temp_rotation == ROTATION_180: temp_cursor['x'] += 2 temp_cursor['y'] -= 2 # oooo # oooo # ==== # oooo elif temp_rotation == ROTATION_270: temp_cursor['x'] -= 2 temp_cursor['y'] += 1 # Out of bounds on the left. if temp_cursor['x'] < 0: return False # Out of bounds on the bottom. if temp_cursor['y'] < 0: return False # Check if there is enough space to rotate here. minmax = get_tetrimino_minmax(self.current_tetrimino, temp_rotation) # Out of bounds on the right. if temp_cursor['x'] + minmax['x'] - 1 >= FIELD_WIDTH: return False # Out of bounds on the top. if temp_cursor['y'] + minmax['y'] - 1 >= FIELD_HEIGHT: return False self.temp_gamefield.reset() self.temp_gamefield.put_tetrimino(self.current_tetrimino, temp_rotation, temp_cursor) # There's something in the way. if self.temp_gamefield.collides_with(self.gamefield): return False # Rotation successful. self.current_rotation = temp_rotation self.cursor['x'] = temp_cursor['x'] self.cursor['y'] = temp_cursor['y'] self.dirty = True return True def move_current_tetrimino_down(self): temp_cursor = {} temp_cursor['x'] = self.cursor['x'] temp_cursor['y'] = self.cursor['y'] - 1 self.temp_gamefield.reset() # We're on the ground already. Stay here now. if (temp_cursor['y'] < 0): self.save_current_tetrimino() # Is there space for the new terimino? self.temp_gamefield.reset() self.temp_gamefield.put_tetrimino(self.current_tetrimino, self.current_rotation, self.cursor) if self.temp_gamefield.collides_with(self.gamefield): self.gameover = True # Nope. self.dirty = True return False else: # Is there enough space below? self.temp_gamefield.put_tetrimino(self.current_tetrimino, self.current_rotation, temp_cursor) if self.temp_gamefield.collides_with(self.gamefield): # No. Save it here. self.save_current_tetrimino() # Is there space for the new terimino? self.temp_gamefield.reset() self.temp_gamefield.put_tetrimino(self.current_tetrimino, self.current_rotation, self.cursor) if self.temp_gamefield.collides_with(self.gamefield): self.gameover = True # Nope. self.dirty = True return False else: # Yes, there is. Move the object down for real. self.cursor['y'] -= 1 self.dirty = True return True def save_current_tetrimino(self): # Just assume there is space. self.gamefield.put_tetrimino(self.current_tetrimino, self.current_rotation, self.cursor) self.dirty = True lines_cleared = self.remove_full_lines() # TODO: Display score # Handle combos if self.combo > 0 and lines_cleared == 0: self.score += self.combo * 50 * self.level self.combo = 0 elif lines_cleared > 0: self.combo += 1 # Handle scoring if lines_cleared == 1: self.score += 100 * self.level self.lines_cleared += 1 elif lines_cleared == 2: self.score += 300 * self.level self.lines_cleared += 3 elif lines_cleared == 3: self.score += 500 * self.level self.lines_cleared += 5 elif lines_cleared == 4: self.score += 800 * self.level self.lines_cleared += 8 # Speed up the game each level. while (self.level * 5) <= self.lines_cleared: self.level += 1 if self.level < MAX_LEVEL: self.down_interval -= LEVEL_SPEEDUP_STEP # Show the current level on the leds. self.update_leds() # Give some feedback, that tetrimino is down. vibra.vibrate(lines_cleared * 15 + 10) self.select_random_tetrimino() def update_leds(self): #............................................................................................LEDs for i in range(11): j = 10 - i a=self.lines_cleared b=(1 << i) if a&b>0: if (j+1)%4!=0: leds.prep(j,(200,0,0)) else: leds.prep(j,color.RED) else: if (j+1)%4!=0: leds.prep(j,color.CHAOSBLUE_DARK) else: leds.prep(j,color.CHAOSBLUE) print ('--------------------------') leds.update() def remove_full_lines(self): removed_lines = 0 # Check every row. y = 0 while y < FIELD_HEIGHT: for x in range(FIELD_WIDTH): # Skip the line if there is a blank square in it. if self.gamefield.field(x, y) == TETRIMINO_NONE: break # We're not at the end of line yet. if x != (FIELD_WIDTH - 1): continue removed_lines += 1 self.gamefield.remove_row(y) # Check this row again, since we moved it all one down. y -= 1 y += 1 return removed_lines def think(self, disp): self.tick += 1 #---------------------------------------- gravity assist: read orientation sensor samples = sensors[sensor]["sensor"].read() if len(samples) > 0: # get orientation sensor samples sample = samples[0] self.xg=-sample.y #left right self.yg= sample.z-20 #up down self.desired_direction = 0 if self.xg>7: # Move right when tilted more than 7° self.desired_direction = 1 if self.xg<-7: # Move left when tilted more than 7° self.desired_direction = -1 # left / right moving speed is based on tilt angle intermediate_interval = (abs(self.xg)-7)//2 if (intermediate_interval<0): intermediate_interval=0 intermediate_interval=20-intermediate_interval if (intermediate_interval<0): intermediate_interval=0 self.tetrimino_move_x_interval = intermediate_interval # drop speed is based on tilt angle self.gravity=-self.yg//2 #----------------------------------------- Button interface # remember button presses of previous cycle self.p_top_right=self.top_right self.p_bottom_right=self.bottom_right self.top_right=0 self.bottom_right=0 self.lock_xg = False # check current button presses current_presses = buttons.read(buttons.TOP_RIGHT | buttons.BOTTOM_RIGHT | buttons.BOTTOM_LEFT) if current_presses > 0: # Drop? if (current_presses & buttons.BOTTOM_LEFT) == buttons.BOTTOM_LEFT: self.move_current_tetrimino_down() self.lock_xg = True #lock left/right movement # Player wants to rotate? if (current_presses & buttons.TOP_RIGHT) == buttons.TOP_RIGHT: self.top_right=1 # Player wants to rotate? if (current_presses & buttons.BOTTOM_RIGHT) == buttons.BOTTOM_RIGHT: self.bottom_right=1 # Tetrimino rotation self.rotation_direction = 0 if (self.top_right==1 and self.p_top_right==0 and self.bottom_right==0): self.rotation_direction = -1 self.rotate_tetrimino() if (self.bottom_right==1 and self.p_bottom_right==0 and self.top_right==0): self.rotation_direction = 1 self.rotate_tetrimino() # Sideways: Actuate the controls every x ticks. if self.tick>self.last_sideways_tick + self.tetrimino_move_x_interval: self.last_sideways_tick=self.tick if self.desired_direction != 0 and self.lock_xg==False: self.move_current_tetrimino_leftright() # Falling: The tetrimino falls slower than it can move sideways or rotate. intermediate_down=self.down_interval+self.gravity if (intermediate_down<0): intermediate_down=0 if(self.tick>self.last_drop_tick+intermediate_down): self.last_drop_tick=self.tick self.move_current_tetrimino_down() # Check if we even have to redraw the gamefield. if not self.dirty: return # Clear the screen first if something changed. disp.clear() #draw background disp.rect(0,0,159,79,col=HILIGHT_COLORS[(self.level%7)+1],filled=False) # Render all the changes. self.draw_gamefield(disp) #if self.gameover: # print('cursor: {} {}'.format(self.cursor['y'], self.cursor['x'])) # disp.circ(self.cursor['y']*TETRIMINO_SIZE, self.cursor['x']*TETRIMINO_SIZE, 4, col=(255,255,255), filled=True) disp.update() self.dirty = False def draw_gamefield(self, disp): # print('Drawing game field') self.temp_gamefield.reset() self.temp_gamefield.put_tetrimino(self.current_tetrimino, self.current_rotation, self.cursor) for y in range(FIELD_HEIGHT): draw_y = y * TETRIMINO_SIZE #if y%2 != 0: #gr =(64,64,64) #disp.rect(draw_y, 1, draw_y + TETRIMINO_SIZE-1,100, col=gr, filled=True) #draw column #rect(100, 20, 50, 70, *, col=None, filled=True, size=1) for x in range(FIELD_WIDTH): draw_x = x * TETRIMINO_SIZE tetrimino_type = self.gamefield.field(x, y) if tetrimino_type != TETRIMINO_NONE: # print('Drawing ({} {}) to ({} {})'.format(draw_y, draw_x, draw_y + TETRIMINO_SIZE, draw_x + TETRIMINO_SIZE)) disp.rect(draw_y, draw_x, draw_y + TETRIMINO_SIZE-1, draw_x + TETRIMINO_SIZE-1, col=HILIGHT_COLORS[tetrimino_type], filled=True) disp.rect(draw_y+1, draw_x+1, draw_y + TETRIMINO_SIZE-1, draw_x + TETRIMINO_SIZE-1, col=TETRIMINO_COLORS[tetrimino_type], filled=True) else: tetrimino_type = self.temp_gamefield.field(x, y) if tetrimino_type != TETRIMINO_NONE: # print('Drawing current ({} {}) to ({} {})'.format(draw_y, draw_x, draw_y + TETRIMINO_SIZE, draw_x + TETRIMINO_SIZE)) disp.rect(draw_y, draw_x, draw_y + TETRIMINO_SIZE-1, draw_x + TETRIMINO_SIZE-1, col=HILIGHT_COLORS[tetrimino_type], filled=True) disp.rect(draw_y+1, draw_x+1, draw_y + TETRIMINO_SIZE-1, draw_x + TETRIMINO_SIZE-1, col=TETRIMINO_COLORS[tetrimino_type], filled=True) #disp.print("gr: %i" % (self.lines_cleared), posx=25, posy=58, font=2) #disp.print("yg: %f" % self.yg, posx=25, posy=58, font=2) tetris = Tetris() tetris.loop()