怎么用Python写一个50到100行的井字棋小游戏
本文详细介绍如何使用Python在50到100行代码内构建一个完整可运行的控制台双人井字棋小游戏,涵盖数据结构设计、坐标映射、输入防错校验与胜负判定逻辑。
井字棋(Tic-Tac-Toe)是验证编程语言基础语法、流程控制与数组操作的经典项目。使用 Python 编写一个完整的双人控制台对战井字棋,只需 60 到 80 行代码即可实现全部核心功能,包括棋盘数据初始化、字符界面的规范渲染、玩家坐标输入异常处理、获胜连线逻辑检测以及主循环状态切换。本文将从底层数据设计、完整实现源码到核心算法模块逐步拆解,帮助初学者清晰掌握实现方法。
一、棋盘数据设计与坐标映射
井字棋由 3×3 的九宫格构成。在 Python 中,最直观且高效的容器是嵌套列表(二维列表),每个元素保存单字符字符串:空格 " " 代表空位,"X" 代表先手玩家,"O" 代表后手玩家。初始化状态可写为:[[" " for _ in range(3)] for _ in range(3)]。
需要特别注意玩家输入与内存索引的映射关系:普通玩家习惯使用从 1 开始的行号和列号(1 至 3),而 Python 列表的索引起始点为 0(0 至 2)。因此程序在接收用户输入后,必须统一下调 1 位转换为 0-based 坐标,再访问底层数据单元。
二、完整可运行代码实现(共68行)
下面是一份完整的 Python 控制台井字棋源码。代码总长度为 68 行,完全落在 50 到 100 行的标准要求内。无需安装第三方依赖,仅使用 Python 标准库内置功能即可在终端中直接运行。
# -*- coding: utf-8 -*-
# 文件名: tictactoe.py (总长: 68行)
def print_board(board):
print("\n 1 2 3")
for r in range(3):
row_str = " | ".join(board[r])
print(f"{r + 1} {row_str}")
if r < 2:
print(" ---------")
print()
def check_winner(board, player):
# 检查横向 3 行与纵向 3 列
for i in range(3):
if all(board[i][j] == player for j in range(3)):
return True
if all(board[j][i] == player for j in range(3)):
return True
# 检查主对角线与副对角线
if all(board[i][i] == player for i in range(3)):
return True
if all(board[i][2 - i] == player for i in range(3)):
return True
return False
def is_board_full(board):
return all(cell != " " for row in board for cell in row)
def get_valid_move(player, board):
while True:
prompt = f"玩家 {player},请输入落子坐标 (行 列,如 1 2): "
try:
parts = input(prompt).strip().split()
if len(parts) != 2:
print("输入格式错误,请输入空格分隔的两个数字(例如: 2 3)。")
continue
r, c = int(parts[0]) - 1, int(parts[1]) - 1
if not (0 <= r < 3 and 0 <= c < 3):
print("坐标超出范围,行与列必须在 1 到 3 之间。")
continue
if board[r][c] != " ":
print("该位置已有棋子,请选择空位落子。")
continue
return r, c
except ValueError:
print("输入无效,请输入纯数字。")
def main():
board = [[" " for _ in range(3)] for _ in range(3)]
current_player = "X"
print("=== 井字棋游戏启动 (X 先手,O 后手) ===")
print_board(board)
while True:
r, c = get_valid_move(current_player, board)
board[r][c] = current_player
print_board(board)
if check_winner(board, current_player):
print(f"恭喜!玩家 {current_player} 赢得胜利!")
break
if is_board_full(board):
print("平局!棋盘已填满。")
break
current_player = "O" if current_player == "X" else "X"
if __name__ == "__main__":
main()
在任意终端(Terminal 或 PowerShell)中执行 python tictactoe.py 即可开始游戏。玩家交替输入行与列数字(用空格隔开),程序会自动判断输入有效性、刷新棋盘,并在达成连线或平局时退出。
三、核心逻辑与程序步骤拆解
井字棋程序的运作流程本质上是一个具备异常中断拦截的状态机循环。从初始化至终局,主循环按固定顺序推进:打印视图、收集输入、防护校验、状态落地、终局裁决与玩家角色翻转。
1. 界面渲染(print_board)
为了让玩家在无图形界面的控制台中精准落子,print_board 函数在每行前输出行号 1、2、3,并在顶部标明列号。通过 " | ".join(board[r]) 拼接每行单元格,并在相邻行之间插入横分隔线 ---------,保证棋盘美观对齐。
2. 输入防护与鲁棒性校验(get_valid_move)
用户输入是控制台程序最容易崩溃的环节。该函数采用 while True 搭配 try...except 结构,实施三道防线:
- 元素数量校验:通过
input().strip().split()切分输入字符串,确保正好得到两个输入项,防止用户只按回车或输入单个数字导致索引解构报错; - 范围边界校验:通过
int()转换并将数值减 1,验证行和列是否在0 <= r < 3与0 <= c < 3范围内,拦截任何越界输入; - 空位状态校验:检查
board[r][c] == " ",防止后手玩家覆盖先手玩家已占领的格子。
3. 获胜检测算法(check_winner)
井字棋的获胜条件是在横向、纵向或对角线上达成三子同色。九宫格棋盘仅有 8 条获胜连线。代码利用 Python 内置的高效生成器 all(...) 进行判定,逻辑简洁且运算耗时接近常数时间。
四、编写时容易忽略的细节与避坑指南
初学者常误写 board = [[" "] * 3] * 3。在 Python 中,乘号浅拷贝会导致外层列表的 3 个元素指向内存中完全相同的子列表对象。一旦修改第一行任意单元格,其他两行对应位置也会同时被改写。必须使用列表推导式 [[" " for _ in range(3)] for _ in range(3)] 保证每行都是独立的对象。
在主循环判定终局时,必须先调用 check_winner(),后调用 is_board_full()。因为在井字棋满盘(9步全部落完)的瞬间,最后一步落子有可能恰好达成三连胜。如果先判断平局,就会错误地将最后一步压哨获胜误判为和棋。
若用户输入 0 1,程序减 1 后得到 r = -1。Python 支持负数索引(board[-1] 等价于最后一行),如果仅仅检查 r < 3,程序不仅不会报错,反而会将棋子落在第三行,造成幽灵落子。因此必须严格限制双向边界:0 <= r < 3 and 0 <= c < 3。
五、代码架构总结与扩展方向
该 68 行实现的优势在于各功能职责高度正交解耦。每一个独立函数都有明确的输入、输出和契约规范,没有复杂的全局变量耦合,也没有不必要的面向对象封装模板代码。
在掌握了基础实现后,如果希望进一步提升程序功能,可考虑以下进阶扩展方向:
- 单人人机对战模式:编写一个
get_computer_move(board)函数,初步可用random.choice随机在空格中落子; - 完美防守 AI:引入博弈论中的极大极小算法(Minimax),在九宫格微小状态树上进行深度优先搜索,使计算机玩家达到永不落败的水平;
- 记分板与连续对局:在外层增加计数器与继续游戏提示,记录多轮对局中 X 玩家、O 玩家的获胜场次与平局数。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









