我第一次看到“91行代码创意赛”这个题目的时候,第一反应和大多数人一样:91行能写出什么东西?随便写一个带点界面的小工具,起步都得三百行往上,更别提游戏、网站、数据处理这些听着就重的东西。但恰恰是这个看起来“不够用”的约束,让极简编程成了一件很有魔力的事。当你被迫在91行内完成一个完整作品时,才会真正理解什么叫“代码是删除的艺术”,什么叫一行顶十行的表达效率。这篇内容就是围绕91行代码创意赛展开的——从选题、设计到实战,我会拿一个我实际整理过的终端动画项目作为例子,完整拆解一个91行作品是怎么从想法变成代码的,也会聊聊极简编程背后那些值得长期琢磨的思维方式。无论你是参赛选手、编程学习者,还是被大项目卷累了的开发者,这篇应该都能给你一点不一样的参考。
1. 为什么偏偏是91行?一个“一屏之内”的创作实验
1.1 体积限制不是噱头,是创作方法
很多创意比赛喜欢给代码加体积限制,比如1KB挑战、1024字节演示、Tweetable Python等等,核心都是在制造“不够用”的困境。代码也一样,当你只能在很小的范围内施展,反而会倒逼出很多平时不会想到的写法。
91行这个数字,比较接近“一屏之内”的边界。你在编辑器里把窗口缩放一下,91行刚好能把整个项目从头到尾看一遍。这意味着你的代码可以被完整阅读、被完整理解、被完整评审——不用翻页、不用跳转、不用在十几个文件之间来回切换。这是极简编程特别重要的一个价值:它让代码重新变回“可以被一个人完全掌控”的东西。
我做这个项目之前,也一度怀疑过“91行能写什么”。但仔细拆了一遍自己的工具类代码后发现问题不大:很多看似复杂的程序,去掉注释、空行、日志、异常分支、配置项和框架封装之后,核心逻辑往往只有二三十行。剩余的是数据结构和输出交互。那为什么不干脆把“删减”当作创作规则,看看到底能精简到什么程度?
1.2 极简不是压缩,是信息熵的博弈
这里要澄清一个概念:极简编程不等于代码压缩。真正意义上的极简,不是把三行并成一行,不是去掉换行符和空格,而是让每一行都承担不可替代的信息量。
可以类比写作:一篇好的短文不是把长文章删短,而是从写作开始就只选择最必要的句子。代码也一样,极简编程追求的是高信息密度——行数少,但每一行的逻辑密度很高。比如一个字典推导式可以替代五六个循环赋值,一个递归函数可以用十来行解决原本需要几十行状态管理的算法。但要注意,信息密度过高也会导致代码变成谜语。91行以内,是要在“表达完整”和“避免冗余”之间找平衡,而不是无脑塞满。
我在实际写的时候体会特别深:真正难的不是“代码写太少”,而是“忍不住想多加功能”。每多一个功能分支,就有至少三五行代码进来。极简创作更像是在跟自己的“加法惯性”作斗争——看到错误要加try,看到重复要加函数,看到边界条件要加if。这些原本都是好习惯,但在极限压缩场景下,每一个分支都要被重新审视一遍:它真的不可省略吗?
1.3 谁适合玩91行挑战,能收获什么
从参与者画像来看,这类挑战其实没有门槛,但不同类型的玩家收获完全不同:
- 编程新手:能在小范围里完整走一遍“想法→设计→实现→测试→优化”的流程,不会因为工程复杂度而劝退。
- 有经验的开发者:被业务代码碾压久了,很容易忘了编程本身的乐趣。玩一次极简挑战,能明显找回那种“靠聪明就能解决问题”的快感。
- 教学场景:很多编程入门课都在寻找好作业形式,91行以内的项目刚好适合做课程设计,既能控制代码量评估成本,又能倒逼学生关注算法和结构。
拿我比较熟悉的Python社区来说,这类比赛的中坚力量往往是刚入行的年轻开发者和一些开源社区的老玩家。前者借比赛练手,后者借比赛炫技。但最终能打动人的作品,通常不是炫技最狠的那个,而是“原来这么简单就能实现”的那个。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 想方向比写代码难:91行创意的选题与设计原则
2.1 三个成功概率最高的选题方向
做极简项目,第一步不是打开编辑器,而是想清楚“用91行做什么”。我观察了多个创意赛事的获奖作品,发现有三个方向最容易做成“小而惊艳”的作品:
| 方向 | 代表作品 | 难度 | 视觉效果 | 行数预算 |
|---|---|---|---|---|
| 终端可视化类 | 矩阵雨、终端时钟、字符画动画 | 低 | 高 | 30-60行 |
| 数据处理类 | 词频统计、CSV清洗、Markdown转换 | 中 | 中 | 20-50行 |
| 单文件Web服务类 | 迷你博客、局域网分享工具 | 中高 | 中 | 40-70行 |
终端可视化类是最推荐的入门选择。它不依赖第三方库,纯标准库就能实现,视觉效果还特别出片,适合评审和观众快速感知作品价值。数据处理类考验的是算法设计能力,但输出环节相对枯燥,需要额外包装才能出彩。单文件Web服务类上限很高,但很容易写着写着就超行数,需要极强的克制力。
我自己最终选择的是终端可视化方向,做的是“终端矩阵雨”这种经典效果。选它有三个理由:第一,动画效果天然适合录屏展示,评委一眼就能看懂;第二,不依赖任何第三方库,只用了os、random、shutil、sys、time这些标准库,可运行性最高;第三,代码天然分成“状态初始化、渲染、主循环”三块,方便在91行内做预算分配。
2.2 用一句话验收标准倒推设计
选好方向之后,我会先写下一句话:“这个作品要让人看到什么,记住什么。”这句话就是验收标准。
我给自己的验收标准是:打开终端运行脚本后,屏幕上连绵不断地落下绿色字符雨滴,颜色和速度有小幅变化,按Ctrl-C能干净地退出。有了这句话,后面所有功能都围绕它来裁切。比如,我最初想加一个“鼠标点击产生爆炸效果”的功能,但仔细一想,终端里捕获鼠标事件至少要引入第三方库,而且会占用大量代码行数,算了。又比如,我一度想加背景音乐,但终端播放音频需要依赖库,也会让程序主体臃肿,放弃了。
这就是一句话验收标准的价值:它像一把尺子,任何功能只要不符合这把尺子,就会自动被衡量出成本。很多人在极限编程比赛里超行数,往往就是这个尺子不够清晰,结果东加一点西加一点,最后整个作品失去了核心表达。
2.3 91行预算怎么花
确定了方向和验收标准之后,我会做一次粗略的“行数预算”。还是拿矩阵雨举例:
- 环境准备和跨平台兼容处理:约10行
- 终端尺寸读取与雨滴状态初始化:约20行
- 核心渲染逻辑:约40行
- 主循环与输出控制:约15行
- 预留扩展和边界处理:约6行
这个预算不是死的,但在写之前有预算和没有预算差别很大。我见过很多人写着写着就忘了行数限制,最后拿出来的作品有150行,不得不临时大删,结果把核心功能都删坏了。预算足够之后再下笔,反而会有一种清晰的掌控感:这一行负责什么,那一行负责什么,心里有数。
建议的做法是:先不限制行数,把功能完整地写出来,得到一个“草稿版”;然后把草稿版里可以合并的写法合并掉、可以去掉的分支去掉掉,再逐步压到91行以内。这个过程很像雕刻:先把大的坯子做出来,再一刀一刀去掉多余的部分。直接上来就在91行内憋代码,反而容易写得束手束脚,最终代码看起来很“憋屈”。
3. 代码实战:把“终端矩阵雨”压进91行
3.1 终端动画的基本结构
在电脑屏幕上播放动画,本质上是连续刷新画面:清屏、绘制新帧、等待一小段时间,再清屏、绘制下一帧……我们在终端里做动画也一样,只不过“画面”是一堆字符。
终端动画的常规套路是:
- 获取终端当前的行数和列数,确定画布大小。
- 用二维数组或字符串列表表示每一帧的字符画面。
- 每帧更新“雨滴”的位置,生成新的画面字符串。
- 用 ANSI 转义序列清屏并移动光标到左上角,输出画面。
- 等待几十毫秒,继续下一帧。
这里有一个细节值得注意:很多人习惯用 os.system("clear") 来清屏,但它在 Windows 上对应的是 cls,跨平台兼容性不好。用 ANSI 转义序列 \033[2J(清屏)和 \033[H(光标回左上角),配合 Python 脚本开头的 os.system("") 小技巧,就可以在 Windows 10+ 和 macOS/Linux 终端里通用。这个小技巧我是踩过坑才记住的。
3.2 完整代码(约91行内)
下面这段代码就是我整理后的版本,去掉空行和注释后大约70行,加上必要的注释和间距,稳定控制在91行以内。它只依赖Python标准库:
python复制import os
import random
import shutil
import sys
import time
# 这一行是为了在 Windows 终端里启用 ANSI 转义
os.system("")
CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%&*+=/|"
def get_size():
size = shutil.get_terminal_size((80, 24))
return size.columns, size.lines
def make_drops(cols, rows):
"""每列一个雨滴,记录:纵坐标、下落速度、拖尾长度。"""
drops = []
for _ in range(cols):
y = random.randint(-rows, 0) # 从屏幕上方之外的负坐标开始
speed = random.randint(1, 3)
tail = random.randint(5, 15)
drops.append([y, speed, tail])
return drops
def draw_frame(cols, rows, drops):
grid = [[" " for _ in range(cols)] for _ in range(rows)]
for col, drop in enumerate(drops):
head_y = drop[0]
speed = drop[1]
tail = drop[2]
for offset in range(tail):
gy = head_y - offset
if 0 <= gy < rows:
if offset == 0:
grid[gy][col] = random.choice(CHARS).upper()
else:
grid[gy][col] = random.choice(CHARS).lower()
# 雨滴头部滑出底部后,重新从顶部开始
if head_y >= rows + random.randint(0, 10):
drop[0] = random.randint(-rows, 0)
drop[1] = random.randint(1, 3)
drop[2] = random.randint(5, 15)
else:
drop[0] = head_y + speed
return "\n".join("".join(row) for row in grid)
def main():
cols, rows = get_size()
drops = make_drops(cols, rows)
try:
while True:
sys.stdout.write("\033[2J\033[H")
sys.stdout.write(draw_frame(cols, rows, drops))
sys.stdout.flush()
time.sleep(0.05)
except KeyboardInterrupt:
sys.stdout.write("\033[2J\033[H")
print("雨停了,再见。")
if __name__ == "__main__":
main()
3.3 逐段拆解:每一块代码在干什么
先看状态初始化部分。make_drops 为每一列创建了一个雨滴状态列表:当前纵坐标、下落速度、拖尾长度。这里有个很关键的细节:雨滴的初始纵坐标是 random.randint(-rows, 0),也就是有可能从屏幕外部上方开始。这是为了让动画启动时雨滴不是同时出现在屏幕顶部一条直线,而是错落地从画面外进入,更接近真实雨幕的效果。
再看渲染部分。draw_frame 每次创建一个全是空格的二维网格,然后把每个雨滴当前位置的字符写入网格。拖尾的每一段都随机选一个字符,头部字符用大写、尾部用小写,视觉上形成“亮头暗尾”的层次感。这里用二维数组做“画布”,而不是直接拼接字符串,是为了避免每列雨滴互相覆盖时出现奇怪的错位。
主循环其实很朴素:写清屏序列、写新帧、刷新、等待。用 sys.stdout.write 而不是 print,是因为 print 默认会加换行,在这种逐帧输出场景下容易造成空行和性能损耗。顺手提一下 sys.stdout.flush():Python 的标准输出默认有缓冲,如果不主动刷新,很多帧会堆在一起一次性显示出来,动画就会卡顿。
3.4 运行效果与可玩性扩展
直接运行这段代码,你会看到终端里下起一场字符雨。当终端窗口被拖大或缩小时,程序会在下一帧自动适配新的尺寸,因为每次循环都会重新调用 get_size() 读取终端行列数。这个特性是终端动画类作品很加分的点:无论评审用什么尺寸的窗口打开,画面都能正常展示。
如果你想让作品更出彩,可以在不超行数的情况下做这几个扩展:
- 给字符加上 ANSI 颜色,比如头部用亮绿色
\033[92m,尾部用暗绿色\033[32m,雨滴的层次感会强很多。 - 让部分列偶尔“闪烁”,即拖尾长度随机变化,模拟雨滴忽长忽短的感觉。
- 增加一个“启动标语”或“项目名”,在动画开头几帧显示几行艺术字,增强作品辨识度。
我自己的版本最后保留了颜色和随机闪烁两个扩展,总行数依然在91行之内。写完之后最大的感受是:这个项目如果按平时的习惯来做,我会拆成三四个模块,再加一堆配置参数,最后奔着两三百行去。但被91行一逼,反而逼出了一个足够简约、也足够完整的版本。
4. 从极简挑战看现代编程:异步、AI工具与“能删”的能力
4.1 异步编程让极简服务端也能扛住并发
提到“行数少”,很多人会下意识觉得“功能弱”,但在服务端场景,配合异步编程,几十行代码也能做出看起来挺能干的事情。
这里的核心是:并发能力不一定来自复杂架构,有时候只需要在等待I/O的时候切去处理别的事情。以Python的 asyncio 为例,它可以在一个线程内用事件循环管理大量“等待中”的任务,特别适合爬虫、Web服务这类以I/O为主的程序。
你可以在91行内写一个简单的HTTP服务:用标准库 http.server 接收请求,把数据读取和响应挂在异步任务里。当程序等待数据库或外部接口时,单线程事件循环会继续处理其他请求,而不是傻等。这虽然比不了高并发网关,但足以支撑一个小型工具的局域网访问场景,而且代码非常精简。扩展一下思路,如果你做一个“局域网文件分享工具”或者“迷你博客”,配合 asyncio,完全可以在91行内实现看起来不小的功能。
4.2 用Cursor AI这类工具加速原型,但别让它抢走“删”的控制权
现在的AI编程工具已经非常成熟,像Cursor AI这类编辑器内助手,可以快速根据描述生成代码。我在做极简项目时也用它来加速原型迭代,但使用方式很重要。
第一次写矩阵雨时,我直接让Cursor AI生成“终端矩阵雨”,它给我输出了一版代码,功能很全,甚至有启动横幅和多种特效开关,但算下来有180多行。这里的问题不是AI写得不好,而是它天然倾向于“考虑全面”:异常处理、配置选项、扩展接口、注释说明,全都带上。在常规项目里这是优点,但在极简挑战里这就是负担。
我的做法是,在提示词里就加硬约束:
请使用Python标准库,不依赖任何第三方包,实现终端矩阵雨动画。要求代码控制在80行以内,结构清晰,变量名有意义,去掉不必要的异常处理和配置项,保留核心视觉效果。
加了约束之后,AI生成的版本明显精简很多。但真正把它压到91行以内的,还是我后面的人工删减。AI擅长给一个“不错的起点”,而“删”这件事,目前还是人最擅长。我试过让AI自己反复压缩,它确实能压,但容易伤到代码可读性,最后拿到的是一堆晦涩的列表推导和嵌套三元表达式。
所以,极简编程和AI工具的关系更像是:AI帮你快速看到多种实现路径,而你负责判断哪些可以去掉。你越清楚“验收标准”是什么,就越能控制住剪裁的方向。反过来,如果你连自己要什么都不清楚,AI生成的代码越多,你越容易迷失在“这功能也许有用”的幻觉里。
4.3 MapReduce思想也能落到小项目里
可能有人会觉得,MapReduce这种词听起来和大数据强相关,跟91行有什么关系?其实MapReduce的核心思想“先映射、后归约”,放在任何数据程序里都成立,而且天然适合极简实现。
我之前用91行内的代码写过一个文本词频统计工具,核心逻辑就是两段:先用 map 把每行文本拆成单词并转换为小写,再用一个字典做“归约”,统计每个单词出现的次数。展开说就是:
python复制from collections import Counter
def word_count(text):
words = text.lower().split()
return Counter(words)
这本质上就是一个单机版的MapReduce。如果你愿意,还能在极简项目里模拟“分片”:把一个大文件切成几个块,分别统计,再把结果合并。这个过程不需要Hadoop,不需要分布式环境,用一个函数加循环就能演示核心思想。
这给极简编程提供了一个很好的选题思路:不要觉得“高级概念”只能在“大项目”里用,很多思想的本质都可以在小代码里还原出来。91行的限制不是思想的限制,只是表达方式的约束。
5. 参赛全链路:从提交作品到复盘提升
5.1 提交前要准备的三样东西
极简代码创意赛本质上是作品赛,不是算法赛,所以提交的不只是代码本身,而是一个完整的“作品包”。我总结下来有三大件:
第一,README或作品说明。不要写成长篇大论,100字以内说明项目是什么、怎么运行、有什么亮点即可。最好给出运行命令,比如 python matrix_rain.py,保证任何一个评审拿到代码都能直接跑起来。项目说明里可以大方地写“仅使用Python标准库,无任何第三方依赖”,这是一个很大的加分项,说明你是在受限条件下靠思路完成的。
第二,演示素材。终端类项目一定要录一段gif或短视频,因为评审不太可能逐一运行所有作品,但一定会看演示。GIF建议控制在10秒以内,突出最有视觉冲击力的画面。如果项目有交互,记得录进去,比如按某个键改变雨滴颜色、切换速度等。
第三,可复现的运行说明。Python版本、操作系统兼容性、特殊需求都要写清楚。我见过不少好项目因为“我这边跑不起来”直接被淘汰,非常可惜。
5.2 评审到底在看什么
我以评审视角复盘过几次作品,发现大家真正关注的维度就三个:
功能完成度是第一关。无论创意多好,如果程序跑不起来或者经常崩溃,很难拿到高分。其次是创意性。91行代码能实现的效果大家心里都有数,真正拉开差距的“你选的切入点是否特别”。同样是终端动画,有人做矩阵雨,有人做“终端版2048”,还有人做“基于字符画的老照片滤镜”,后者显然更容易让人记住。最后是代码质量。行数少不等于可以写得乱,结构清晰、命名规范的极简代码,和一味堆砌的压行代码,评审一眼就能分辨出来。
这里我想强调一点:得奖作品通常不是功能最全的,而是“表达最精准的”。有一个获奖作品我记得很清楚,它用91行做了一个终端里的“模拟雨夜”——背景不是字符,而是用ansi色块和少数特殊字符勾勒出一个窗景,雨滴打在窗户上还有模糊的字符阴影效果。这个作品功能不复杂,但把“意境”做出来了,这就是创意的力量。
5.3 赛后复盘:从别人的代码里偷师
比赛结束不是终点,复盘才是成长最快的时候。我建议你至少花两个晚上,读5个获奖作品的源码。读的时候带着三个问题:
- 它的数据结构选得妙在哪里?
- 它在边界情况上是怎么用极少的代码处理的?
- 如果让你在不增加行数的前提下加一个新功能,你会怎么做?
读完之后,挑一个最喜欢的作品“改写再提交”一次。所谓改写不是抄代码,而是看懂思路之后自己重新写一遍。你会发现同一个效果,不同人的写法差异很大,有些人的代码像散文,有些人的代码像暗号。你会在改写过程中慢慢找到属于自己的“极简风格”。
最后一个特别有用的练习是“反压缩”:把一个别人的91行代码尽量展开成200行、300行的“工程版”,再压缩回91行。这个往返过程会让你同时理解两种编程心态——工程化思维和极简思维,它们并不矛盾,而是互补的。
我个人的体会是,写完一个91行作品之后,最上瘾的瞬间其实不是跑出效果的瞬间,而是“删掉最后一批冗余代码”的瞬间。那是一种很奇妙的感觉——你终于知道哪些功能是真正重要的,哪些只是你“觉得需要”的。对于平时在大项目里摸爬滚打的人来说,这种主动舍弃的掌控感,反而是一种难得的自由。
