我花了几天时间试图理解你的代码和它的逻辑,但我被它无法运行的事实所阻碍,而且它的布局逻辑似乎有缺陷。结果,我自己分析了游戏问题并构建了自己的解决方案。为此,为了清楚地说明我认为的游戏规则,我已尽可能简洁明了地重申规则:
游戏
Peter 和 Henry 正在玩一个由字符 '0' 和 '1' 组成的长度为 n 的字符串 s 的游戏。两位玩家轮流轮流,但彼得总是第一轮。
戏剧
在每一回合中,玩家可以对字符串执行以下操作之一:
- 选择任意 i,其中 s[i]= '0' 并将 s[i] 更改为 '1' 并支付 1 卢比。
- 反转整个字符串并支付 0 卢比。如果出现以下情况,则不允许使用此选项:
结束游戏
当字符串的每个字符都变成'1'时游戏结束。
花费最少的玩家获胜。如果双方花费相同,则为平局。
问题
如果两名球员都发挥最佳,确定谁会赢,或者比赛是否平局。
分析
假设以上内容准确描述了游戏玩法:
任何解决方案都需要解决的第一个问题是确定“最佳玩法”的含义。通过初步观察,我们可以推断出任何玩家的最佳玩法都必须符合与:
- 如果可能,选择字符串反转 - 这会迫使对手在点球时选择数字
- 如果反转不是一个选项,请选择一个要反转的数字,这会导致回文。这要求对手也转换一个数字,因此惩罚是相等的
- 如果无法创建回文,请选择索引并更改值,惩罚为 1
但经过进一步评估,这不一定是正确的优先级,例如:
开始:10100,
玩家 0 播放 00101 得分(0,0)
玩家 1 播放 10101 分数(0,1)
玩家 0 播放 11101 得分(1,1)
玩家 1 播放 10111 得分(1,1)
玩家 0 播放 11111 得分(2,1)
玩家 1 获胜
或者
玩家 0 播放 10101 得分(1,0)
玩家 1 播放 11101 得分(1,1)
玩家 0 播放 10111 得分(1,1)
玩家 1 打出 11111 得分(1,1)
玩家 0 获胜
如上所示,玩家 0 可以通过在第一步创建回文而不是反转字符串来改善结果。因此,最佳的定义取决于游戏的最终结果,而不是简单地从任何给定游戏阶段的可用选项中选择最佳游戏。
实现此逻辑的方法要求首先确定所有游戏状态,然后在每个回合为玩家采取最佳的最终游戏解决方案路径。
然后,第一步需要我们创建所有游戏状态的连接图 (GameTree)。为了跟踪游戏每一步的游戏状态,我创建了一个 GameState 类。此类的一个实例将用于定义 GameTree 的每个节点。每个 GameState 实例都会定义:
- 输入得分 [玩家 1,玩家 2]
- 在此状态下移动的玩家
- 输入字符串
- 之前的动作是反向标记 True/False
- 之前的游戏状态
- 子游戏状态
- 每个孩子的终点得分
因此,使用上面的示例,生成的简化树将符合以下条件,其中每个节点由 (Player, String_state, score, children) 描述:
GS0=[0: '10100', (0,0), [GS1, ..]]
GS1 = [1: '00101', (0,0), [GS3, ..]]
GS3 = [0, '10101', (0,1), [GS4, ..]]
GS4 = [1: '11101', (1,1). [GS5]]
GS5 = [0: '10111', (1,1), [GS6, ..]]
GS6 = [1: '11111', (2,1), []]
GS2 = [1: '10101', (1,0), [GS7, ..]]
GS7 = [0: '11101', (1,1), [GS8, ..]]
GS8 = [1: '10111', (1,1), [GS9]]
GS9 = [0: '11111', (1,2), []]
一旦定义了 GameTree 图,就可以使用深度优先搜索 (DFS) 算法沿着树的每个分支向下移动,直到找到叶节点(即没有子节点的节点),然后返回到前一个节点的叶子节点得分。在每个有多个选项可用的节点上,tghe 函数将为玩家选择最佳解决方案并将该解决方案传递给前一个节点,这将继续返回,直到搜索完所有路径并且第一个玩家的最佳最终游戏移动可以是决定。以下代码实现了这种方法。
# Utility functions to assist in implementing optimal play
def is_palindrome(z):
# Returns True if s is a palindrome
return z == z[::-1]
def find_palindrome(s):
""" Returns a list of indexes that if switched
will result in a palindrome position"""
rslt = []
k = ''
for i, d in enumerate(s):
k += d
if d == '0':
j = k[:i] + '1' + s[i+1:]
if is_palindrome(j):
rslt.append(i)
return rslt
def get_bestScore(l, plr):
""" Return best score for existing move options"""
best = None
scr = None
for sc in l:
rslt = sc[(plr+1)%2] - sc[plr]
if best == None:
best = rslt
scr = sc
else:
if rslt > best:
best = rslt
scr = sc
return scr
def find_BestMove(node):
# Recursive function to search for leaf nodes and bactrack to define best play option
ptr = 0
if node.children:
while len(node._end_options) < len(node.children):
node._end_options.append(find_BestMove(node.children[ptr]))
ptr += 1
return get_bestScore(node._end_options, node.player)
else:
return node.score
#Class to maintain game tree node states
class GameState:
def __init__(self, parent, str_in, rvflg, score):
self._prior = parent
self._str = str_in
self._player = 0
self._score = score
self._rvflg = rvflg
self._children = []
self._end_options = []
if self._prior:
self._player = (self._prior.player + 1)%2
self._children.extend(self.get_move_options())
def __gt__(self, other):
return self.score[self.player] < other.score[other.player]
def __repr__(self):
return f"GS{self._id:02} - [{self.player}: {self.state_string} => {self.score}"
def get_move_options(self):
''' return a list of next game states for each possible move
from current postion. The list is sorted so that the best moves are
at the begining of the list'''
mv_opts = []
if not self.rvflg and not is_palindrome(self._str):
mv_opts.append(GameState(self, self._str[::-1], True, self.score))
sc = self._score.copy()
sc[self._player] += 1
popts = find_palindrome(self._str)
for i, d in enumerate(self._str):
if d == '0' and i not in popts:
popts.append(i)
for m in popts:
nw_str = self._str[:m]+'1'+self._str[m+1:]
mv_opts.append(GameState(self, nw_str, False, sc))
return mv_opts
@property
def state_string(self):
return self._str
@property
def rvflg(self):
return self._rvflg
@property
def score(self):
return self._score
@property
def player(self):
return self._player
@property
def children(self):
return self._children
def playGame(instr):
# Function to play the Game by building the Gamestate tree and playing optimum moves
players = ["Peter", 'Henry']
cgs = GameState(None,instr, False, [0,0])
rslt = find_BestMove(cgs)
out_str = ''
if rslt[0] == rslt[1]:
out_str = 'Draw'
elif rslt[0] < rslt[1]:
out_str = players[0]
else:
out_str = players[1]
return out_str
作为使用上述代码的示例:
test_cases = [('00', 'Henry'), ('100', "Draw"), ('10100', 'Peter'), ('0010', 'Peter')]
for tc in test_cases:
print(f"For Input String '{tc[0]}', Expected {tc[1]} and got: {playGame(tc[0])}")
产生以下输出:
For Input String '00', Expected Henry and got: Henry
For Input String '100', Expected Draw and got: Draw
For Input String '10100', Expected Peter and got: Peter
For Input String '0010', Expected Peter and got: Peter