用 Claude 和 Prolog 解决国际象棋谜题Solving a chess puzzle with Claude and Prolog
作者尝试用 Claude 配合 Prolog 来解决国际象棋谜题。Prolog 作为最早的逻辑编程语言,其核心优势在于能直接表达逻辑问题,但学习曲线陡峭。实验验证了 LLM 与 Prolog 结合的可行性:Claude 负责将自然语言描述的棋局转化为 Prolog 逻辑规则,Prolog 引擎则负责精确推理求解。这种组合为逻辑推理类问题提供了一种 LLM 单独难以胜任的可靠方案。
John
Prolog 是最早的逻辑编程语言。其名称源于“programming in logic”(逻辑编程)。更具体地说,这个名字来自“programmation en logique”,因为该语言的发明者 Philippe Roussel 是法国人。
Prolog 有其优缺点。优点之一是该语言能够直接表达逻辑问题。缺点之一是它的语法可能有些古怪。但如果是由 LLM 编写代码,或者至少在辅助编写代码,语法问题就没那么重要了。
我想测试一下 Claude(Sonnet 4.6,中等推理力度)解决 Martin Gardner 国际象棋谜题的能力如何,我在一年多前曾写过关于这个谜题的文章。我选择了一个相对冷门的问题,而不是像八皇后问题那样的经典题目,因为对于后者,LLM 可能会直接照搬无数现成文章中的答案。
谜题
正如我在去年的文章中所述,任务是将两个车、两个象和两个马放置在一个 4x4 的国际象棋棋盘上,使得任何棋子都不会攻击到其他棋子。
它有两个基本解;如果把旋转和镜像反射算作不同的解,则有十二个。
Prolog 运行结果
Claude 编写了一个 SWI-Prolog 程序,我使用以下命令运行了它:
swipl -g "run, halt" chess_placement.pl它给出了如下输出。
Total solutions (raw): 12
Solutions up to D4 symmetry: 2
Solutions up to D4 symmetry: 2
Solution 1:
B . . N
N . . B
. R . .
. . R .
Solution 2:
. B N .
. . . R
R . . .
. N B .Claude 生成了一张展示这些解的精美图片。
需要注意的是,通过旋转和翻转的组合,第一个解可以产生八种变体。而第二个解只有四种变体,因为旋转两次就会恢复到初始状态。
Prolog 源代码
以下是 Claude 生成的 Prolog 代码。
% ============================================================
% Non-attacking placement of 2 Rooks, 2 Bishops, 2 Knights
% on a 4x4 chessboard
%
% Run with SWI-Prolog:
% swipl -g "run, halt" chess_placement.pl
%
% Or interactively:
% swipl chess_placement.pl
% ?- run.
% ?- count_solutions(N).
% ?- count_unique(N).
% ============================================================
:- use_module(library(lists)).
% ---- Board squares: (Row, Col) with Row, Col in 0..3 ----------------
square(R, C) :- member(R, [0,1,2,3]), member(C, [0,1,2,3]).
% ---- Attack predicates ----------------------------------------------
rook_attacks(R,_,R,_). % same row
rook_attacks(_,C,_,C). % same column
bishop_attacks(R1,C1,R2,C2) :-
DR is abs(R1-R2), DC is abs(C1-C2),
DR =:= DC, DR > 0.
knight_attacks(R1,C1,R2,C2) :-
DR is abs(R1-R2), DC is abs(C1-C2),
( DR =:= 1, DC =:= 2 ; DR =:= 2, DC =:= 1 ).
% attacks(+Type1, +R1, +C1, +Type2, +R2, +C2)
% True if either piece threatens the other's square.
attacks(rook, R1,C1, _, R2,C2) :- rook_attacks(R1,C1,R2,C2).
attacks(_, R1,C1, rook, R2,C2) :- rook_attacks(R1,C1,R2,C2).
attacks(bishop, R1,C1, _, R2,C2) :- bishop_attacks(R1,C1,R2,C2).
attacks(_, R1,C1, bishop,R2,C2) :- bishop_attacks(R1,C1,R2,C2).
attacks(knight, R1,C1, _, R2,C2) :- knight_attacks(R1,C1,R2,C2).
attacks(_, R1,C1, knight,R2,C2) :- knight_attacks(R1,C1,R2,C2).
% ---- All pairs safe -------------------------------------------------
% Check all unordered pairs in a list.
no_attack_pair(_, []).
no_attack_pair(T1-(R1,C1), [T2-(R2,C2)|Rest]) :-
\+ attacks(T1,R1,C1,T2,R2,C2),
no_attack_pair(T1-(R1,C1), Rest).
all_pairs_safe([]).
all_pairs_safe([P|Rest]) :-
no_attack_pair(P, Rest),
all_pairs_safe(Rest).
% ---- Generate a placement -------------------------------------------
%
% Placement = [rook-(R1,C1), rook-(R2,C2),
% bishop-(R3,C3), bishop-(R4,C4),
% knight-(R5,C5), knight-(R6,C6)]
%
% We enforce an ordering within each pair to avoid counting
% same-type duplicates (rook1 vs rook2 swapped).
% Squares are represented canonically: (R,C) with R*4+C as index.
sq_index(R, C, I) :- I is R*4 + C.
placement(Placement) :-
% --- Rooks ---
square(R1r, C1r), square(R2r, C2r),
sq_index(R1r,C1r,I1r), sq_index(R2r,C2r,I2r),
I1r < I2r, % canonical order for the two rooks
% --- Bishops ---
square(R1b, C1b), square(R2b, C2b),
sq_index(R1b,C1b,I1b), sq_index(R2b,C2b,I2b),
I1b < I2b, % canonical order for the two bishops
% --- Knights ---
square(R1n, C1n), square(R2n, C2n),
sq_index(R1n,C1n,I1n), sq_index(R2n,C2n,I2n),
I1n < I2n, % canonical order for the two knights
% --- All six squares distinct ---
Squares = [(R1r,C1r),(R2r,C2r),(R1b,C1b),(R2b,C2b),(R1n,C1n),(R2n,C2n)],
all_distinct_squares(Squares),
% --- Build placement list ---
Placement = [ rook-(R1r,C1r), rook-(R2r,C2r),
bishop-(R1b,C1b), bishop-(R2b,C2b),
knight-(R1n,C1n), knight-(R2n,C2n) ],
% --- No piece attacks any other ---
all_pairs_safe(Placement).
all_distinct_squares([]).
all_distinct_squares([S|Rest]) :-
\+ member(S, Rest),
all_distinct_squares(Rest).
% ---- Count all solutions --------------------------------------------
count_solutions(N) :-
findall(P, placement(P), Ps),
length(Ps, N),
format("Total solutions (raw): ~w~n", [N]).
% ---- D4 symmetry transformations on a 4x4 board ---------------------
% Squares (R,C) in 0..3
transform(identity, R, C, R, C).
transform(rot90, R, C, C, Nr) :- Nr is 3-R.
transform(rot180, R, C, Nr, Nc) :- Nr is 3-R, Nc is 3-C.
transform(rot270, R, C, Nc, R) :- Nc is 3-C.
transform(flipH, R, C, R, Nc) :- Nc is 3-C.
transform(flipV, R, C, Nr, C) :- Nr is 3-R.
transform(flipD1, R, C, C, R).
transform(flipD2, R, C, Nr, Nc) :- Nr is 3-C, Nc is 3-R.
apply_transform(_, [], []).
apply_transform(T, [Type-(R,C)|Rest], [Type-(NR,NC)|TRest]) :-
transform(T, R, C, NR, NC),
apply_transform(T, Rest, TRest).
% Canonical form: sort pieces within same-type pairs, then sort the
% whole list to get a unique representative.
canonical_placement(Placement, Canonical) :-
findall(T, member(T,[identity,rot90,rot180,rot270,
flipH,flipV,flipD1,flipD2]), Ts),
maplist(transform_and_sort(Placement), Ts, AllForms),
msort(AllForms, Sorted),
Sorted = [Canonical|_].
transform_and_sort(Placement, T, Sorted) :-
apply_transform(T, Placement, TPl),
msort(TPl, Sorted).
% ---- Count solutions up to D4 symmetry ------------------------------
count_unique(N) :-
findall(P, placement(P), Ps),
maplist(canonical_placement, Ps, Canonicals),
list_to_set(Canonicals, Unique),
length(Unique, N),
format("Solutions up to D4 symmetry: ~w~n", [N]).
% ---- Pretty-print a board -------------------------------------------
print_board(Placement) :-
forall(member(R, [0,1,2,3]),
print_row(R, Placement)),
nl.
print_row(R, Placement) :-
forall(member(C, [0,1,2,3]),
print_cell(R, C, Placement)),
nl.
print_cell(R, C, Placement) :-
( member(rook-(R,C), Placement) -> write('R ')
; member(bishop-(R,C), Placement) -> write('B ')
; member(knight-(R,C), Placement) -> write('N ')
; write('. ')
).
% ---- Print all unique solutions -------------------------------------
print_unique_solutions :-
findall(P, placement(P), Ps),
maplist(canonical_placement, Ps, Canonicals),
list_to_set(Canonicals, Unique),
length(Unique, N),
format("~nSolutions up to D4 symmetry: ~w~n~n", [N]),
forall(nth1(I, Unique, Sol),
( format("Solution ~w:~n", [I]),
print_board(Sol) )).
% ---- Top-level entry point ------------------------------------------
run :-
count_solutions(Raw),
count_unique(Sym),
format("~n"),
print_unique_solutions,
format("Summary: ~w raw solutions, ~w up to D4 symmetry.~n",
[Raw, Sym]).需要完整排版与评论请前往来源站点阅读。