我想问问是否有人能帮我,我已经在8块拼图程序上做了很多工作,现在我想进一步扩展它,但我遇到了困难。
所以这里是棋盘。
这个游戏的想法是尝试将一个棋子,例如目前位于a1的P1,移动到b1。棋盘上最多可以有5个其他棋子,每个棋子一次只能占据和移动一个空间。
我想做的是计算所需的移动步骤,例如将位于B1位置的P1移动到A3位置(或者任何结束位置),如果它一次只能移动一个空间。两个棋子不能占据同一个空间,如果t区有棋子,棋子也不能穿越到另一侧。我该如何编写代码,以便当我输入类似于
?- pmove([at(a1,p3),at(b1,p1),at(b3,p2)], [at(a1,p1),at(b1,p3),at(b3,p2)],M).
输入所有位置的起始状态以及它们应该结束的位置时,它会输出类似于以下内容的结果:
M = [move(p3,a1,t),move(p1,b1,b2),move(p3,t,b1), move(p1,b2,t),move(p1,t,a1)]
显示从开始到结束移动棋子所需的所有步骤,以及其他棋子到达其位置所需的移动。我认为使用广度优先搜索来解决这个问题是最好的,但我不是很确定接下来该怎么做。
回答:
这是一个很棒的问题,通过解决它将是提高Prolog技能的绝佳方式!所以我赞赏你选择了这个问题。
这个回答是一个草图,而不是完整的解决方案,我希望这足以满足你的需求。
接下来你要做的是编写一个谓词,表达从一个状态到另一个状态的单个有效移动,像这样:
singlemove(StartBoardState, Move, NextBoardState) :- ...
所以我们将把at(Place, Piece)
的列表视为棋盘状态,把像move(Piece,From,To)
这样的结构视为移动;前者将与StartBoardState
和NextBoardState
统一,后者将与Move
统一。
你的游戏规则应该编码在singlemove/3
中。你可以将其视为给定棋盘状态、每个有效移动和结果状态之间的关系。
我认为一旦你有了这个,至少一种低效的解决方案就会变得显而易见,使用蛮力搜索。一旦你让它工作起来(速度慢,可能只适用于两步游戏),你就可以开始看到如何通过使搜索更智能来提高性能。
如何实现singlemove/3
从问题中,这个游戏的规则是:
…它一次只能移动一个空间。两个棋子不能占据同一个空间,如果t区有棋子,棋子也不能穿越到另一侧。
所以首先,让我们陈述一些关于棋盘的事实。空间叫什么名字?
boardspace(b1).boardspace(b2).boardspace(b3).boardspace(t).boardspace(a1).boardspace(a2).boardspace(a3).
现在我们需要一些位置信息。
:- op(300, xfx, left_of).:- op(300, xfx, right_of). b1 left_of b2.b2 left_of b3.a1 left_of a2.a2 left_of a3.row(b1, upper).row(b2, upper).row(b3, upper).row(a1, lower).row(a2, lower).row(a3, lower).X right_of Y :- Y left_of X.adjacent(X, Y) :- X left_of Y.adjacent(X, Y) :- X right_of Y.adjacent(t, X) :- boardspace(X), X \= t.adjacent(X, t) :- boardspace(X), X \= t.
我还不确定是否需要所有这些,但这似乎是一个合理的开始。现在让我们处理规则。
因此,游戏有三个规则:
- 每回合只能移动一个棋子。
- 一个棋子每回合只能移动一个空间。
- 两个棋子不能占据同一个空间。
- 如果t区有棋子,棋子不能“穿越到另一侧”。
我觉得#1通过有一个singlemove/3
谓词就已经得到了充分处理。我们调用它一次,就得到一个移动。
对于#2,我们可以根据当前棋子旁边的空间来构建附近空间的列表。假设p1
要移动,并且我有一个如上定义的棋盘,member(at(StartLocation, p1), Board), adjacent(StartLocation, EndLocation)
将EndLocation
与p1
可以移动的地方统一。让我们试试:
?- Board = [at(a1,p3),at(b1,p1),at(b3,p2)], member(at(StartLocation, p1), Board), adjacent(StartLocation, EndLocation).Board = [at(a1, p3), at(b1, p1), at(b3, p2)],StartLocation = b1,EndLocation = b2 ;Board = [at(a1, p3), at(b1, p1), at(b3, p2)],StartLocation = b1,EndLocation = t ;false.
所以这似乎是正确的;b1的相邻位置是b2和t。
现在让我们编码下一条规则,两个棋子不能占据同一个空间。
unoccupied(Board, Location) :- \+ member(at(Location, _), Board).
现在我们可以将这两件事结合起来,作为singlemove/3
的一个良好开端:
singlemove(Board, move(Piece,StartLocation,EndLocation), [at(EndLocation,Piece)|RemainingLocations]) :- select(at(StartLocation,Piece), Board, RemainingLocations), adjacent(StartLocation, EndLocation), unoccupied(Board, EndLocation).
让我们试试:
?- Board = [at(a1,p3),at(a2,p1),at(a3,p2)], singlemove(Board, Move, NextBoard).Board = [at(a1, p3), at(a2, p1), at(a3, p2)],Move = move(p3, a1, t),NextBoard = [at(t, p3), at(a2, p1), at(a3, p2)] ;Board = [at(a1, p3), at(a2, p1), at(a3, p2)],Move = move(p1, a2, t),NextBoard = [at(t, p1), at(a1, p3), at(a3, p2)] ;Board = [at(a1, p3), at(a2, p1), at(a3, p2)],Move = move(p2, a3, t),NextBoard = [at(t, p2), at(a1, p3), at(a2, p1)] ;false.
这有什么有趣的地方吗?我使用select/3
将列表分成候选和剩余部分。我在子句的头部构建结果。但除此之外,我只是在拿你的规则,翻译成Prolog,并列出它们。所以你可以看到你只需要再实现一个谓词来实现你的第四条规则,它将直接放在unoccupied/2
之后,以进一步过滤掉无效的移动。
希望你能理解这个过程。我的数据模型可能有点过多,但话说回来,你可能会发现你需要的比最初看起来的更多。而且搜索策略很弱。但这是整体解决方案的基础情况。归纳情况将很有趣——我再次建议你尝试使用内置的深度优先策略,看看它有多糟糕,然后再使用BFS。你可能会想使用trace/0
,看看你什么时候陷入陷阱,以及你如何通过更好的显式推理来规避它。但我认为这是一个好的开始,我希望这对你有帮助。