一 实现思路
我们五子棋的棋盘可以视为一个数据表格,
棋子视为表格中的一个单元格数据,
只能对棋盘(表格)没有棋子(没有单元格数据)的位置添加数据,
对棋子(表格中的数据)的八个方向进行判断,
如果相对的两个方向的相同数据总和大于或者等于五则游戏结束,该玩家胜利
二 基本逻辑
棋盘的完整结构如下方表格:
None | None | None | None | None |
None | 1.1 | 1.2 | 1.3 | None |
None | 2.1 | 2.2 | 2.3 | None |
None | 3.1 | 3.2 | 3.3 | None |
None | None | None | None | None |
用户实际看到的棋盘:
1.1 | 1.2 | 1.3 |
2.1 | 2.2 | 2.3 |
3.1 | 3.2 | 3.3 |
1.None的作用
None在此处的作用是当做边界来使用,如果在对八个方向查找时遇到边界则不在向该方向继续查找。
2.棋盘坐标
通过用户输入1.1这种方式来确定用户向要输入的坐标的位置,在用户展示时表现外圈的None。
3.检测逻辑
通过二维数组加减来实现对八个方向的判断,
例如以坐标2.2,
上坐标为1.2 (x-1,y)
下坐标为3.2 (x+1,y)
左上坐标为 1.1 (x+1,y+1)
如果检测的八个方向遇到第一个不与2.2相同的数据就不在向该方向检测,并返回有几个坐标与2.2坐标相同的数据
三 代码实现
# initialize_flag_box是用来生成棋盘,为了设置边界,二维数组的顶行和底行用None来表示,其他数组开头和结尾也采用None表示
# 这样就能使有效数组被None完全包裹
def initialize_flag_box():
# list1 用来生成棋盘所需的列表
list1 = [[None]*22, [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [None]*22]
sum1 = 0 # sum1用于统计棋盘的格子数量
for i1 in range(1, 21):
list1[i1].append(None) # 为行的开头设置None
for j1 in range(1, 21):
str_i = str(i1)
str_j = str(j1) # str_i,str_j是将数值类型的数字转换成字符串类型
if len(str_i) < 2:
str_i = " " + str_i
if len(str_j) < 2: # if 判断str_i,str_j的长度,如果不够两位则用一个空格补齐
str_j = str_j + " "
list1[i1].append(str_i+"."+str_j) # 将坐标添加进列表
sum1 += 1
list1[i1].append(None) # 为行的末尾设置None
return [list1, sum1] # 返回绘制完成的棋盘和格子数量
# chessboard是用来绘制棋盘,只为用户展示有效棋盘不展示边界
def chessboard(flag_box):
for i in range(1, 21):
print("|", end="") # 绘制棋盘头
print("-----|" * 20, end="") # 绘制棋盘头
print() # 换行
print("|", end="")
for j in range(1, 21):
print(flag_box[i][j], end="|")
print()
print("|", end="") # 绘制棋盘尾
print("-----|" * 20, end="") # 绘制棋盘尾
print() # 换行
# 分割线内的八个函数为八个方向的查找,有相同的就继续查找,遇到第一个不是的就返回,并发回有效的循环执行次数
# ---------------------------------------------------------------------------------------------------------
# def_under向下查找(此处八个函数主体结构相同)
def def_under(flag_box, x, y):
for i in range(1, 4):
# if继续向下查找,如果向下查找遇到不同的 或者 遇到边界值 则执行 if,返回有效的执行次数
if flag_box[x + i][y] != flag_box[x][y] or not flag_box[x + i][y]:
return i - 1
return 3 # 如果向下查找的三次全部成立则直接返回3,因为函数外已经检测一次,再加上坐标本身正好五个满足胜利条件
# def_above向上查找
def def_above(flag_box, x, y):
for i in range(1, 4):
if flag_box[x - i][y] != flag_box[x][y] or not flag_box[x - i][y]:
return i - 1
return 3
# def_right向右查找
def def_right(flag_box, x, y):
for i in range(1, 4):
if flag_box[x][y + i] != flag_box[x][y] or not flag_box[x + i][y]:
return i - 1
return 3
# def_left向左查找
def def_left(flag_box, x, y):
for i in range(1, 4):
if flag_box[x][y - i] != flag_box[x][y] or not flag_box[x - i][y]:
return i - 1
return 3
# def_above_left向左上角查找
def def_above_left(flag_box, x, y):
for i in range(1, 4):
if flag_box[x - i][y-i] != flag_box[x][y] or not flag_box[x - i][y-i]:
return i - 1
return 3
# def_under_right向右下角查询
def def_under_right(flag_box, x, y):
for i in range(1, 4):
if flag_box[x + i][y + i] != flag_box[x][y] or not flag_box[x + i][y + i]:
return i - 1
return 3
# def_above_right向右上角查询
def def_above_right(flag_box, x, y):
for i in range(1, 4):
if flag_box[x - i][y+i] != flag_box[x][y] or not flag_box[x - i][y+i]:
return i - 1
return 3
# def_under_left向左下角查询
def def_under_left(flag_box, x, y):
for i in range(1, 4):
if flag_box[x + i][y - i] != flag_box[x][y] or not flag_box[x + i][y - i]:
return i - 1
return 3
# ---------------------------------------------------------------------------------------------------------
# preliminary_inquiry函数是用来检测是否胜利,其中分为竖,横,左,右四部分,只要有一部分成立则返回1,表示胜利
# *********************************************************************************************************
def preliminary_inquiry(flag_box, x, y):
vertical = 1 # 统计竖(上查找和下查找) 中有几个与此坐标相同,此处初始化为1 是因为坐标自身与自身相同,所以也算一次,
side = 1 # 统计横(左查找和右查找) 中有几个与此坐标相同
right = 1 # 统计右(右上查找和左下查找) 中有几个与此坐标相同
left = 1 # 统计左(左上查找和右下查找) 中有几个与此坐标相同
# 先向下查找一个坐标,检测是否与坐标是否内容是否相同,如果相同则执行def_under查找函数
# 如果不同则不执行if中的函数,表示坐标向下没有可以相连的数据,然后再执行向上查找,同向下一样,
# 当上下都查找完毕后则判读上和下中一共有多少个与坐标一样的相同数据,如果大于等于5 则结束函数返回胜利,没有就继续执行
# 竖查找(其他三个与此处相同)
if flag_box[x][y] == flag_box[x+1][y]: # 向下查找一个坐标,检测是否与坐标是否内容是否相同,如果相同执行if
vertical += 1 # 竖的统计次数+1
vertical += def_under(flag_box, x+1, y) # 进入def_under函数,并接收返回值,将返回的次数加入竖统计中
if flag_box[x][y] == flag_box[x-1][y]: # 向上查找一个坐标,检测是否与坐标是否内容是否相同,如果相同执行if
vertical += 1 # 竖的统计次数+1
vertical += def_above(flag_box, x-1, y) # 进入def_above函数,并接收返回值,将返回的次数加入竖统计中
if vertical >= 5:
return 1
# 横查找
if flag_box[x][y] == flag_box[x][y-1]:
side += 1
side += def_left(flag_box, x, y-1)
if flag_box[x][y] == flag_box[x][y+1]:
side += 1
side += def_right(flag_box, x, y+1)
if side >= 5:
return 1
# 左斜查找
if flag_box[x][y] == flag_box[x-1][y-1]:
left += 1
left += def_above_left(flag_box, x - 1, y - 1)
if flag_box[x][y] == flag_box[x+1][y+1]:
left += 1
left += def_under_right(flag_box, x + 1, y + 1)
if left >= 5:
return 1
# 右斜查找
if flag_box[x][y] == flag_box[x-1][y+1]:
right += 1
right += def_above_right(flag_box, x - 1, y + 1)
if flag_box[x][y] == flag_box[x+1][y-1]:
right += 1
right += def_under_left(flag_box, x + 1, y - 1)
if right >= 5:
return 1
# *********************************************************************************************************
# 函数是用于player中的对用户输入的坐标进行检测,查看是否为纯数值,如果是则将其转换成整型,不是则返回输入无效
# 然后检查值是否在合法范围内
def num_detect(str_num):
num1 = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
for str1 in str_num:
if str1 not in num1:
return False
if int(str_num) > 20 or int(str_num) < 1:
return False
return True
# 函数是用于player中的对用户输入的坐标处是否存在棋子,如果存在则提示用户无法在此位置输入
def judgment_kiko(list_box, x, y):
if list_box[x][y] != " ■ " and list_box[x][y] != " □ " and list_box[x][y]:
return True
else:
return False
# 此函数为用户执行函数,首先判断用户输入的坐标是否有效,无效则重新进入while循环,
# 有效在传入num_detect中判断是否为有除数字以为的其他字符,如果没有则将其转换成数值类型,
# 然后检测坐标位置是否有其他棋子,有就提示无法在此位置输入,重新进入while
# 如果此处没有棋子在此位置输入棋子并 进行查找看齐相连的是否可以连成五个,如果是则胜利
# 不是则break结束循环,
def player(player_, list_box, flag, num1):
while True:
if num1[0] < 0: # 判断棋盘可用空间是否为0,如果为0表示棋盘已经下满,此局为平局
print("棋盘已满平局")
return 1
kiko = input(f"请{player_}输入坐标{flag}") # 输入坐标
# 检测坐标格式有效。
if kiko.count(".") == 1 and len(kiko) <= 5 and (kiko[1] == "." or kiko[2] == "."):
p1, p2 = kiko.split(".")
else:
print("输入有误")
continue
if num_detect(p1) and num_detect(p2): # 将p1, p2 传入num_detect函数,检测是否为纯数字,防止int转换时报错
px = int(p1)
py = int(p2)
# if检测坐标位置是否有其他棋子,有就执行else提示无法在此位置输入,没有就执行if内语句
if judgment_kiko(list_box, px, py):
list_box[px][py] = flag # 将棋子写入坐标
num1[0] -= 1 # 可用的棋盘空间-1
chessboard(list_box) # 绘制更新后的棋盘
if preliminary_inquiry(list_box, px, py) == 1: # 判断是否可以连成五个,如果是则胜利
print(f"{player_}胜利")
return 1
else: # 不是则结束循环
break
else:
print("无法在此位置输入")
else:
print("无效输入")
# play用于执行游戏,玩家1和2有一人胜利就终止游戏
# player_ 存储的是玩家姓名,
# list_box[0]存储的是棋盘信息, list_box[1],存储的是盘中可用格子的数量,
# list_flag存储的是棋子(黑棋和白旗)
def play(player_, list_box, list_flag):
chessboard(list_box[0]) # 游戏开始时,先绘制一遍棋盘为第一个下棋的用户参考坐标
num = [list_box[1]] # 用list方式存放棋盘中可用格子的数量,
while True:
# 将if 判断玩家1和玩家2 ,如果有一个人胜利就return终止,
if player(player_[0], list_box[0], list_flag[0], num) or player(player_[1], list_box[0], list_flag[1], num):
return 0
# initialize_flag_box函数用于生成是棋盘和棋子格子数量,list_flag_box里面存放的是棋盘和棋子格子数量
list_flag_box = initialize_flag_box()
# listflag 存放棋子
listflag = [" □ ", " ■ "]
# playerlist用于存放玩家姓名
playerlist = []
while True:
mode = input("请选择游戏模式,1.单机、2.联机")
if mode == "1":
print("恭喜你选择了单机模式,自娱自乐吧")
print("黑子先手")
mode1 = input("请玩家1选择棋子 ■ □ ,1.白 2.黑")
if mode1 == "1":
playerlist.append("玩家2")
playerlist.append("玩家1")
# 调用play函数,将棋盘信息,玩家姓名,和棋子传入
play(playerlist, list_flag_box, listflag)
break
elif mode1 == "2":
playerlist.append("玩家1")
playerlist.append("玩家2")
play(playerlist, list_flag_box, listflag)
break
else:
print("输入无效")
elif mode == "2":
print("待开发")
else:
print("输入无效")
四 功能拓展
通过socket实现两个人联机对战,有两种方式可以实现
第一种是数据在本地校验,确认无误后将坐标上传至服务器,然后服务器转送给另外一个客户端,
第二种是客户端直接向服务器发送坐标,然后在服务器进行计算,并将结果返回给双方客户端
第一种方式,对服务器压力小,是大型游戏和主流游戏都采用这种方式,
但缺点是计算在本地,数据容易被篡改,不安全
第二种方式,因为在服务器进行计算,所以安全系数较高,客户端无法篡改核心数据
缺点是服务器需要大量计算,压力较大
该拓展不确定会去实现,靠后续是否有时间,有基础的可以自行拓展一下