ZX81 BASIC版"Dropout"游戏转Pygame开发技术咨询
Dropout游戏Pygame适配与改进
我基于一段ZX BASIC代码完成了"Dropout"游戏到Pygame的转换,原ZX BASIC代码如下:
10 LET P=0 20 LET T=P 30 FOR Z=1 TO 10 35 CLS 37 PRINT AT 12,0;T 40 LET R=INT (RND*17) 50 FOR Y=0 TO 10 60 PRINT AT Y,R;"O" 70 LET N=P+(INKEY$="4")-(INKEY$="1") 80 IF N<0 OR N>15 THEN LET N=P 100 PRINT AT 11,P;" ";AT 11,N;"┗┛";AT Y,R;" " 110 LET P=N 120 NEXT Y 130 LET T=T+(P=R OR P+1=R) 150 NEXT Z 160 PRINT AT 12,0;"YOU SCORED ";T;"/10" 170 PAUSE 4E4 180 RUN
我的需求是降低实现门槛,避免OOP,保留ZX BASIC的风格,仅用函数(类似当年的GOSUB)。但我对主循环里的嵌套FOR循环存疑,想咨询以下问题:
- 如何把退出代码
if event.type == pygame.QUIT放在一处,让它在游戏回合中也生效,不用重复写? - 不使用FOR/嵌套FOR循环的话,这个游戏该怎么实现?
- 我写的初始Pygame代码违反了哪些最佳实践?
- 在保留ZX81游戏风格的前提下,有哪些改进建议?
同时希望提供包含改进思路的完整代码示例。
我的初始Pygame代码:
import pygame import random import sys # Define colors and other global constants BLACK = (0, 0, 0) WHITE = (255, 255, 255) TEXT_SIZE = 16 SCREEN_SIZE = (16 * TEXT_SIZE, 13 * TEXT_SIZE) NUM_ROUNDS = 5 def print_at_pos(row_num, col_num, item): """Blits text to row, col position.""" screen.blit(item, (col_num * TEXT_SIZE, row_num * TEXT_SIZE)) # Set up stuff pygame.init() screen = pygame.display.set_mode(SCREEN_SIZE) pygame.display.set_caption("Dropout") game_font = pygame.font.SysFont('consolas', TEXT_SIZE) # Create clock to manage how fast the screen updates clock = pygame.time.Clock() # initialize some game variables player_pos, new_player_pos, coin_row, score = 0, 0, 0, 0 # -------- Main Program Loop ----------- while True: score = 0 # Each value of i represents 1 round for i in range(NUM_ROUNDS): coin_col = random.randint(0, 15) # Each value of j represents one step in the coin's fall for j in range(11): pygame.event.get() pressed = pygame.key.get_pressed() if pressed[pygame.K_RIGHT]: new_player_pos = player_pos + 1 elif pressed[pygame.K_LEFT]: new_player_pos = player_pos - 1 if new_player_pos < 0 or new_player_pos > 15: new_player_pos = player_pos # --- Game logic player_pos = new_player_pos coin_row = j if player_pos + 1 == coin_col and j == 10: score += 1 # --- Drawing code # First clear screen screen.fill(WHITE) player_icon = game_font.render("|__|", True, BLACK, WHITE) print_at_pos(10, new_player_pos, player_icon) coin_text = game_font.render("O", True, BLACK, WHITE) print_at_pos(coin_row, coin_col, coin_text) score_text = game_font.render(f"SCORE: {score}", True, BLACK, WHITE) print_at_pos(12, 0, score_text) # --- Update the screen. pygame.display.flip() # --- Limit to 6 frames/sec maximum. Adjust to taste. clock.tick(8) msg_text = game_font.render("PRESS ANY KEY TO PLAY AGAIN", True, BLACK, WHITE) print_at_pos(5, 0, msg_text) pygame.display.flip() waiting = True while waiting: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit(0) if event.type == pygame.KEYDOWN: waiting = False
问题解答
1. 统一处理退出事件
编写一个全局事件处理函数,在游戏的每帧循环节点调用,就能实现退出逻辑的统一管理:
def handle_events(): for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() return pygame.event.poll() # 返回事件供其他逻辑使用
之后在游戏主循环、硬币下落循环、重玩等待循环中调用这个函数,就能确保退出事件随时被处理。
2. 摆脱FOR循环的实现方式
把循环计数改成状态变量,用主循环驱动状态更新,完全贴合Pygame的事件驱动模式:
- 用
current_round记录当前轮次,初始为0,每完成一轮递增,直到达到设定的总轮数 - 用
coin_fall_step记录硬币下落的当前步数,初始为0,每帧递增,直到步数达到落地阈值后重置,同时生成新硬币、推进轮次
整个游戏流程由主循环的帧更新推进,没有嵌套阻塞的FOR循环。
3. 初始代码违反的最佳实践
- 重复渲染浪费性能:每帧都调用
game_font.render()生成新的Surface,静态元素(玩家图标、"O")应该提前渲染缓存 - 事件处理不规范:用
pygame.event.get()却不处理事件,会导致事件队列堆积,应该配合事件循环处理输入 - 全局变量耦合度高:
screen等全局变量被函数直接调用,应该通过参数传递降低耦合 - 嵌套循环阻塞响应:硬币下落的FOR循环会阻塞事件处理,导致退出、按键等操作延迟生效
- 变量冗余初始化:
new_player_pos无需提前初始化,可在需要时临时赋值 - 计分逻辑不完整:原BASIC中玩家位置等于硬币列或玩家位置+1等于硬币列都得分,初始代码只实现了后者
4. 保留ZX81风格的改进建议
- 还原视觉元素:把玩家图标改回原BASIC的
┗┛,贴合ZX81的字符显示风格 - 匹配原游戏节奏:通过调整
clock.tick()的帧率,还原原BASIC中硬币下落的速度 - 简化渲染逻辑:预渲染所有需要的字符Surface,避免每帧重复生成
- 保留原计分规则:完整实现原游戏的两种得分判定条件
- 贴合原游戏流程:原游戏结束后自动重玩,可保留该逻辑或提供按键重玩选项
改进后的完整代码
import pygame import random import sys # 全局常量 BLACK = (0, 0, 0) WHITE = (255, 255, 255) TEXT_SIZE = 16 SCREEN_COLS = 16 SCREEN_ROWS = 13 SCREEN_SIZE = (SCREEN_COLS * TEXT_SIZE, SCREEN_ROWS * TEXT_SIZE) NUM_ROUNDS = 10 # 还原原游戏的10轮 FALL_SPEED = 8 # 匹配原游戏下落速度 # 预渲染静态资源,提升性能 def init_resources(font): return { "player": font.render("┗┛", True, BLACK, WHITE), "coin": font.render("O", True, BLACK, WHITE), "score_label": font.render("SCORE: ", True, BLACK, WHITE), "game_over_prefix": font.render("YOU SCORED ", True, BLACK, WHITE), "play_again": font.render("PRESS ANY KEY TO PLAY AGAIN", True, BLACK, WHITE) } # 统一事件处理 def handle_events(): for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() return pygame.event.poll() # 绘制文本到指定行列 def draw_text(screen, text_surface, row, col): screen.blit(text_surface, (col * TEXT_SIZE, row * TEXT_SIZE)) # 初始化游戏状态 def reset_game_state(): return { "score": 0, "current_round": 0, "coin_col": random.randint(0, SCREEN_COLS - 1), "coin_row": 0, "player_pos": 0 } def main(): pygame.init() screen = pygame.display.set_mode(SCREEN_SIZE) pygame.display.set_caption("Dropout (ZX81 Style)") game_font = pygame.font.SysFont('consolas', TEXT_SIZE) resources = init_resources(game_font) clock = pygame.time.Clock() game_state = reset_game_state() waiting_for_restart = False while True: event = handle_events() if waiting_for_restart: # 重玩等待逻辑 if event and event.type == pygame.KEYDOWN: game_state = reset_game_state() waiting_for_restart = False # 绘制结束界面 screen.fill(WHITE) draw_text(screen, resources["play_again"], 5, 0) score_text = game_font.render(f"{game_state['score']}/{NUM_ROUNDS}", True, BLACK, WHITE) draw_text(screen, resources["game_over_prefix"], 6, 0) draw_text(screen, score_text, 6, len("YOU SCORED ")) pygame.display.flip() clock.tick(10) continue # 玩家移动处理 pressed = pygame.key.get_pressed() new_player_pos = game_state["player_pos"] if pressed[pygame.K_RIGHT]: new_player_pos += 1 elif pressed[pygame.K_LEFT]: new_player_pos -= 1 # 边界限制:玩家图标占2列,最大位置为14(16-2) new_player_pos = max(0, min(new_player_pos, SCREEN_COLS - 2)) game_state["player_pos"] = new_player_pos # 硬币下落逻辑 game_state["coin_row"] += 1 # 硬币落地判定 if game_state["coin_row"] >= 11: # 还原原游戏计分规则 if (game_state["player_pos"] == game_state["coin_col"] or game_state["player_pos"] + 1 == game_state["coin_col"]): game_state["score"] += 1 # 推进轮次 game_state["current_round"] += 1 if game_state["current_round"] >= NUM_ROUNDS: waiting_for_restart = True else: # 重置硬币位置 game_state["coin_col"] = random.randint(0, SCREEN_COLS - 1) game_state["coin_row"] = 0 # 游戏界面绘制 screen.fill(WHITE) # 绘制玩家 draw_text(screen, resources["player"], 11, game_state["player_pos"]) # 绘制未落地的硬币 if game_state["coin_row"] < 11: draw_text(screen, resources["coin"], game_state["coin_row"], game_state["coin_col"]) # 绘制分数 draw_text(screen, resources["score_label"], 12, 0) score_text = game_font.render(str(game_state["score"]), True, BLACK, WHITE) draw_text(screen, score_text, 12, len("SCORE: ")) pygame.display.flip() clock.tick(FALL_SPEED) if __name__ == "__main__": main()
内容的提问来源于stack exchange,提问作者Robin Andrews
相关产品推荐
相关产品推荐

