牛角棋项目实践1:牛角棋的定义和用python实现简单功能
牛角棋的定义:
有这样一种棋,叫牛角棋,也叫十三太保棋,整个棋盘是三角形,中间有横线和斜线,总共13个交叉点,也就是落子点。两个人比赛,一方拿两颗白棋,一方拿一颗黑棋。拿白棋的一方目标是将黑棋堵住不能行走。
下图就是牛角棋的图示,当然这里只画了11个落子点。
具体规则
黑白双方轮流移动棋子,每次只能移动一枚棋子,且只能沿着棋盘上的线条移动到相邻的棋位。比如1可以走到2和3, 而4可以走到2、3、5和6 ,最下面的11可以走到9和10.
牛角棋的规则是由白棋先走。
主要的获胜条件是:白棋一方将另一方也就是黑方棋子围住,导致黑棋无法移动,这时黑棋输。严格的规则是黑棋在1,白棋在2和3,且这时候轮到黑棋走,黑棋被围住,无法行动,这时候白棋就赢了。还有一种较宽松的规则,就是只要黑棋无法行动就输,也就是如果黑棋在11,白棋在9和10,这时候如果轮到黑棋走,黑棋无法行动,黑棋输。一般用严格的规则来判定。
如果双方共计走了80步,白棋还没有赢,则黑棋胜利。也就是超过一定的步数(比如2*交叉点数+3),白棋都没有围住黑棋,那么白棋被判超时(超过步数)输。这个步数可以根据双方的棋力等级调整,还有一种规则,就是白棋走最佳步骤,一步都不错,判白棋赢。如果白棋走错一步,判黑棋赢。
棋盘可以表示为qi=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ,共计13个落子点。如果落白子,则用1表示,落黑子,用2表示,0则表示此位置没有任何棋子。比如0号位黑子,表示为qi[0]=2
11和12位为白子,表示为qi[11]=1 qi[12]=1 ,整个棋盘表示为qi=[2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
根据规则,每个子的临近可移动位置是-2 -1 +1 +2 ,但不能超出qi的下标区间,比如qi[0]=2,是黑子, 0-2=-2 0-1=-1 ,这两个为负数,超出0-12的下标,因此要舍弃,不可移动到-1和-2 位置。如果要移动的目标位置有其他其它子(不管是黑子还是白子),也不可以走到该位置。比如qi[0]=2,是黑子,可以移动到0+1 =1 和0+2 =2 这两个坐标,如果移动到下标1,则qi[0]=0 , qi[1]=3 ,棋盘表示为qi=[0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] 。如果移动到下标2,则qi[0]=0 , qi[2]=3 ,棋盘表示为qi=[0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] 。同理对qi[12]的白子,可以移动到10, 11 。当然因为11有子,那么就只能移动到10,也就是qi[12]=0 qi[10]=1, 棋盘数据变成qi=[0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0]
棋盘python实现
棋盘初始化
初始棋盘表示为:
board=[2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
后来为了可以灵活的设置棋盘大小,设置落子点数为boardsize,这样初始化棋盘的代码为:
boardsize = 13
board = [0] * boardsize
board[0] = 2
board[-1] = 1
board[-2] = 1
最终初始化代码为:
def __init__(self, num=7):
# 初始化棋盘,13个位置,0表示空,1表示白棋,2表示黑棋
# self.qi = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
# 牛角棋也叫13太保棋,默认是13个位置。测试中设置默认为7个位置
# 因为所以从0开始,位置的最大索引值为num-1
self.num = num
self.maxindex = self.num -1
self.qi = [0] * self.num
self.qi[0] = 2
self.qi[-1] = 1
self.qi[-2] = 1
self.total_moves = 0
self.max_moves = 13*self.num+6
self.current_player = 'white' # 白棋先走
将牛角棋的定义传给AI大模型,让它们帮着写代码。
使用海螺AI的实现
根据您提供的牛角棋(十三太保棋)的详细定义和规则,以下是一个完整的Python实现。该实现包括棋盘的表示、棋子的移动、吃子规则以及胜利条件的判断。为了确保与您提供的规则一致,我们将棋盘表示为一个包含13个元素的列表 qi
,其中:
- 0:表示该位置没有棋子。
- 1:表示白棋(农夫)。
- 2:表示黑棋(牛)。
实现细节
1.
棋盘表示:
- 使用一个长度为13的列表
qi
来表示棋盘,每个索引对应一个落子点。 - 初始棋盘设置为
qi = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
,其中索引0为黑棋,索引11和12为白棋。
2.
移动规则:
- 白棋和黑棋:每次只能移动一枚棋子,且只能沿着棋盘上的线条移动到相邻的节点。
- 相邻位置:每个位置的相邻位置为其索引加减1和加减2,但需确保不超出
qi
的索引范围(0到12)。 - 移动限制:白棋(1)只能向棋盘顶部移动,即只能向索引减小的方向移动;黑棋(2)可以向任意方向移动。
3.
胜利条件:
- 白棋胜利:将黑棋围住,使其无法移动。
- 黑棋胜利:如果双方共计走了80步,白棋还没有赢,则黑棋胜利。
4.
用户交互:
- 游戏通过命令行进行,玩家交替输入移动指令。
- 白棋由玩家控制,黑棋由程序自动移动(简单策略:随机移动)。
Python 实现
import sys
import random
class NiuJiaoQi:
def __init__(self):
# 初始化棋盘,13个位置,0表示空,1表示白棋,2表示黑棋
self.qi = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
self.total_moves = 0
self.max_moves = 80
self.current_player = 'white' # 白棋先走
def display_board(self):
"""
显示当前棋盘状态。
"""
print("\n当前棋盘状态:")
for idx, piece in enumerate(self.qi):
if piece == 1:
marker = 'W'
elif piece == 2:
marker = 'B'
else:
marker = '.'
print(f"位置{idx}: {marker}")
print()
def get_valid_moves(self, position):
"""
获取指定位置的所有有效移动。
:param position: 当前棋子的位置(索引)。
:return: 一个列表,包含可以移动到的位置索引。
"""
# 相邻位置为-2, -1, +1, +2
adjacent = [-2, -1, 1, 2]
valid = []
for move in adjacent:
target = position + move
if 0 <= target <= 12:
if self.qi[target] == 0:
valid.append(target)
return valid
def make_move(self, from_pos, to_pos):
"""
执行移动。
:param from_pos: 起始位置(索引)。
:param to_pos: 目标位置(索引)。
:return: 如果移动合法,返回True;否则返回False。
"""
if 0 <= from_pos <= 12 and 0 <= to_pos <= 12:
if self.qi[from_pos] != 0:
if self.qi[to_pos] == 0:
self.qi[to_pos] = self.qi[from_pos]
self.qi[from_pos] = 0
return True
return False
def is_black_trapped(self):
"""
判断黑棋是否被围住。
:return: 如果黑棋无法移动,返回True;否则返回False。
"""
black_pos = self.qi.index(2)
return len(self.get_valid_moves(black_pos)) == 0
def is_game_over(self):
"""
判断游戏是否结束。
:return: 如果游戏结束,返回获胜方('white'或'black');否则返回None。
"""
if self.is_black_trapped():
return 'white'
if self.total_moves >= self.max_moves:
return 'black'
return None
def switch_player(self):
"""
切换当前玩家。
"""
self.current_player = 'black' if self.current_player == 'white' else 'white'
def play(self):
"""
开始游戏循环。
"""
while True:
self.display_board()
print(f"当前玩家: {self.current_player}")
if self.current_player == 'white':
from_pos = self.get_player_move('white')
to_pos = self.get_player_move('white', from_pos)
if self.make_move(from_pos, to_pos):
self.total_moves += 1
self.switch_player()
else:
print("无效的移动,请重新选择。")
else:
# 黑棋自动移动(简单策略:随机移动)
black_pos = self.qi.index(2)
moves = self.get_valid_moves(black_pos)
if moves:
to_pos = random.choice(moves)
self.make_move(black_pos, to_pos)
self.total_moves += 1
self.switch_player()
else:
print("黑棋无法移动,白棋胜利!")
return
winner = self.is_game_over()
if winner:
self.display_board()
if winner == 'white':
print("白棋胜利!黑棋被围住。")
else:
print("黑棋胜利!达到最大步数。")
break
def get_player_move(self, player, from_pos=None):
"""
获取玩家移动的棋子位置和目标位置。
:param player: 'white'或'black'
:param from_pos: 如果提供,则只获取目标位置。
:return: (from_pos, to_pos)
"""
if player == 'white':
if from_pos is None:
while True:
try:
from_pos = int(input("选择要移动的白棋位置(位置索引0-12):"))
if 0 <= from_pos <= 12 and self.qi[from_pos] == 1:
break
else:
print("无效的选择。请选择白棋的位置。")
except ValueError:
print("请输入一个有效的整数。")
while True:
try:
to_pos = int(input(f"选择要移动到的位置(位置索引0-12):"))
if to_pos in self.get_valid_moves(from_pos):
return from_pos, to_pos
else:
print("无效的移动。请重新选择。")
except ValueError:
print("请输入一个有效的整数。")
else:
# 黑棋自动移动策略(简单策略:随机移动)
black_pos = self.qi.index(2)
moves = self.get_valid_moves(black_pos)
if moves:
to_pos = random.choice(moves)
return black_pos, to_pos
else:
return None, None
if __name__ == "__main__":
game = NiuJiaoQi()
game.play()
代码说明
1.
棋盘表示:
self.qi
:长度为13的列表,表示棋盘上的每个位置的状态。- 初始棋盘设置为
qi = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
,其中索引0为黑棋,索引11和12为白棋。
2.
移动逻辑:
get_valid_moves(position)
:获取指定位置的所有有效移动,确保不超出棋盘范围且目标位置为空。make_move(from_pos, to_pos)
:执行移动,更新棋盘状态。is_black_trapped()
:判断黑棋是否被围住,即黑棋没有有效的移动位置。is_game_over()
:判断游戏是否结束,包括黑棋被围住或达到最大步数。
3.
玩家交互:
play()
:游戏主循环,交替进行白棋和黑棋的移动。get_player_move(player, from_pos=None)
:获取玩家移动的棋子位置和目标位置。白棋由玩家输入,黑棋采用简单策略(随机移动)。get_black_move()
:黑棋的移动策略,这里采用随机选择相邻节点。
4.
胜利判断:
- 如果黑棋被围住,白棋胜利。
- 如果总步数达到80步,白棋未获胜,则黑棋胜利。
示例运行
取消自动换行
复制
当前棋盘状态:
位置0: B
位置1: .
位置2: .
位置3: .
位置4: .
位置5: .
位置6: .
位置7: .
位置8: .
位置9: .
位置10: .
位置11: W
位置12: W
当前玩家: white
选择要移动的白棋位置(位置索引0-12):11
选择要移动到的位置(位置索引0-12):10
当前棋盘状态:
位置0: B
位置1: .
位置2: .
位置3: .
位置4: .
位置5: .
位置6: .
位置7: .
位置8: .
位置9: .
位置10: W
位置11: .
位置12: W
当前玩家: black
...
注意事项
- 移动策略:当前黑棋的移动策略为随机移动。您可以根据需要实现更复杂的策略,例如避开被围的风险或主动攻击白棋。
- 棋盘显示:为了简化,棋盘的显示仅为位置索引和棋子位置。您可以扩展
display_board()
方法,使用图形库(如pygame
)来绘制更直观的棋盘。 - 输入验证:当前实现对玩家输入进行了基本验证,但在实际应用中可能需要更严格的验证和错误处理。
这个实现提供了一个基础框架,您可以根据具体需求进行扩展和优化,例如添加图形界面、实现更复杂的AI策略或调整游戏规则。
总结
这段代码并不能直接运行,主要问题有两点:
1 会一直询问白棋的索引和移动位置,第二次重复后白棋执行报错can only concatenate tuple (not "int") to tuple,好像是逻辑有点问题。解决问题见调试部分。
2 白棋之后黑棋报错。是因为刚开始修改白棋报错,是在公共处理部分修改的,导致黑棋的传参有问题。在白棋部分修改,这样黑棋传参就没问题了。
后来又加上了设置棋盘大小的功能。最终代码最后面。
文心代码实现
文心也给了代码,但是初始化里第一句:self.board = * 13 就把我拦下了,不明白这里为什么这样写,所以这段代码算费了。
class HornChess:
def __init__(self):
# 初始化棋盘,13个位置都为0
self.board = * 13
# 黑棋初始位置在0号位
self.board = 2
# 白棋初始位置在11和12号位
self.board = 1
self.board = 1
# 当前步数
self.steps = 0
# 当前轮到谁走,1表示白棋,2表示黑棋
self.current_player = 1
def is_valid_move(self, position, target):
# 检查目标位置是否在棋盘范围内
if target < 0 or target >= 13:
return False
# 检查目标位置是否为空
if self.board[target] != 0:
return False
# 检查移动是否合法(只能移动到相邻位置)
valid_moves = [position - 2, position - 1, position + 1, position + 2]
return target in valid_moves
def move_piece(self, position, target):
# 移动棋子并更新棋盘状态
self.board[position] = 0
self.board[target] = self.current_player
self.steps += 1
# 切换玩家
self.current_player = 3 - self.current_player
def check_win(self):
# 检查白棋是否获胜(黑棋无法移动)
for i in range(13):
if self.board[i] == 2:
valid_moves = [i - 2, i - 1, i + 1, i + 2]
for move in valid_moves:
if 0 <= move < 13 and self.board[move] == 0:
return False
return True
def play(self):
# 游戏主循环
while self.steps < 80:
self.print_board()
# 获取玩家输入(这里简化为自动选择合法移动)
moved = False
for i in range(13):
if self.board[i] == self.current_player:
for target in [i - 2, i - 1, i + 1, i + 2]:
if self.is_valid_move(i, target):
self.move_piece(i, target)
moved = True
break
if moved:
break
# 检查是否获胜
if self.check_win():
self.print_board()
print(f"Player {self.current_player} wins!")
return
# 如果80步内没有获胜,则黑棋获胜
self.print_board()
print("Player 2 (black) wins by reaching 80 steps!")
def print_board(self):
# 打印棋盘状态
board_str = " ".join(str(self.board[i]) for i in range(13))
print(board_str)
# 创建游戏实例并开始游戏
game = HornChess()
game.play()
最终:修改之后的可运行版本
import sys
import random
class NiuJiaoQi():
def __init__(self, num=7):
# 初始化棋盘,13个位置,0表示空,1表示白棋,2表示黑棋
# self.qi = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
# 牛角棋也叫13太保棋,默认是13个位置。测试中设置默认为7个位置
# 因为所以从0开始,位置的最大索引值为num-1
self.num = num
self.maxindex = self.num -1
self.qi = [0] * self.num
self.qi[0] = 2
self.qi[-1] = 1
self.qi[-2] = 1
self.total_moves = 0
self.max_moves = 13*self.num+6
self.current_player = 'white' # 白棋先走
def display_board(self):
"""
显示当前棋盘状态。
"""
print("\n当前棋盘状态:")
for idx, piece in enumerate(self.qi):
if piece == 1:
marker = 'W'
elif piece == 2:
marker = 'B'
else:
marker = '.'
print(f"位置{idx}: {marker}")
print()
def get_valid_moves(self, position):
"""
获取指定位置的所有有效移动。
:param position: 当前棋子的位置(索引)。
:return: 一个列表,包含可以移动到的位置索引。
"""
# 相邻位置为-2, -1, +1, +2
adjacent = [-2, -1, 1, 2]
valid = []
for move in adjacent:
target = position + move
if 0 <= target <= self.maxindex:
if self.qi[target] == 0:
valid.append(target)
# print(f"当前棋子位置:{position}可移动位置:{valid}")
return valid
def make_move(self, from_pos, to_pos):
"""
执行移动。
:param from_pos: 起始位置(索引)。
:param to_pos: 目标位置(索引)。
:return: 如果移动合法,返回True;否则返回False。
"""
print(f"准备移动,源:{from_pos} 移动到:{to_pos}")
# to_pos=from_pos[1]
# from_pos=from_pos[0]
# print(f"修改后准备移动,源:{from_pos} 移动到:{to_pos}")
if 0 <= from_pos <= self.maxindex and 0 <= to_pos <= self.maxindex:
if self.qi[from_pos] != 0:
if self.qi[to_pos] == 0:
self.qi[to_pos] = self.qi[from_pos]
self.qi[from_pos] = 0
# self.display_board()
return True
return False
def is_black_trapped(self):
"""
判断黑棋是否被围住。
:return: 如果黑棋无法移动,返回True;否则返回False。
"""
black_pos = self.qi.index(2)
return len(self.get_valid_moves(black_pos)) == 0
def is_game_over(self):
"""
判断游戏是否结束。
:return: 如果游戏结束,返回获胜方('white'或'black');否则返回None。
"""
if self.is_black_trapped():
return 'white'
if self.total_moves >= self.max_moves:
return 'black'
return None
def switch_player(self):
"""
切换当前玩家。
"""
self.current_player = 'black' if self.current_player == 'white' else 'white'
def play(self):
"""
开始游戏循环。
"""
while True:
self.display_board()
print(f"当前玩家: {self.current_player}")
if self.current_player == 'white':
from_pos = self.get_player_move('white')
# print(f"in play white from_pos:{from_pos}")
to_pos = self.get_player_move('white', from_pos)
# print(f"in play white to_pos:{to_pos}")
to_pos=from_pos[1]
from_pos=from_pos[0]
# print(f"in play white after change from_pos:{from_pos} to_pos:{to_pos}")
if self.make_move(from_pos, to_pos):
self.total_moves += 1
self.switch_player()
else:
print("无效的移动,请重新选择。")
else:
# 黑棋自动移动(简单策略:随机移动)
black_pos = self.qi.index(2)
moves = self.get_valid_moves(black_pos)
if moves:
to_pos = random.choice(moves)
self.make_move(black_pos, to_pos)
self.total_moves += 1
self.switch_player()
else:
print("黑棋无法移动,白棋胜利!")
return
winner = self.is_game_over()
if winner:
self.display_board()
if winner == 'white':
print("白棋胜利!黑棋被围住。")
else:
print("黑棋胜利!达到最大步数。")
break
def get_player_move(self, player, from_pos=None):
"""
获取玩家移动的棋子位置和目标位置。
:param player: 'white'或'black'
:param from_pos: 如果提供,则只获取目标位置。
:return: (from_pos, to_pos)
"""
if player == 'white':
if from_pos is None:
while True:
try:
# from_pos = int(input(f"选择要移动的白棋位置(位置索引0-{self.maxindex}):"))
from_pos = int(input(f"选择要移动的白棋位置(位置索引\
{[index for index, value in enumerate(self.qi) if value == 1]}:"))
self.get_valid_moves(from_pos)
# print(from_pos)
if 0 <= from_pos <= self.maxindex and self.qi[from_pos] == 1:
break
else:
print("无效的选择。请选择白棋的位置。")
except ValueError:
print("请输入一个有效的整数。")
while True:
try:
# to_pos = int(input(f"选择要移动到的位置(位置索引0-{self.maxindex}):"))
to_pos = int(input(f"选择要移动到的位置(可选位置索引:{self.get_valid_moves(from_pos)}):"))
# print(f"目标移动位置:{to_pos}")
if to_pos in self.get_valid_moves(from_pos):
# print(f"get_player_move:白棋move ok from {from_pos}to {to_pos}")
return from_pos, to_pos
else:
print("无效的移动。请重新选择。")
except ValueError:
print("请输入一个有效的整数。")
else:
# 黑棋自动移动策略(简单策略:随机移动)
black_pos = self.qi.index(2)
print(f"黑棋black_pos:(black_pos)")
moves = self.get_valid_moves(black_pos)
print(f"黑棋moves:{moves}")
if moves:
to_pos = random.choice(moves)
print(f"black moves:{moves} to_pos:{to_pos}")
print(f"get_player_move:黑棋 move ok from {from_pos}to {to_pos}")
return black_pos, to_pos
else:
return None, None
if __name__ == "__main__":
game = NiuJiaoQi()
game.play()
调试
白棋执行报错can only concatenate tuple (not "int") to tuple
~\AppData\Local\Temp\ipykernel_18688\4084430725.py in get_player_move(self, player, from_pos) 141 try: 142 to_pos = int(input(f"选择要移动到的位置(位置索引0-12):")) --> 143 if to_pos in self.get_valid_moves(from_pos): 144 return from_pos, to_pos 145 print(f"move ok from {from_pos}to {to_pos}") ~\AppData\Local\Temp\ipykernel_18688\4084430725.py in get_valid_moves(self, position) 35 valid = [] 36 for move in adjacent: ---> 37 target = position + move 38 if 0 <= target <= 12: 39 if self.qi[target] == 0: TypeError: can only concatenate tuple (not "int") to tuple 原来是这段代码
if self.current_player == 'white':
from_pos = self.get_player_move('white')
to_pos = self.get_player_move('white', from_pos)
发现这里from_pos返回的是一个tuple,包含位置和目标,修改代码,让其返回一个值位置,目标值传入to_pos变量:
if self.current_player == 'white':
from_pos = self.get_player_move('white')
# print(f"in play white from_pos:{from_pos}")
to_pos = self.get_player_move('white', from_pos)
# print(f"in play white to_pos:{to_pos}")
to_pos=from_pos[1]
from_pos=from_pos[0]