def cross_over(self, agent): """ "Breed" with another agent to produce a "child" :param agent: the other parent agent :return: "child" agent """ child = GeneticAgentComplete() # Choose weight randomly from the parents child.weight_height = self.weight_height if random.getrandbits( 1) else agent.weight_height child.weight_holes = self.weight_holes if random.getrandbits( 1) else agent.weight_holes child.weight_bumpiness = self.weight_bumpiness if random.getrandbits( 1) else agent.weight_bumpiness child.weight_line_clear = self.weight_line_clear if random.getrandbits( 1) else agent.weight_line_clear # Randomly mutate weights if random.random() < MUTATION_RATE: child.weight_height = TUtils.random_weight() if random.random() < MUTATION_RATE: child.weight_holes = TUtils.random_weight() if random.random() < MUTATION_RATE: child.weight_bumpiness = TUtils.random_weight() if random.random() < MUTATION_RATE: child.weight_line_clear = TUtils.random_weight() # Return completed child model return child
def draw_tiles(self, matrix, offsets=(0, 0), outline_only=False): for y, row in enumerate(matrix): for x, val in enumerate(row): if val == 0: continue coord_x = (offsets[0] + x) * self.grid_size coord_y = (offsets[1] + y) * self.grid_size # Draw rectangle if not outline_only: pygame.draw.rect(self.screen, TUtils.get_color_tuple(COLORS.get("TILE_" + TILES[val - 1])), (coord_x, coord_y, self.grid_size, self.grid_size)) pygame.draw.rect(self.screen, TUtils.get_color_tuple(COLORS.get("BACKGROUND_BLACK")), (coord_x, coord_y, self.grid_size, self.grid_size), 1) # Draw highlight triangle offset = int(self.grid_size / 10) pygame.draw.polygon(self.screen, TUtils.get_color_tuple(COLORS.get("TRIANGLE_GRAY")), ((coord_x + offset, coord_y + offset), (coord_x + 3 * offset, coord_y + offset), (coord_x + offset, coord_y + 3 * offset))) else: # Outline-only for prediction location pygame.draw.rect(self.screen, TUtils.get_color_tuple(COLORS.get("TILE_" + TILES[val - 1])), (coord_x + 1, coord_y + 1, self.grid_size - 2, self.grid_size - 2), 1)
def calculate_actions(self, board, current_tile, next_tile, offsets) -> List[int]: """ Calculate action sequence based on the agent's prediction :param board: the current Tetris board :param current_tile: the current Tetris tile :param next_tile: the next Tetris tile (swappable) :param offsets: the current Tetris tile's coordinates :return: list of actions (integers) that should be executed in order """ best_fitness = -9999 best_tile_index = -1 best_rotation = -1 best_x = -1 tiles = [current_tile, next_tile] # 2 tiles: current and next (swappable) for tile_index in range(len(tiles)): tile = tiles[tile_index] # Rotation: 0-3 times (4x is the same as 0x) for rotation_count in range(0, 4): # X movement for x in range(0, GRID_COL_COUNT - len(tile[0]) + 1): new_board = TUtils.get_future_board_with_tile( board, tile, (x, offsets[1]), True) fitness = self.get_fitness(new_board) if fitness > best_fitness: best_fitness = fitness best_tile_index = tile_index best_rotation = rotation_count best_x = x # Rotate tile (prep for next iteration) tile = TUtils.get_rotated_tile(tile) ################################################################################## # Obtained best stats, now convert them into sequences of actions # Action = index of { NOTHING, L, R, 2L, 2R, ROTATE, SWAP, FAST_FALL, INSTA_FALL } actions = [] if tiles[best_tile_index] != current_tile: actions.append(ACTIONS.index("SWAP")) for _ in range(best_rotation): actions.append(ACTIONS.index("ROTATE")) temp_x = offsets[0] while temp_x != best_x: direction = 1 if temp_x < best_x else -1 magnitude = 1 if abs(temp_x - best_x) == 1 else 2 temp_x += direction * magnitude actions.append( ACTIONS.index(("" if magnitude == 1 else "2") + ("R" if direction == 1 else "L"))) actions.append(ACTIONS.index("INSTA_FALL")) return actions
def rotate_tile(self, pseudo=False): if not self.active or self.paused: return False, self.tile_x, self.tile_shape new_shape = TUtils.get_rotated_tile(self.tile_shape) temp_x = self.tile_x # Out of range detection if self.tile_x + len(new_shape[0]) > GRID_COL_COUNT: temp_x = GRID_COL_COUNT - len(new_shape[0]) # If collide, disallow rotation if TUtils.check_collision(self.board, new_shape, (temp_x, self.tile_y)): return False, self.tile_x, self.tile_shape if not pseudo: self.tile_x = temp_x self.tile_shape = new_shape return True, temp_x, new_shape
def rotate_tile(self): """ Rotate current tile by 90 degrees """ new_tile_shape = TUtils.get_rotated_tile(self.tile_shape) new_x = self.tile_x # Out of range detection if self.tile_x + len(new_tile_shape[0]) > GRID_COL_COUNT: new_x = GRID_COL_COUNT - len(new_tile_shape[0]) # If collide, disallow rotation if TUtils.check_collision(self.board, new_tile_shape, (new_x, self.tile_y)): return # Apply tile properties self.tile_x = new_x self.tile_shape = new_tile_shape
def get_fitness(self, board): """ Utility method to calculate fitness score """ score = 0 # Check if the board has any completed rows future_board, clear_count = TUtils.get_board_and_lines_cleared(board) # Calculate the line-clear score and apply weights score += self.weight_line_clear * clear_count # Calculate the aggregate height of future board and apply weights score += self.weight_height * sum(TUtils.get_col_heights(future_board)) # Calculate the holes score and apply weights score += self.weight_holes * TUtils.get_hole_count(future_board) # Calculate the "smoothness" score and apply weights score += self.weight_bumpiness * TUtils.get_bumpiness(future_board) # Return the final score return score
def swap_tile(self, pseudo=False): if not self.active or self.paused: return False, (self.tile_x, self.tile_y), self.tile_shape new_tile = self.get_next_tile(False) new_tile_shape = TILE_SHAPES.get(new_tile)[:] temp_x, temp_y = self.tile_x, self.tile_y # Out of range detection if temp_x + len(self.tile_shape[0]) > GRID_COL_COUNT: temp_x = GRID_COL_COUNT - len(self.tile_shape[0]) if temp_y + len(self.tile_shape) > GRID_ROW_COUNT: temp_y = GRID_ROW_COUNT - len(self.tile_shape) # If collide, disallow swapping if TUtils.check_collision(self.board, new_tile_shape, (temp_x, temp_y)): return False, (self.tile_x, self.tile_y), self.tile_shape if not pseudo: # Swap next tile with current tile self.get_next_tile(True) self.tile_bank.insert(0, self.tile) # Apply stats self.tile = new_tile self.tile_shape = new_tile_shape self.tile_x, self.tile_y = temp_x, temp_y return True, (temp_x, temp_y), new_tile_shape
def calculate_scores(self): score_count = 0 row = 0 while True: if row >= len(self.board): break if 0 in self.board[row]: row += 1 continue # Delete the "filled" row del self.board[row] # Insert empty row at top self.board.insert(0, [0] * GRID_COL_COUNT) score_count += 1 # Calculate fitness score self.fitness = TUtils.get_fitness_score(self.board) # If cleared nothing, early return if score_count == 0: return # Calculate total score based on algorithm total_score = MULTI_SCORE_ALGORITHM(score_count) # Callback for callback in self.on_score_changed_callbacks: callback(self.score, self.score + total_score) self.score += total_score self.lines += score_count self.log("Cleared " + str(score_count) + " rows with score " + str(total_score), 3) # Calculate game speed pygame.time.set_timer(pygame.USEREVENT + 1, SPEED_DEFAULT if not SPEED_SCALE_ENABLED else int( max(50, SPEED_DEFAULT - self.score * SPEED_SCALE)))
def step(self, action=0, use_fitness=False): # Update UI if HAS_DISPLAY: pygame.event.get() # Obtain previous score previous_fitness = self.fitness previous_score = self.score # Move action if action in [1, 2, 3, 4]: self.move_tile((-1 if action in [1, 3] else 1) * (1 if action in [1, 2] else 2)) # Rotate elif action == 5: self.rotate_tile() # Swap elif action == 6: self.swap_tile() # Fast fall / Insta-fall elif action in [7, 8]: self.drop(instant=(action == 8)) # Continue by 1 step self.drop() # >> Returns: board matrix (state), score change (reward), is-game-over (done), next piece (extras) measurement = self.score - previous_score if use_fitness: measurement = self.fitness - previous_fitness board = TUtils.get_board_with_tile(self.board, self.tile_shape, (self.tile_x, self.tile_y), True) return board, measurement, not self.active, self.get_next_tile()
def draw_tiles(screen, matrix, offsets=(0, 0), global_offsets=(0, 0), outline_only=False): """ Draw tiles from a matrix (utility method) :param screen: the screen to draw on :param matrix: the matrix to draw :param offsets: matrix index offsets :param global_offsets: global pixel offsets :param outline_only: draw prediction outline only? """ for y, row in enumerate(matrix): for x, val in enumerate(row): if val == 0: continue coord_x = global_offsets[0] + (offsets[0] + x) * GAME_GRID_SIZE coord_y = global_offsets[1] + (offsets[1] + y) * GAME_GRID_SIZE # Draw rectangle if not outline_only: pygame.draw.rect( screen, TUtils.get_color_tuple(COLORS.get("TILE_" + TILES[val - 1])), (coord_x, coord_y, GAME_GRID_SIZE, GAME_GRID_SIZE)) pygame.draw.rect( screen, TUtils.get_color_tuple(COLORS.get("BACKGROUND_BLACK")), (coord_x, coord_y, GAME_GRID_SIZE, GAME_GRID_SIZE), 1) # Draw highlight triangle offset = int(GAME_GRID_SIZE / 10) pygame.draw.polygon( screen, TUtils.get_color_tuple(COLORS.get("TRIANGLE_GRAY")), ((coord_x + offset, coord_y + offset), (coord_x + 3 * offset, coord_y + offset), (coord_x + offset, coord_y + 3 * offset))) else: # Outline-only for prediction location pygame.draw.rect( screen, TUtils.get_color_tuple(COLORS.get("TILE_" + TILES[val - 1])), (coord_x + 1, coord_y + 1, GAME_GRID_SIZE - 2, GAME_GRID_SIZE - 2), 1)
def draw_next_tile(self, offsets): size = int(self.grid_size * 0.75) for y, row in enumerate(TILE_SHAPES.get(self.get_next_tile())): for x, val in enumerate(row): if val == 0: continue coord_x = offsets[0] + x * size coord_y = offsets[1] + y * size # Draw rectangle pygame.draw.rect(self.screen, TUtils.get_color_tuple(COLORS.get("TILE_" + TILES[val - 1])), (coord_x, coord_y, size, size)) pygame.draw.rect(self.screen, TUtils.get_color_tuple(COLORS.get("BACKGROUND_BLACK")), (coord_x, coord_y, size, size), 1) # Draw highlight triangle offset = int(size / 10) pygame.draw.polygon(self.screen, TUtils.get_color_tuple(COLORS.get("TRIANGLE_GRAY")), ((coord_x + offset, coord_y + offset), (coord_x + 3 * offset, coord_y + offset), (coord_x + offset, coord_y + 3 * offset)))
def spawn_tile(self): self.tile = self.get_next_tile(pop=True) self.tile_shape = TILE_SHAPES.get(self.tile)[:] self.tile_x = int(GRID_COL_COUNT / 2 - len(self.tile_shape[0]) / 2) self.tile_y = 0 self.log("Spawning a new " + self.tile + " tile!", 1) if TUtils.check_collision(self.board, self.tile_shape, (self.tile_x, self.tile_y)): self.active = False self.paused = True
def drop(self, instant=False): if not self.active or self.paused: return # Drop the tile if instant: destination = TUtils.get_effective_height(self.board, self.tile_shape, (self.tile_x, self.tile_y)) self.score += PER_STEP_SCORE_GAIN * (destination - self.tile_y) self.tile_y = destination + 1 else: self.tile_y += 1 self.score += PER_STEP_SCORE_GAIN # If no collision happen, skip if not TUtils.check_collision(self.board, self.tile_shape, (self.tile_x, self.tile_y)) and not instant: return # Collided! Add tile to board, spawn new tile, and calculate scores self.add_tile_to_board() self.calculate_scores() self.spawn_tile()
def move_tile(self, delta): if not self.active or self.paused: return new_x = self.tile_x + delta # Clamping new_x = max(0, min(new_x, GRID_COL_COUNT - len(self.tile_shape[0]))) # Cannot "override" blocks AKA cannot move when it is blocked if TUtils.check_collision(self.board, self.tile_shape, (new_x, self.tile_y)): return self.tile_x = new_x
def draw_board(screen, tetris: Tetris, x_offset: int, y_offset: int): """ Draws one Tetris board with offsets, called by draw() multiple times per frame :param screen: the screen to draw on :param tetris: Tetris instance :param x_offset: X offset (starting X) :param y_offset: Y offset (starting Y) """ # [0] Striped background layer for a in range(GRID_COL_COUNT): color = TUtils.get_color_tuple( COLORS.get("BACKGROUND_DARK" if a % 2 == 0 else "BACKGROUND_LIGHT")) pygame.draw.rect(screen, color, (x_offset + a * GAME_GRID_SIZE, y_offset, GAME_GRID_SIZE, GAME_HEIGHT)) # [1] Board tiles draw_tiles(screen, tetris.board, global_offsets=(x_offset, y_offset)) # [1] Current tile draw_tiles(screen, tetris.tile_shape, offsets=(tetris.tile_x, tetris.tile_y), global_offsets=(x_offset, y_offset)) # [2] Game over graphics if tetris.game_over: color = TUtils.get_color_tuple(COLORS.get("BACKGROUND_BLACK")) ratio = 0.9 pygame.draw.rect(screen, color, (x_offset, y_offset + (GAME_HEIGHT * ratio) / 2, GAME_WIDTH, GAME_HEIGHT * (1 - ratio))) message = "GAME OVER" color = TUtils.get_color_tuple(COLORS.get("RED")) text_image = pygame.font.SysFont(FONT_NAME, GAME_WIDTH // 6).render( message, False, color) rect = text_image.get_rect() screen.blit(text_image, (x_offset + (GAME_WIDTH - rect.width) / 2, y_offset + (GAME_HEIGHT - rect.height) / 2))
def pseudo_step(self): scores = [0] * 9 curr_score = TUtils.get_fitness_score(TUtils.get_future_board_with_tile(self.board, self.tile, self.tile_x)) # Wait action for action in [0, 7, 8]: scores[action] = curr_score # Move left once scores[1] = curr_score if TUtils.check_collision(self.board, self.tile_shape, (self.tile_x - 1, self.tile_y)) else TUtils.get_fitness_score(TUtils.get_future_board_with_tile(self.board, self.tile, self.tile_x - 1)) # Move right once scores[2] = curr_score if TUtils.check_collision(self.board, self.tile_shape, (self.tile_x + 1, self.tile_y)) else TUtils.get_fitness_score(TUtils.get_future_board_with_tile(self.board, self.tile, self.tile_x + 1)) # Move left twice scores[3] = scores[1] if TUtils.check_collision(self.board, self.tile_shape, (self.tile_x - 2, self.tile_y)) else TUtils.get_fitness_score(TUtils.get_future_board_with_tile(self.board, self.tile, self.tile_x - 2)) # Move right twice scores[4] = scores[2] if TUtils.check_collision(self.board, self.tile_shape, (self.tile_x + 2, self.tile_y)) else TUtils.get_fitness_score(TUtils.get_future_board_with_tile(self.board, self.tile, self.tile_x + 2)) # Rotate success, offset_x, tile_shape = self.rotate_tile(pseudo=True) scores[5] = curr_score if not success else TUtils.get_fitness_score(TUtils.get_future_board_with_tile(self.board, tile_shape, offset_x)) # Swap success, offsets, tile_shape = self.swap_tile(pseudo=True) scores[6] = curr_score if not success else TUtils.get_fitness_score(TUtils.get_future_board_with_tile(self.board, tile_shape, offsets[0])) return scores
def spawn_tile(self) -> bool: """ Spawns a new tile from the tile pool :return: whether the game is over """ self.current_tile = self.get_next_tile(pop=True) self.tile_shape = TILE_SHAPES[self.current_tile][:] self.tile_x = int(GRID_COL_COUNT / 2 - len(self.tile_shape[0]) / 2) self.tile_y = 0 # Game over check: game over if new tile collides with existing blocks return TUtils.check_collision(self.board, self.tile_shape, (self.tile_x, self.tile_y))
def drop_tile(self, instant=False): """ Drop the current tile by 1 grid; if <INSTANT>, then drop all the way :param instant: whether to drop all the way """ if instant: # Drop the tile until it collides with existing block(s) new_y = TUtils.get_effective_height(self.board, self.tile_shape, (self.tile_x, self.tile_y)) self.tile_y = new_y + 1 self.score += PER_STEP_SCORE_GAIN * (new_y - self.tile_y) else: # Drop the tile by 1 grid self.tile_y += 1 self.score += PER_STEP_SCORE_GAIN # If doesn't collide, skip next step if not instant and not TUtils.check_collision( self.board, self.tile_shape, (self.tile_x, self.tile_y)): return # Trigger collision event self.on_tile_collision()
def move_tile(self, delta: int): """ Move current tile to the right by <DISTANCE> grids :param delta: one of [-2, -1, 1, 2]; negative values will move to the left """ assert delta in [-2, -1, 1, 2], "Invalid move distance" new_x = self.tile_x + delta new_x = max(0, min(new_x, GRID_COL_COUNT - len(self.tile_shape[0]))) # clamping # Cannot "override" blocks AKA cannot clip into existing blocks if TUtils.check_collision(self.board, self.tile_shape, (new_x, self.tile_y)): return # Apply tile properties self.tile_x = new_x
def highlight(screen, index: int, mode: int): """ Highlight a certain Tetris grid :param screen: the screen to draw on :param index: index of Tetris grid to highlight :param mode: 0/1, 0 = best, 1 = previous best """ game_x = index % COL_COUNT game_y = index // COL_COUNT color = TUtils.get_color_tuple( COLORS.get("HIGHLIGHT_GREEN" if mode == 0 else "HIGHLIGHT_RED")) if mode == 1: # Draw previous best (thick border) temp_x = (GAME_WIDTH + PADDING) * game_x temp_y = (GAME_HEIGHT + PADDING) * game_y pygame.draw.rect(screen, color, (temp_x, temp_y, GAME_WIDTH + PADDING * 2, PADDING)) pygame.draw.rect(screen, color, (temp_x, temp_y, PADDING, GAME_HEIGHT + PADDING * 2)) temp_x = (GAME_WIDTH + PADDING) * (game_x + 1) + PADDING - 1 temp_y = (GAME_HEIGHT + PADDING) * (game_y + 1) + PADDING - 1 pygame.draw.rect(screen, color, (temp_x, temp_y, -GAME_WIDTH - PADDING, -PADDING)) pygame.draw.rect(screen, color, (temp_x, temp_y, -PADDING, -GAME_HEIGHT - PADDING)) elif mode == 0: # Draw current best (thin border) temp_x = (GAME_WIDTH + PADDING) * game_x + PADDING / 2 temp_y = (GAME_HEIGHT + PADDING) * game_y + PADDING / 2 pygame.draw.rect(screen, color, (temp_x, temp_y, GAME_WIDTH + PADDING, PADDING / 2)) pygame.draw.rect(screen, color, (temp_x, temp_y, PADDING / 2, GAME_HEIGHT + PADDING)) temp_x = (GAME_WIDTH + PADDING) * (game_x + 1) + PADDING / 2 - 1 temp_y = (GAME_HEIGHT + PADDING) * (game_y + 1) + PADDING / 2 - 1 pygame.draw.rect( screen, color, (temp_x, temp_y, -GAME_WIDTH - PADDING + 2, -PADDING / 2)) pygame.draw.rect( screen, color, (temp_x, temp_y, -PADDING / 2, -GAME_HEIGHT - PADDING + 2))
def get_fitness(self, board): """ Utility method to calculate fitness score """ score = 0 # Check if the board has any completed rows future_board, rows_cleared = TUtils.get_board_and_lines_cleared(board) # TODO: calculate fitness score for the board # TODO: calculate the aggregate height of the board and apply weights pass # TODO: count the holes and apply weights pass # TODO: calculate the "bumpiness" score and apply weights pass # TODO: Calculate the line-clear score and apply weights pass # Return the final score return score
def swap_tile(self): """ Swaps current tile with the future one """ # Get next tile without popping (swapping could fail) new_tile = self.get_next_tile(pop=False) new_tile_shape = TILE_SHAPES[new_tile][:] # clone tile shape temp_x, temp_y = self.tile_x, self.tile_y # Out of range detection if temp_x + len(self.tile_shape[0]) > GRID_COL_COUNT: temp_x = GRID_COL_COUNT - len(self.tile_shape[0]) if temp_y + len(self.tile_shape) > GRID_ROW_COUNT: temp_y = GRID_ROW_COUNT - len(self.tile_shape) # If collide, disallow swapping if TUtils.check_collision(self.board, new_tile_shape, (temp_x, temp_y)): return # Put current tile as the next tile self.tile_pool[0] = self.current_tile # Apply tile properties self.current_tile = new_tile self.tile_shape = new_tile_shape self.tile_x, self.tile_y = temp_x, temp_y
# Utility Functions # ##################### def get_next_tile(self, pop=False): """ Obtains the next tile from the tile pool """ if not self.tile_pool: self.generate_tile_pool() return self.tile_pool[0] if not pop else self.tile_pool.pop(0) # Test game board using step action if __name__ == "__main__": tetris = Tetris() while True: if not tetris.game_over: # Print game board TUtils.print_board( TUtils.get_board_with_tile(tetris.board, tetris.tile_shape, (tetris.tile_x, tetris.tile_y))) # Get user input (q = quit, r = reset) # 0-8 = actions message = input("Next action (0-8): ") if message == "q": break elif message == "r": tetris.reset_game() continue # Step tetris.step(int(message))
def draw(screen): """ Called by the update() function every frame, draws the PyGame GUI """ # Background layer screen.fill(TUtils.get_color_tuple(COLORS.get("BACKGROUND_BLACK"))) # Draw Tetris boards curr_x, curr_y = PADDING, PADDING for x in range(ROW_COUNT): for y in range(COL_COUNT): draw_board(screen, TETRIS_GAMES[x * COL_COUNT + y], curr_x, curr_y) curr_x += GAME_WIDTH + PADDING curr_x = PADDING curr_y += GAME_HEIGHT + PADDING # Draw statistics # Realign starting point to statistics bar curr_x, curr_y = GAME_WIDTH * COL_COUNT + PADDING * (COL_COUNT + 1), PADDING # Draw title draw_text("Tetris", screen, (curr_x, curr_y), font_size=48) curr_y += 60 # Draw statistics best_indexes, best_score = get_high_score() draw_text(f"High Score: {best_score:.1f}", screen, (curr_x, curr_y)) curr_y += 20 draw_text(f"Best Agent: {SEP.join(map(str, best_indexes))}", screen, (curr_x, curr_y)) curr_y += 20 # Draw genetics if gen_generation > -1: curr_y += 20 draw_text(f"Generation #{gen_generation}", screen, (curr_x, curr_y), font_size=24) curr_y += 35 draw_text(f"Time Limit: {time_elapsed}/{time_limit}", screen, (curr_x, curr_y)) curr_y += 20 survivor = len([a for a in TETRIS_GAMES if not a.game_over]) draw_text( f"Survivors: {survivor}/{GAME_COUNT} ({survivor / GAME_COUNT * 100:.1f}%)", screen, (curr_x, curr_y)) curr_y += 20 draw_text(f"Prev H.Score: {gen_previous_best_score:.1f}", screen, (curr_x, curr_y)) curr_y += 20 draw_text(f"All Time H.S: {gen_top_score:.1f}", screen, (curr_x, curr_y)) curr_y += 40 # Display selected agent mouse_x, mouse_y = pygame.mouse.get_pos() grid_x, grid_y = mouse_x // (GAME_WIDTH + PADDING), mouse_y // ( GAME_HEIGHT + PADDING) selected = grid_y * COL_COUNT + grid_x agent_index = -1 highlight_selected = False if selected < GAME_COUNT and mouse_x < (GAME_WIDTH + PADDING) * COL_COUNT: agent_index = selected highlight_selected = True elif len(best_indexes) > 0: agent_index = best_indexes[0] if agent_index != -1: draw_text(f"Agent #{agent_index}:", screen, (curr_x, curr_y), font_size=24) curr_y += 35 draw_text( f">> Agg Height: {AGENTS[agent_index].weight_height:.1f}", screen, (curr_x, curr_y)) curr_y += 20 draw_text(f">> Hole Count: {AGENTS[agent_index].weight_holes:.1f}", screen, (curr_x, curr_y)) curr_y += 20 draw_text( f">> Bumpiness: {AGENTS[agent_index].weight_bumpiness:.1f}", screen, (curr_x, curr_y)) curr_y += 20 draw_text( f">> Line Clear: {AGENTS[agent_index].weight_line_clear:.1f}", screen, (curr_x, curr_y)) curr_y += 20 if highlight_selected: highlight(screen, selected, mode=1) else: # Highlight current best(s) for a in best_indexes: highlight(screen, a, mode=0) # Update display pygame.display.update()
def print_board(self, flattened=False): TUtils.print_board( TUtils.get_board_with_tile(self.board, self.tile_shape, (self.tile_x, self.tile_y), flattened))
def draw_text(message: str, screen, offsets, font_size=16, color="WHITE"): """ Draws a line of text at the specified offsets """ text_image = pygame.font.SysFont(FONT_NAME, font_size).render( message, False, TUtils.get_color_tuple(COLORS.get(color))) screen.blit(text_image, offsets)
def draw(self): # Background layer self.screen.fill(TUtils.get_color_tuple(COLORS.get("BACKGROUND_BLACK"))) ################ # Tetris Board # ################ # Layered background layer for a in range(GRID_COL_COUNT): color = TUtils.get_color_tuple(COLORS.get("BACKGROUND_DARK" if a % 2 == 0 else "BACKGROUND_LIGHT")) pygame.draw.rect(self.screen, color, (a * self.grid_size, 0, self.grid_size, SCREEN_HEIGHT)) # x, y, width, height # Tetris (tile) layer # Draw board first self.draw_tiles(self.board) # Draw hypothesized tile if DISPLAY_PREDICTION: self.draw_tiles(self.tile_shape, ( self.tile_x, TUtils.get_effective_height(self.board, self.tile_shape, (self.tile_x, self.tile_y))), True) # Draw current tile self.draw_tiles(self.tile_shape, (self.tile_x, self.tile_y)) ################# # Message Board # ################# # Coordinates calculations margin = 20 # 20 pixels margin text_x_start = GRID_COL_COUNT * self.grid_size + margin text_y_start = margin # Title message = MESSAGES.get("TITLE") if not self.active: message = "Game Over" elif self.paused: message = "= PAUSED =" text_image = pygame.font.SysFont(FONT_NAME, 32).render(message, False, TUtils.get_color_tuple(COLORS.get("WHITE"))) self.screen.blit(text_image, (text_x_start, text_y_start)) text_y_start = 60 # Controls for msg in MESSAGES.get("CONTROLS").split("\n"): text_image = pygame.font.SysFont(FONT_NAME, 16).render(msg, False, TUtils.get_color_tuple(COLORS.get("WHITE"))) self.screen.blit(text_image, (text_x_start, text_y_start)) text_y_start += 20 text_y_start += 10 # Score text_image = pygame.font.SysFont(FONT_NAME, 16).render(MESSAGES.get("SCORE").format(self.score, self.lines), False, TUtils.get_color_tuple(COLORS.get("WHITE"))) self.screen.blit(text_image, (text_x_start, text_y_start)) text_y_start += 20 # High Score high_score = self.score if self.score > self.high_score else self.high_score high_score_lines = self.lines if self.lines > self.high_score_lines else self.high_score_lines text_image = pygame.font.SysFont(FONT_NAME, 16).render(MESSAGES.get("HIGH_SCORE").format(high_score, high_score_lines), False, TUtils.get_color_tuple(COLORS.get("WHITE"))) self.screen.blit(text_image, (text_x_start, text_y_start)) text_y_start += 20 # Fitness score text_image = pygame.font.SysFont(FONT_NAME, 16).render(MESSAGES.get("FITNESS").format(self.fitness), False, TUtils.get_color_tuple(COLORS.get("WHITE"))) self.screen.blit(text_image, (text_x_start, text_y_start)) text_y_start += 20 # Speed speed = SPEED_DEFAULT if not SPEED_SCALE_ENABLED else int(max(50, SPEED_DEFAULT - self.score * SPEED_SCALE)) text_image = pygame.font.SysFont(FONT_NAME, 16).render(MESSAGES.get("SPEED").format(speed), False, TUtils.get_color_tuple(COLORS.get("WHITE"))) self.screen.blit(text_image, (text_x_start, text_y_start)) text_y_start += 20 # Next tile text_image = pygame.font.SysFont(FONT_NAME, 16).render(MESSAGES.get("NEXT_TILE").format(self.get_next_tile()), False, TUtils.get_color_tuple(COLORS.get("WHITE"))) self.screen.blit(text_image, (text_x_start, text_y_start)) text_y_start += 20 self.draw_next_tile((text_x_start, text_y_start)) text_y_start += 60 pygame.display.update()