python - ZX81 BASIC 到 "Dropout"游戏的 Pygame 转换

标签 python pygame game-development basic retro-computing

我根据这篇文章编写了以下代码:http://kevman3d.blogspot.com/2015/07/basic-games-in-python-1982-would-be.html

在这张图片中的 ZX BASIC 上:

code listing

 10 LET P=0
 20 LET T=P
 30 FOR Z=1 T0 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

我还在 Code Review Stack Exchange 上分享了它,并得到了非常有帮助的响应,将其重构为带有类型提示的高质量 Python 代码。

但是,出于我的目的,我希望保持使这项工作稍微不那么先进所需的知识水平,包括避免使用 OOP。我基本上想保持“ZX BASIC 的精神”,但让代码“不糟糕”。函数的使用很好,因为我们在过去被允许 GOSUB

我对在主游戏循环中使用嵌套 FOR 循环来使游戏正常运行的方法持怀疑态度,但与此同时我很好奇 BASIC 范例的表现如何映射到 Pygame 的更多事件驱动方法,因此我欢迎任何关于这种方法的优缺点的评论。

更具体地说,

  • 有什么地方可以把退出代码 if event.type == pygame.QUIT 放在游戏回合期间有效的地方,而不必在其他地方重复代码?

  • 如果我要避免使用 FOR 循环/嵌套的 FOR 循环,这个游戏将如何实现?

  • 我是否违反了 pygame/Python 的最佳实践要点?

  • 考虑到我的目的是在保持 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

最佳答案

这是我对您的代码进行的重组:

import pygame
import random

# Define global constants
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()

# draw the images
player_icon = game_font.render("|__|", True, "black", "white")
# if we don't specify a background color, it'll be transparent
coin_text = game_font.render("O", True, "black")
msg_text = game_font.render("PRESS ANY KEY TO PLAY AGAIN", True, "black", "white")

# initialize some game variables
waiting = False  # start in game
player_pos = 0
score = 0
game_round = 0
coin_row = 0
coin_col = random.randint(0, 15)
running = True  # For program exit
# -------- Main Program Loop -----------
while running:
    # event handling
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if waiting:
                waiting = False
                score = 0  #  reset score
            elif event.key == pygame.K_LEFT:
                player_pos -= 1
            elif event.key == pygame.K_RIGHT:
                player_pos += 1

    # --- Game logic
    if waiting:
        # don't update the game state or redraw screen
        print_at_pos(5, 0, msg_text)
    else:
        coin_row += 1  # TODO: decouple from frame rate
        if -1 > player_pos:
            player_pos = -1 # so we can catch a coin at zero
        elif 15 < player_pos:
            player_pos = 15

        # coin is in scoring position
        if coin_row == 10:
            if player_pos + 1 == coin_col:
                score += 1
        elif coin_row > 10:  # round is over
            coin_col = random.randint(0, 15)
            coin_row = 0
            game_round+= 1
            if game_round >= NUM_ROUNDS:
                waiting = True
                game_round = 0  # reset round counter

        # --- Drawing code
        screen.fill("white")  # clear screen
        print_at_pos(10, player_pos, player_icon)
        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(6)
pygame.quit()

我使用了一个 bool 值 waiting 来允许仅在游戏过程中移动的常见事件和游戏状态处理。对于更复杂的交互,您需要 state machine .

硬币运动目前与帧速率相关联,这很容易,但理想情况下您会指定一个速率/时间间隔,例如行下降之间有 200 毫秒,然后您可以获得类似于显示器刷新率的刷新率。

关于python - ZX81 BASIC 到 "Dropout"游戏的 Pygame 转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73950834/

相关文章:

python - 为什么我的缓冲区溢出漏洞只会打开用户 shell 而不是 root shell?

python - pandas read_csv 按列索引(不是名称)设置 `dtype`

python - 如何使用 tkinter GUI 可视化 matplotlib 预测?

python - 在 PyGame 中渲染大图像导致帧率低

unity-game-engine - 新的 UI 构建器/工具包和 VR 世界空间

python - numpy 乘法表输出

python - 如何从 "B"类中调用 "A"类的实例方法?

python - 线与线与 pygame 的倒置 y 轴相交

unity-game-engine - Unity插件纹理是不可变的

ios - 即使在 GameScene 的 Update 方法中游戏还没有结束,我也会得到 "You Lose"