def newShadow(piece): global shadow_piece shadow = Piece(clock, piece=piece) shadow_sprites.add(shadow.getSprites()) shadowMove(shadow, hard_drop_key) shadow_piece = shadow
def main(): global falling_piece global shadow_piece global falling_sprites running = True new_piece_pending = False new_piece_pending_elapsed = 0 # ms # # Text displays # level = Level.startLevel() level_surf = None total_elapsed = 0 zero_time = datetime.timedelta(seconds=0) time_surf = None points = 0 point_surf = None if display_level: lines = Level.lines(level) else: lines = start_lines # from Settings lines_surf = None if display_level: level_surf = createText(level_x, level_y, 'Level: ') updateText(level_surf, level) if display_time: time_surf = createText(time_x, time_y) updateText(time_surf, timeObj(time_limit, 0)[1]) if display_points: point_surf = createText(points_x, points_y, 'Points: ') updateText(point_surf, points) if display_lines: lines_surf = createText(lines_x, lines_y, 'Remaining: ') updateText(lines_surf, lines) # # Delayed auto shift variables: # last_movement = 0 # tracks when last manual movement was das_triggered = False # tracks if DAS has been triggered # # The goal for hard drops is the piece to hit the bottom as fast as # possible. We want the user to be able to hold the hard drop button down # to continuously drop pieces too. BUT there needs to be a delay otherwise # they'll be forced to press the button for only one frame, otherwise # there's a good chance 2+ pieces will drop since we're running at 60 fps. # # SO, this is a delay that extends into the next piece. # hard_drop_repeat_wait = 0 hard_drop_repeat_wait_elapsed = 0 # # Initialize invisible pieces that occupy the well. well_rows and # well_sheets are lists of sprite groups with one entry per row. # # well_rows: In order to detect when a row is complete we need 10 (width) # surfaces per row to check when all of them are colliding with # pieces on the screen. # # well_sheets: In order to move pieces down when a row is completed we # need to be able to determine which blocks are *above* the # row that is completed. Therefore these surfaces will stretch # the width of the well and get longer and longer the farther # down they are (stretching upward). These could be avoided # if surfaces gave info about their position on the screen? # for y in range(0, well_length): # Initialize the sprites for each column for x in range(0, well_width): curr = Piece(clock, x_coord=x, y_coord=y).getSprites() well.add(curr) well_rows[y].add(curr) # Initialize the sheet. well_sheets.append(Sheet(y, well_width)) # Initialize invisible pieces that line the outside of the well. In order: # top, right, bottom, left. border_coords = [(x,-1) for x in range(-1, well_width)] border_coords += [(well_width,y) for y in range(-1, well_length)] border_coords += [(x,well_length) for x in range(well_width, -1, -1)] border_coords += [(-1,y) for y in range(well_length, -1, -1)] for (x,y) in border_coords: curr = Piece(clock, x_coord=x, y_coord=y) sprites = curr.getSprites() border.add(sprites) if not (y < 0 and x >= 0 and x < well_width): border_U.add(sprites) # shaped like a U if x < 0: border_l.add(sprites) if x == well_width: border_r.add(sprites) # Set up first piece all_drawable.add([piece.getSprites() for piece in manager.getPieces()]) gravity = Level.gravity(level) # starts at 1 second per drop falling_piece = newPiece(gravity) saved_piece = None last_line_animation = lines # Draw countdown for i in range(countdown_secs, 0, -1): animations.append(Animation(i, clock, screen)) while animations: clock.tick(fps) updateScreen() # Flush input buffer - applicable if countdown happened pygame.event.get() # # MAIN LOOP # while running: clock.tick(fps) total_elapsed += clock.get_time() if display_time: if time_counts_up: time_obj, time_string = timeObj(0, total_elapsed) else: time_obj, time_string = timeObj(time_limit, -total_elapsed) updateText(time_surf, time_string) if time_obj <= zero_time: updateText(time_surf, zero_time) print(points) gameOver() last_movement += clock.get_time() reverted_downward_move = False # See explanation in Settings.py, under MISC. if level_enabled: new_piece_pending_period = Level.nppp(level) else: new_piece_pending_period = hard_drop_wait if new_piece_pending: new_piece_pending_elapsed += clock.get_time() if hard_drop_repeat_wait: hard_drop_repeat_wait_elapsed += clock.get_time() if hard_drop_repeat_wait_elapsed > 200: hard_drop_repeat_wait = False ####################################################################### # KEY PRESSES # ####################################################################### events = pygame.event.get() # Artificially add an arrow key to the events queue. Without this code, # holding a key down does nothing. if not events: keys = pygame.key.get_pressed() for key in hard_drop_key, left_key, right_key: if keys[key]: if last_movement > 150 or das_triggered: das_triggered = True events.append(pygame.event.Event(KEYDOWN, {'key': key})) for event in events: if event.type == KEYDOWN: key = event.key # Quit if key in quit_keys: running = False # Store piece and restart loop. elif key == hold_key: falling_piece, saved_piece = hold(falling_piece, saved_piece, gravity) # NOTE: not sure if these are necessary but they could save # the game from a few bugs. new_piece_pending = False das_triggered = False last_movement = 0 updateScreen() continue # Code related to user moving or rotating piece else: last_movement = 0 # Code for downward movement - does not affect shadow. if key in down_keys: if key in hard_drop_keys: if hard_drop_repeat_wait: continue lines_down = well_length else: lines_down = 1 successful_drops = 0 for i in range(lines_down): falling_piece.move(hard_drop_key) if needToRevert(falling_piece): falling_piece.move(cheat_key) # Override normal detection if key in hard_drop_no_wait_keys: new_piece_pending = True new_piece_pending_period = 0 hard_drop_repeat_wait = True hard_drop_repeat_wait_elapsed = 0 break successful_drops += 1 if display_points: points += Scoring.drop_points(successful_drops) updateText(point_surf, points) # Other movement else: # Let player move piece even around the bottom #new_piece_pending = False falling_piece.dispatch(key) shadow_moved = shadowMove(shadow_piece, key) # Try wall/floor kicks if a rotation was illegal. if needToRevert(falling_piece) and key in rotate_keys: kick(falling_piece) # Revert movement if it was illegal and can't be done. # shadowMove usually takes care of reverting itself but # since the dection is different we need to double # check here. if needToRevert(falling_piece): falling_piece.dispatch(opposite[key]) if shadow_moved: shadow_moved = shadowMove(shadow_piece, opposite[key]) if not shadow_moved: print('no revert = bad?') # If we just moved the piece left/right and it's now # over a gap, reset the new piece pending variables. #if key in left_right_keys: if True: falling_piece.move(hard_drop_key) if not needToRevert(falling_piece): new_piece_pending = False falling_piece.move(cheat_key) elif event.type == KEYUP: das_triggered = False if event.key in hard_drop_keys: hard_drop_repeat_wait = False elif event.type == QUIT: running = False ####################################################################### # PIECE MOVEMENT # ####################################################################### # Auto movement if not new_piece_pending and falling_piece.handleGravity(needToRevert): #reverted_downward_move = checkDownwardRevert(falling_piece) reverted_downward_move = True # If the shadow and the falling piece overlap completely, we're at the # bottom so let's just kill off the shadow. This is really just because # shadow movement is buggy and this avoids some corner cases. if len(groupcollide(falling_sprites, shadow_sprites)) >= \ len(falling_piece.getSprites()): resetShadow() # The piece can't move down farther if it just collided with another # piece or is currently colliding with the bottom row of invisible # blocks. if not new_piece_pending and ( reverted_downward_move or groupcollide(falling_sprites, well_rows[-1]) ): new_piece_pending = True new_piece_pending_elapsed = 0 # Piece is locked in. Check for row completion and eventually spawn a # new piece. if new_piece_pending and \ new_piece_pending_elapsed >= new_piece_pending_period: new_piece_pending = False # Transfer falling_sprites to piece_sprites and reset variables # related to the falling piece. piece_sprites.add(falling_sprites) falling_sprites = pygame.sprite.Group() resetShadow() # Check all rows for completion. rows_completed = 0 for i, row_sprites in enumerate(well_rows): sheet = well_sheets[i] # idx 0 will be None # 'collisions' will be a dict of Block objects that are in # 'piece_sprites'. collisions = groupcollide(piece_sprites, row_sprites) # Completed row! if len(collisions) == well_width: rows_completed += 1 # Delete the row for block in collisions: for piece in pieces: if block in piece.getSprites(): piece.remove(block) # We don't need to keep empty Piece objects around. if not piece.getSprites(): pieces.remove(piece) # Move other pieces down. for piece in pieces: piece.downIfCollide(sheet) # Check for new level or game over, depending on the mode. if display_lines: lines -= rows_completed updateText(lines_surf, lines) if lines <= 0: # New level if level_enabled: level += 1 lines, new_piece_pending_period, gravity = \ Level.updateLevel(level) updateText(level_surf, level) updateText(lines_surf, lines) # User cleared all the lines else: updateText(lines_surf, 0) print(timeObj(0, total_elapsed)[1]) gameOver() # Handle animations if display_lines and not level_enabled: if lines <= 10 and lines != last_line_animation: last_line_animation = lines animations.append(Animation(lines, clock, screen)) if display_points: points += Scoring.line_points(rows_completed) updateText(point_surf, points) falling_piece = newPiece(gravity) updateScreen()