
aider 实战实录用 Pygame 从零构建 Pong 游戏——一步步拆解 AI 结对编程全流程【免费下载链接】aideraider is AI pair programming in your terminal项目地址: https://gitcode.com/GitHub_Trending/ai/aider本文以仓库中的 pong.md 会话实录为主体完整还原用户通过 aider 在终端里从零搭建 Pygame Pong 游戏的五次交互。全文涵盖新文件创建、增量编辑、权限确认、自动 commit 等核心机制并对照 aider 源码说明 edit block 的解析、文件准入与自动提交的底层实现。读完你可以复现这段对话理解 aider 如何把自然语言需求变成可运行、按 git commit 组织的小步代码变更并学会在游戏开发这类迭代性任务中高效使用 aider。这个示例是什么pong.md 位于仓库的示例对话目录 aider/website/examples 下与 hello-world-flask、2048-game、complex-change 等一起收录在 示例对话索引 中。这类 transcript 的价值在于它们不是教学文案而是aider 与用户真实交互的输出记录——既展示 LLM 如何按 aider 规定的编辑格式产出代码也展示 aider 每轮自动应用补丁、自动提交 git 的完整闭环。本示例的核心剧情很清晰用户在一个空目录里启动aider请求用 pygame 写一个简单的 pong 游戏在得到可运行的基础版本后又连续提出了四轮修改诉求球速、球拍尺寸与颜色、反弹加速每一步都以一次自动 git 提交收尾。先学会读会话实录示例目录的 README.md 专门说明了 transcript 的阅读方式理解它有助于分辨哪部分是用户、哪部分是 AI、哪部分是 aider 工具本身 xxx前缀的是aider 工具自身的输出如文件创建确认、git 操作结果#### xxx前缀的是用户输入的对话消息蓝色正文此处为普通段落是LLM 的回复其中常包含成块的编辑内容edit block。会话中出现形如下面的代码块就是一次编辑建议它告诉 aider 要修改哪个文件、把哪一段内容换成什么pong_game.py ORIGINAL print(hello) print(goodbye) UPDATED要点ORIGINAL与UPDATED之间是被替换的旧内容与新的内容。当ORIGINAL段为空时表示这是一个全新文件、整段内容将被创建当UPDATED段为空时表示删除对应代码。这正是当前仓库中 fenced edit block 编辑格式的核心约定可对照 editblock_fenced_prompts.py 中新建 hello.py与删除 factorial 函数的示例消息现代版本中以 SEARCH/ REPLACE标记呈现语义等价。aider 在后台自动完成的动作同样见 示例索引 的说明LLM 每提出一次代码变更aider 就自动应用到源文件应用后aider 会自动生成描述性的提交信息并 commit 到 gitLLM 只能看到加入会话的文件。用户通过命令行参数或会话内/add命令把文件加入会话若 LLM 主动要看某个文件aider 会征得用户同意后加入。因此 transcript 里每次出现文件加入/移出会话时都会有对应通知。准备工作空目录 git aider转录的第一行是$ aider意味着用户进入一个建议已用git init初始化的空目录后直接启动。为什么要 git 仓库因为 aider 的自动提交依赖 git源码层面即 repo.py 的commit()base_coder.auto_commit()中if not self.repo or not self.auto_commits or self.dry_run: return直接体现了这一点见 base_coder.py。若想在全新目录复现本会话可参考仓库的 安装文档 安装 aider并准备 pygame 运行时运行游戏需要pygame库# 1. 建一个全新项目目录并进入 mkdir pong-tutorial cd pong-tutorial git init # 2. 启动 aider交互式终端会话 aider # 3. 运行游戏需要 pygame在会话外单独安装 python3 -m pip install pygame python3 pong_game.py第一轮从零搭建游戏骨架用户发出的第一条指令can you make a simple game of pong with pygame?LLM 没有直接甩出一大段代码而是先给出了 10 步实现规划安装 Pygame 库初始化 Pygame 并创建游戏窗口创建 Paddle球拍和 Ball球类初始化球拍与球创建游戏主循环处理球拍移动的用户输入更新球的位置并做碰撞检测在屏幕上绘制球拍与球计分与更新分数在屏幕显示分数。随后它按EDIT BLOCK格式给出第一个代码块。因为是新建文件所以ORIGINAL段为空新文件命名为pong_game.pypong_game.py ORIGINAL import pygame import sys # Initialize Pygame pygame.init() # Set up the game window screen_width 640 screen_height 480 screen pygame.display.set_mode((screen_width, screen_height)) pygame.display.set_caption(Pong) # Game loop running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False # Update game objects # Draw game objects # Update the display pygame.display.flip() # Quit Pygame pygame.quit() sys.exit() UPDATED这段代码只做了两件事初始化 640×480 的窗口以及一个能响应窗口关闭事件的空主循环。LLM 在文本中也明确表示这是游戏的基础结构下一步再补充类和玩法逻辑。此时 aider 的两条安全机制登场。因为模型要求创建会话中尚不存在的文件aider 弹出确认并得到用户同意随后为了能纳入自动提交与后续编辑又询问是否把新文件加入 git用户同样同意 Allow creation of new file pong_game.py? y Add pong_game.py to git? y Applied edit to pong_game.py Commit 14799cb aider: Added basic Pygame initialization and game loop structure to pong_game.py.注意这几行输出对应了 aider 核心的文件准入流程实现于 base_coder.py 的allowed_to_edit()当目标路径在文件系统中尚不存在时aider 会以Create new file?旧版本 transcript 中表现为 Allow creation of new file …?向用户确认见 base_coder.py若文件本就不在仓库里且开启了自动提交self.auto_commitsaider 会先执行git add把新文件纳入版本控制见 base_coder.py补丁应用成功后打印 Applied edit to …见 base_coder.py随后auto_commit()以本轮对话为上下文、让模型撰写提交信息并执行 commit。这正是 transcript 呈现先确认、再应用、后提交三个步骤的原因——你看到的Commit 14799cb aider: ...就是第一个可回滚的里程碑。提交信息中的aider:前缀在 repo.py 中可看到其拼接逻辑当开启对应 attribution 配置时。第二轮补全类、输入、碰撞与主循环用户回复go aheadLLM 在同一轮里给出了两个编辑块。第一个把Paddle与Ball两个 Sprite 类插到窗口初始化之后pong_game.py ORIGINAL import pygame import sys # Initialize Pygame pygame.init() # Set up the game window screen_width 640 screen_height 480 screen pygame.display.set_mode((screen_width, screen_height)) pygame.display.set_caption(Pong) import pygame import sys import random # Initialize Pygame pygame.init() # Set up the game window screen_width 640 screen_height 480 screen pygame.display.set_mode((screen_width, screen_height)) pygame.display.set_caption(Pong) # Paddle and Ball classes class Paddle(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() self.width 10 self.height 60 self.image pygame.Surface((self.width, self.height)) self.image.fill((255, 255, 255)) self.rect self.image.get_rect() self.rect.x x self.rect.y y def move(self, dy): if self.rect.y dy 0 and self.rect.y dy screen_height - self.height: self.rect.y dy class Ball(pygame.sprite.Sprite): def __init__(self): super().__init__() self.width 10 self.height 10 self.image pygame.Surface((self.width, self.height)) self.image.fill((255, 255, 255)) self.rect self.image.get_rect() self.rect.x screen_width // 2 - self.width // 2 self.rect.y screen_height // 2 - self.height // 2 self.dx random.choice([-1, 1]) * 4 self.dy random.choice([-1, 1]) * 4 def update(self): self.rect.x self.dx self.rect.y self.dy # Collision with top and bottom if self.rect.y 0 or self.rect.y screen_height - self.height: self.dy -self.dy # Scoring if self.rect.x 0 or self.rect.x screen_width - self.width: self.reset() def reset(self): self.rect.x screen_width // 2 - self.width // 2 self.rect.y screen_height // 2 - self.height // 2 self.dx random.choice([-1, 1]) * 4 self.dy random.choice([-1, 1]) * 4 def collide_with(self, paddle): if self.rect.colliderect(paddle.rect): self.dx -self.dx UPDATED代码要点Paddle10×60 的白色矩形move(dy)带边界钳制保证球拍不越出窗口上下沿Ball10×10 的白色矩形从窗口正中央出发dx/dy初值为 ±4随机决定方向update()移动球并处理上下边界反弹若球从左右两侧出界rect.x 0或rect.x screen_width - width则调用reset()回到中心重新开局collide_with()用pygame.Rect.colliderect判断是否撞到球拍撞到就水平反向。第二个编辑块则把占位的空主循环整体替换为真正的游戏逻辑——初始化左右球拍与球、键盘输入、更新与绘制pong_game.py ORIGINAL # Game loop running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False # Update game objects # Draw game objects # Update the display pygame.display.flip() # Initialize paddles and ball left_paddle Paddle(10, screen_height // 2 - 30) right_paddle Paddle(screen_width - 20, screen_height // 2 - 30) ball Ball() # Game loop running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False # Handle user input keys pygame.key.get_pressed() if keys[pygame.K_w]: left_paddle.move(-5) if keys[pygame.K_s]: left_paddle.move(5) if keys[pygame.K_UP]: right_paddle.move(-5) if keys[pygame.K_DOWN]: right_paddle.move(5) # Update game objects ball.update() ball.collide_with(left_paddle) ball.collide_with(right_paddle) # Draw game objects screen.fill((0, 0, 0)) screen.blit(left_paddle.image, left_paddle.rect) screen.blit(right_paddle.image, right_paddle.rect) screen.blit(ball.image, ball.rect) # Update the display pygame.display.flip() pygame.time.delay(16) UPDATED新增的关键行为左球拍在x10、右球拍在xscreen_width-20都垂直居中左拍用W/S控制、右拍用方向键↑/↓控制每帧移动 5 像素每次循环清空屏幕为黑色screen.fill((0, 0, 0))再依次绘制两个球拍与球pygame.time.delay(16)把帧间隔压到约 60 FPS 的量级避免游戏跑得过快。同一轮里两个 edit block 被连续应用到同一文件因此这次提交前出现了两次Applied edit to pong_game.py随后合并为一次提交 Applied edit to pong_game.py Applied edit to pong_game.py Commit b20bcee aider: Added Paddle and Ball classes, handled user input, updated ball position, drew objects, and handled scoring.值得强调一个回合可以包含多个 edit blockaider 会全部应用后再做一次自动提交。这对应 base_coder.py 中逐个文件打印 Applied edit to …、再由 auto_commit 统一提交的实现方式。LLM 在结束时还给了一句诚实的收尾也为后文的迭代埋下伏笔Now, you should have a simple Pong game using Pygame. You can further improve the game by adding a scoring system and displaying the score on the screen.即当前版本其实还没有计分显示只是球出界后重置。第三轮球速太快减半nice, it works. but the ball moves too fast.LLM 精准定位到Ball类的初速度设置把位移系数从 4 降到 2pong_game.py ORIGINAL self.dx random.choice([-1, 1]) * 4 self.dy random.choice([-1, 1]) * 4 self.dx random.choice([-1, 1]) * 2 self.dy random.choice([-1, 1]) * 2 UPDATED Applied edit to pong_game.py Commit f4ceed4 aider: Reduced ball speed by half.这就是典型的小步调优单点改动、语义清晰的提交信息Reduced ball speed by half。如果你希望进一步放慢/加快直接调这个乘数即可dx/dy同时出现在Ball.__init__与Ball.reset()中追求一致时两处都要同步。第四轮球拍加大一红一蓝can you make the paddles a bit bigger? and make one paddle red and one blue?需求被拆成两处修改。第一处改Paddle的几何尺寸并顺手把颜色变成由横坐标决定的属性——左半屏白、右半屏蓝pong_game.py ORIGINAL self.width 10 self.height 60 self.image pygame.Surface((self.width, self.height)) self.image.fill((255, 255, 255)) self.width 15 self.height 80 self.image pygame.Surface((self.width, self.height)) self.color (255, 255, 255) if x screen_width // 2 else (0, 0, 255) self.image.fill(self.color) UPDATED第二处把左球拍实例化后的颜色覆盖为红色同时因高度变化把 y 初值从// 2 - 30微调为// 2 - 40以保持视觉居中pong_game.py ORIGINAL left_paddle Paddle(10, screen_height // 2 - 30) left_paddle Paddle(10, screen_height // 2 - 40) left_paddle.color (255, 0, 0) left_paddle.image.fill(left_paddle.color) UPDATED Applied edit to pong_game.py Applied edit to pong_game.py Commit 2c645c9 aider: Changed paddle size and color, and set left paddle to red.注意这段还演示了一个技巧修改color属性后必须调用image.fill(color)才能刷新像素表面。因为pygame.Surface是立即填充的只改记录颜色的属性并不会改变已绘制的图像。若你继续自定义颜色请记得同样改属性 重填充。第五轮每次碰拍加速 10%each time the ball bounces off the paddle, can you make it go 10% faster?最后一项调优针对Ball.collide_with——反弹时不仅水平反向还让dx、dy同时乘以 1.1pong_game.py ORIGINAL def collide_with(self, paddle): if self.rect.colliderect(paddle.rect): self.dx -self.dx def collide_with(self, paddle): if self.rect.colliderect(paddle.rect): self.dx -self.dx * 1.1 self.dy * 1.1 UPDATED Applied edit to pong_game.py Commit 693faf4 aider: Increased ball speed by 10% in both x and y directions each time it bounces off a paddle.这为游戏引入了难度递增的经典手感对拉回合越长、球越快越接近真实乒乓的节奏。作为对照加速逻辑刻意没有改动update()里的上下壁反弹也不改变reset()的初始速度——所以每次丢球后都从 2 的基准速重新开始。会话结束时游戏处于什么状态把五轮变更叠加起来pong_game.py最终参数如下对象最终状态窗口640×480标题 Pong黑色背景左球拍15×80位置 x10红色W/S 控制右球拍15×80位置 xscreen_width-20蓝色↑/↓ 控制球10×10白色起始速度幅值 2随机方向球拍移动每帧 5 像素受窗口上下边界限制上下反弹撞到窗口上下沿时dy取反拍面反弹撞拍时dx -dx * 1.1dy * 1.1即双向加速 10%出界处理球离开左右边界即调用reset()回到中心暂无计分显示帧节奏主循环每帧delay(16)毫秒完整演进过程可用 git 还原查看——五次自动提交分别是14799cb、b20bcee、f4ceed4、2c645c9、693faf4每一条都对应一次需求 → 编辑 → 提交的闭环git log --oneline git show b20bcee # 查看类 主循环这一大步的完整 diff背后机制aider 是如何完成这一切的从这段实录可以提炼出 aider 的三个关键设计也都能在当前仓库源码里找到实现依据Edit block 是 LLM 与文件系统之间的结构化协议。模型按固定格式输出文件名 分界块aider 负责解析与落盘。不同编辑格式对应不同的 coder 实现模块全部位于 aider/coders如 wholefile_coder.py 针对整文件重写、editblock_coder.py 系列针对 SEARCH/REPLACE 局部替换、editblock_prompts.py 则通过系统提示词约束模型严格遵循该协议。文件准入是双向的、有确认的。会话只允许编辑已加入 chat 的文件当模型要创建新文件或修改会话外文件时aider 会弹出确认源码见 base_coder.py 的allowed_to_edit()与check_for_dirty_commit()。这保证了 AI 不会在你不知情时乱动磁盘也解释了 transcript 中每一条确认行。每次成功应用后自动提交。aider 会基于本轮对话内容而非简单 diff让模型撰写提交信息随后执行 git commit消息生成与执行见 repo.py 的get_commit_message()与 repo.py 的 commit 主体包含可选aider:前缀与 Co-authored-by 归属逻辑。因此每轮对话都是一个可git revert的单元配合/undo还能丢弃最近一轮 aider 提交见 commands 文档。对 pong 这类骨架 → 玩法 → 调参的迭代任务这种机制尤其顺手每一轮自然语言请求恰好沉淀为一个语义清晰的 commit随时可以回退到球速正常球拍加大之前的版本去对比手感。复现与延伸建议要在本地复现完整会话新建空目录并git init进入后运行aider依次发送 transcript 中的五条指令即可——aider 会引导你走一遍创建确认、git add 确认和自动提交。更完整的 git 协作约定可参考 git.md。想继续往下玩可以从 transcript 里尚未完成的清单出发LLM 在第一轮规划的 10 步中计分与分数显示两项目前只实现了出界重置而未真正计分。这类续写正是 aider 的典型用法直接再发一条需求例如add a score display, and make the game end when someone reaches 10 points而若想让球拍反弹更有角度感可考虑在collide_with中根据撞击点在球拍上的相对位置修改dy把-dx * 1.1的纯水平反弹升级为带入射角度的反弹——每条这样的需求都会和 transcript 中一样以一个可追踪的 aider 自动提交结束。小结这段 pong 实录虽然只写了约百行游戏代码却把 aider 的完整工作流演示得淋漓尽致从空目录建起新文件、按 edit block 批量应用多文件改动、遇到会话外文件时请求确认、把每轮调优固化成独立 commit。对照 pong.md 原文与 aider/coders 下各类 coder 的源码实现你能清楚看到提示词格式约束 → 结构化解析 → 权限确认 → 自动提交这条 AI 结对编程流水线是如何落到每一行代码的。掌握了这套交互模式无论写游戏还是改业务代码你都可以像示例中那样把大需求拆成自然语言小步让 git 历史替你记录每一次 AI 驱动的变更。【免费下载链接】aideraider is AI pair programming in your terminal项目地址: https://gitcode.com/GitHub_Trending/ai/aider创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考