Python动态生成五子棋棋盘:从基础循环到灵活扩展
1. 从一行代码到动态棋盘:为什么我们需要“活”的棋盘
大家好,我是老张,一个写了十几年Python的老码农。今天想和大家聊聊一个特别有意思的小项目:用Python动态生成五子棋棋盘。你可能觉得,打印个棋盘有什么难的?网上搜一下,不是满屏都是固定15×15的代码吗?确实,我当年给学生布置这个作业时,也以为是个简单的循环练习。直到我看到我室友交上来的作业——一个硬编码了15行打印语句的“棋盘”,我才意识到,很多初学者其实并没有真正理解“动态”和“可扩展”这两个词在编程里的分量。
想想看,如果你写的棋盘代码只能打印15×15,那它的价值就非常有限。万一老师下次要求打印9×9的棋盘呢?或者你想做个手机上的小游戏,需要适应不同屏幕尺寸的棋盘呢?难道要重新写一遍代码吗?这显然不是我们学习编程的初衷。我们写代码,尤其是用Python这种优雅的语言,追求的是“一劳永逸”的解决方案。一个真正好的棋盘生成程序,应该像乐高积木一样,给你几个基础模块(循环、条件判断),你就能拼出任意尺寸的棋盘,这才是编程的乐趣所在。
所以,这篇文章我们不只讲怎么“打印”一个棋盘,我们要深入骨髓地聊聊怎么“生成”一个棋盘。我们会从一个最基础、最“笨”的版本开始,一步步迭代,把它变成一个灵活、健壮、可以轻松应对各种需求的小工具。这个过程里,你会看到循环结构如何从僵化变得灵动,代码如何从“能用”变得“优雅”。相信我,跟着走完这一趟,你对Python基础的理解,绝对会上一个台阶。
2. 理解棋盘的本质:先拆解,再构建
在动手写代码之前,我们得先像个木匠一样,把“棋盘”这个成品拆解成最基本的零件。五子棋的棋盘,在控制台里显示,其实就是由一些特定的线条符号,按照行和列的规则排列组合而成的图形。
2.1 认识我们的“建筑材料”:Unicode制表符
我们不用任何图形库,就在黑乎乎的控制台里画图,靠的就是一组神奇的字符——制表符。它们看起来像线条,能组合成边框和交叉点。原始文章里给了我们一套好用的符号:
┌、┬、┐:这三个是顶边的零件。┌是左上角,┬是顶部的中间连接点,┐是右上角。├、┼、┤:这三个是中间行的零件。├是左边的连接点,┼是中间的十字交叉点,┤是右边的连接点。└、┴、┘:这三个是底边的零件。└是左下角,┴是底部的中间连接点,┘是右下角。
你可以把它们想象成拼图的边缘块和中间块。一个3x3的迷你棋盘长这样:
┌─┬─┐
├─┼─┤
└─┴─┘
看,第一行用了┌、┬、┐,中间行用了├、┼、┤,最后一行用了└、┴、┘。中间的横线和竖线,其实是由这些符号自带的线条视觉上连接而成的。
2.2 棋盘的结构化思维:三明治模型
理解了符号,我们来看结构。一个N行N列的棋盘(交叉点数量),在打印时,需要打印N+1行字符行(因为线条本身也占一行)。我们可以把它分成三个逻辑部分,就像做一个三明治:
- 第一行(顶部面包):永远是
┌开头,中间重复N-1个┬,最后以┐结尾。 - 中间行(夹心层):这部分会有
N-1行。每一行都是├开头,中间重复N-1个┼,最后以┤结尾。 - 最后一行(底部面包):永远是
└开头,中间重复N-1个┴,最后以┘结尾。
这个模型是理解动态生成的关键。无论棋盘是5×5还是50×50,这个“三明治”结构是不变的,变化的只是中间重复的次数(N-1)。有了这个模型,我们就能把问题从“画一个复杂的图”简化成“按照模板填充重复单元”。
3. 动手实现第一个版本:硬编码的“静态”棋盘
我们先别急着追求“动态”,把原始文章里的代码拿过来,看看一个能工作的基础版本长什么样。这就像学做菜,先严格按照菜谱做一遍,知道成品是什么味道。
# 第一行:顶部边框
print("┌", end="")
for y in range(1, 14):
print("┬", end="")
print("┐")
# 第2到14行:中间部分,共13行
for s in range(13):
print("├", end="")
for y in range(1, 14):
print("┼", end="")
if y == 13:
print("┤")
# 第15行:底部边框
print("└", end="")
for y in range(1, 14):
print("┴", end="")
print("┘")
运行这段代码,你会在控制台看到一个标准的15×15五子棋棋盘。代码逻辑非常清晰,完全对应了我们刚才说的“三明治模型”。但是,请你盯着代码里的数字看:14出现了好几次,13也出现了。为什么是14?因为15条线形成了14个格子区间,在打印连接符时,两个边角之间的连接点数量就是列数-1。这里的14和13,就是为15×15棋盘“量身定做”的。
这个版本的“坑”在哪里?
- 数字魔法:代码里直接写死了
14和13。如果不加注释,过两个月你自己都忘了这些数字代表什么。 - 修改困难:想改成9×9棋盘?你得把所有
14改成8,所有13改成7。容易漏,也容易错。 - 代码重复:打印第一行、中间行、最后一行的逻辑非常相似,都是“左边符号 + 重复中间符号 + 右边符号”,但我们却写了三遍几乎一样的循环结构。
这个版本完成了任务,但它是“死”的。作为一个有追求的开发者,我们绝不能止步于此。我们的目标是,写一段“活”的代码。
4. 迈向灵活的关键一步:引入变量控制尺寸
要让代码“活”起来,第一步就是把那些硬编码的数字,替换成有意义的变量。我们定义一个变量size,来表示棋盘的大小(即线条数,标准五子棋是15)。那么,交叉点的数量就是size,而连接符的重复次数就是size - 1。
def print_gobang_board(size=15):
"""打印指定大小的五子棋棋盘
Args:
size (int): 棋盘的尺寸,即横纵线条数。默认为15。
"""
# 计算中间需要重复打印的连接符次数
repeat_count = size - 1
# 1. 打印顶部边框
print("┌", end="")
for _ in range(repeat_count):
print("┬", end="")
print("┐")
# 2. 打印中间部分,共有 size-1 行
for _ in range(repeat_count):
print("├", end="")
for _ in range(repeat_count):
print("┼", end="")
print("┤") # 注意这里,每行结束都要换行
# 3. 打印底部边框
print("└", end="")
for _ in range(repeat_count):
print("┴", end="")
print("┘")
# 试试不同的尺寸
print("=== 9x9 棋盘 ===")
print_gobang_board(9)
print("\n=== 15x15 棋盘(标准)===")
print_gobang_board()
print("\n=== 20x20 超大棋盘 ===")
print_gobang_board(20)
看,我们只是简单地引入了一个size变量和一个计算出来的repeat_count,代码的威力就大大增强了!现在,你可以通过一个参数,轻松生成任意尺寸的棋盘。print_gobang_board(9)生成9路棋盘,print_gobang_board(20)生成20路棋盘,清晰又直观。
这里有个小优化:你注意到中间部分的循环了吗?原始代码里有一个if(y==13): print("┤")的判断,用来在循环结束后打印右边的符号并换行。其实这个判断是多余的,因为内层循环打完repeat_count个┼后,必然要执行print("┤")。所以我们可以直接把它移到内层循环外面,代码更简洁。我上面写的版本已经做了这个优化。
5. 追求优雅:消除重复代码,提炼核心模式
虽然上一个版本已经很好用了,但仔细看,三段打印的逻辑还是太像了。作为一个“懒”程序员,我们总想着“Don't Repeat Yourself”(DRY原则)。我们能不能只写一次打印“一行”的逻辑,然后通过传入不同的“边角符号”和“中间符号”来生成顶行、中间行和底行呢?
当然可以!我们来抽象一下。打印任何一行,其实就做三件事:
- 打印一个左端符号(可能是
┌、├、└)。 - 重复打印 N次 一个中间符号(可能是
┬、┼、┴)。 - 打印一个右端符号(可能是
┐、┤、┘),然后换行。
那么,我们可以写一个通用的函数来做这件事:
def print_line(left, middle, right, repeat):
"""打印棋盘的一行
Args:
left (str): 行首字符,如 '┌', '├', '└'
middle (str): 行中间重复的字符,如 '┬', '┼', '┴'
right (str): 行尾字符,如 '┐', '┤', '┘'
repeat (int): 中间字符需要重复的次数
"""
print(left, end="")
for _ in range(repeat):
print(middle, end="")
print(right)
def print_gobang_board_elegant(size=15):
"""优雅版:打印指定大小的五子棋棋盘"""
repeat = size - 1
# 顶部行
print_line("┌", "┬", "┐", repeat)
# 中间行
for _ in range(repeat):
print_line("├", "┼", "┤", repeat)
# 底部行
print_line("└", "┴", "┘", repeat)
怎么样?代码是不是一下子清爽多了?print_gobang_board_elegant这个主函数,就像一份清晰的说明书:先打印顶行,再循环打印中间行,最后打印底行。所有的脏活累活都交给了print_line这个通用函数。这种“分离关注点”的思想,是写出好代码的关键。以后如果你想修改打印的样式(比如把线条加粗),只需要改print_line这一个地方就行了。
6. 更进一步:从“打印”到“生成”与“返回”
到目前为止,我们的函数都是直接print到控制台。但在实际项目中,我们可能不仅仅想“打印”它,还想“得到”它。比如,我们想把这个棋盘字符串保存到文件里,或者通过网络发送给另一个程序,或者作为图形界面组件的文本标签。这时候,直接打印就不够灵活了。
更好的做法是,让函数“生成”棋盘字符串,然后由调用者决定怎么处理它(打印、保存、发送等)。这涉及到函数设计的一个理念:一个函数最好只做一件事。生成字符串是一件事,打印到屏幕是另一件事。
def generate_gobang_board(size=15):
"""生成指定大小的五子棋棋盘字符串
Args:
size (int): 棋盘尺寸。
Returns:
str: 代表棋盘的完整字符串,每行以换行符'\n'结尾。
"""
def make_line(left, middle, right, repeat):
"""构造一行的字符串"""
return left + middle * repeat + right + '\n'
repeat = size - 1
board_str = ""
# 构造顶部行
board_str += make_line("┌", "┬", "┐", repeat)
# 构造中间行
for _ in range(repeat):
board_str += make_line("├", "┼", "┤", repeat)
# 构造底部行
board_str += make_line("└", "┴", "┘", repeat)
return board_str
# 用法一:打印到控制台
board_15 = generate_gobang_board(15)
print(board_15)
# 用法二:保存到文件
with open('gobang_board.txt', 'w', encoding='utf-8') as f:
f.write(generate_gobang_board(10))
# 用法三:在Web应用或GUI中显示
# 假设有一个文本显示组件
# text_widget.set_text(generate_gobang_board(19))
这个版本实现了质的飞跃。generate_gobang_board函数成了一个纯粹的生产者,它不关心产品怎么被消费。内部我们用了字符串乘法middle * repeat来代替循环拼接,这是Python里更高效、更Pythonic的写法。函数返回一个完整的字符串,调用者拥有了完全的控制权。这种设计让代码的复用性达到了最高。
7. 应对真实场景:错误处理与边界情况
一个健壮的程序,必须考虑用户可能的各种“骚操作”。如果用户传入一个负数怎么办?传入一个非整数怎么办?传入一个1(最小的有效棋盘)又怎么办?我们的函数能正确处理吗?
我们来给函数加上一些防御性代码:
def generate_gobang_board_robust(size=15):
"""生成指定大小的五子棋棋盘字符串(健壮版)
Args:
size (int): 棋盘尺寸,必须为大于等于2的整数。
Returns:
str: 代表棋盘的完整字符串。如果参数无效,返回错误信息字符串。
Raises:
ValueError: 如果size不是整数或小于2。
"""
# 类型检查
if not isinstance(size, int):
raise ValueError(f"棋盘尺寸必须为整数,但传入的是 {type(size).__name__}")
# 有效性检查
if size < 2:
raise ValueError(f"棋盘尺寸必须至少为2,当前传入的尺寸为 {size}。尺寸为1无法构成有效的棋盘区域。")
# 对于非常大的尺寸,给予友好提示(可选)
if size > 50:
print(f"警告:您指定的棋盘尺寸 {size} 较大,生成的字符串可能很长。")
def make_line(left, middle, right, repeat):
return left + middle * repeat + right + '\n'
repeat = size - 1
board_str = make_line("┌", "┬", "┐", repeat)
# 当size=2时,中间部分的行数为 repeat=1,循环一次
for _ in range(repeat):
board_str += make_line("├", "┼", "┤", repeat)
board_str += make_line("└", "┴", "┘", repeat)
return board_str
# 测试各种情况
try:
print(generate_gobang_board_robust(3)) # 正常小棋盘
print(generate_gobang_board_robust(1)) # 会触发 ValueError
except ValueError as e:
print(f"参数错误:{e}")
try:
print(generate_gobang_board_robust(2.5)) # 传入浮点数
except ValueError as e:
print(f"参数错误:{e}")
我们做了几件事:
- 类型检查:确保
size是整数,避免float类型导致奇怪的错误。 - 范围检查:棋盘至少需要2条线才能形成一个交叉点(1x1的格子)。
size=1时,repeat=0,我们的make_line函数依然能工作(middle * 0是空字符串),但生成的是一个单行单列的“点”,这没有棋盘的语义。所以我们规定size必须大于等于2,并在错误信息中明确告知。 - 友好提示:对于可能生成超长字符串的大尺寸(比如50以上),给出一个控制台警告,防止程序无响应或输出刷屏。这是一个提升用户体验的细节。
- 异常处理:使用
raise ValueError抛出明确的异常,而不是在函数内部静默地返回一个错误棋盘或None。这让调用者能够清楚地知道问题所在,并决定如何处理。
经过这番加固,我们的棋盘生成器就不再是一个“玩具”,而是一个可以在更严肃场景下使用的可靠工具了。
8. 扩展思考:不止于控制台,棋盘还能怎么用?
生成了一个漂亮的棋盘字符串,我们的旅程就结束了吗?当然不!这只是开始。这个字符串可以作为更多有趣功能的基石。让我分享几个我实际项目中用到的扩展方向:
方向一:集成到图形界面(GUI)
控制台棋盘是基础,但体验一般。我们可以用tkinter、PyQt或Pygame创建一个窗口,把棋盘字符串渲染成真正的图形线条和交叉点。这时,generate_gobang_board函数生成的数据(比如尺寸、交叉点坐标)可以转化为GUI库中的绘图指令。棋盘上的每个交叉点都可以绑定鼠标点击事件,从而实现落子功能。
方向二:作为游戏状态的核心表示
在一个完整的五子棋游戏程序中,棋盘不仅仅是用来“看”的,更是用来“算”的。我们通常用一个二维列表(列表的列表)来表示棋盘状态,比如board[row][col] = 0表示空位,=1表示黑子,=2表示白子。我们的打印函数,可以基于这个二维列表来生成带棋子的棋盘!比如,遍历二维列表,遇到1就打印●,遇到2就打印○,遇到0就打印┼(或保持为空位符号)。这样,打印函数就和游戏逻辑核心数据关联起来了。
方向三:网络对战与序列化 如果你在做网络对战的五子棋,棋盘状态需要通过网络传输。我们的棋盘字符串本身就是一种简洁的序列化格式。当然,更常见的做法是传输二维列表的JSON格式,但纯文本的棋盘在调试和日志记录时非常直观。你可以把每局棋的棋盘字符串记录到日志文件里,复盘的时候一目了然。
写代码就像搭积木,今天我们从最基础的循环开始,搭出了一个可以随意伸缩的棋盘生成器。在这个过程中,最重要的不是记住了那几个Unicode符号,而是体会了如何通过抽象和封装,把一段固定的代码,变成灵活、可重用、健壮的工具。下次当你再遇到类似“打印某种规则图形”的问题时,希望你能想起这个“三明治模型”和“分离变化与不变”的思路。编程的乐趣,就在于这种从具体到抽象,再从抽象解决无数具体问题的循环之中。
更多推荐


所有评论(0)