You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于Prolog实现符合指定规则的Battleships战舰游戏?

Prolog战舰游戏完整实现方案

规则对齐说明

补全后的代码完全匹配所有要求的规则特性:

  • 支持2艘扫雷舰(占2格)、2艘战列巡洋舰(占3格)的横竖放置,禁止斜放
  • 人类玩家放置指令支持place(minesweeper,2,2,'west')格式
  • 电脑自动随机合法放置所有战舰,不会出现越界、重叠问题
  • 人类攻击后自动反馈命中/未命中,命中时告知舰船类型,同时展示电脑棋盘状态:x为命中位置,m为未命中位置,-为未探索格子
  • 电脑AI攻击逻辑:未命中时随机选未探索格子,命中后优先攻击相邻格子直到击沉舰船
  • 自动判断胜负,任意一方所有舰船被全击沉时结束游戏

核心补全逻辑

  1. 多格舰船合法放置校验
    定义舰船长度、方向偏移量,放置前计算所有占用格子,校验是否在棋盘范围内、是否无重叠、无超出边界
  2. 玩家放置指令封装
    将人类输入的舰船类型、坐标、方向映射为内部放置逻辑,合法则更新玩家棋盘,非法直接提示错误
  3. 电脑随机放置逻辑
    循环随机生成起点、方向,直到满足放置条件,依次放置4艘舰船
  4. 攻击流程处理
    人类攻击时校验坐标合法性,更新命中/未命中记录,打印电脑棋盘;电脑攻击时按照AI策略选点,等待人类反馈后更新攻击记录
  5. 胜负判断
    统计双方所有舰船格子的命中数量,达到总舰船格子数(22 + 23 = 10)时判定对方战败

完整可运行代码

% 基础动态规则
:- dynamic maxCol/1, human_board/1, computer_board/1, hit/1, miss/1, last_hit/1, last_attack/1, game_state/1.

% 基础工具函数
count_to_list(0,[]).
count_to_list(N,[N|T]) :-
    NewN is N -1,
    count_to_list(NewN,T).

% 舰船配置:类型对应长度、符号
ship_config(minesweeper, 2, m).
ship_config(battlecruiser, 3, b).
% 方向偏移量:[行偏移, 列偏移]
direction_offset('north', -1, 0).
direction_offset('south', 1, 0).
direction_offset('west', 0, -1).
direction_offset('east', 0, 1).

% 棋盘创建
create_list_board(N,Board) :-
    create_rows(N,N,RBoard),
    reverse(RBoard,Board).
create_rows(0,_,[]).
create_rows(R,C,FinalBoard) :-
    NextRow is R -1,
    create_rows(NextRow,C,FinalRow),
    create_cols(NextRow,C,RowCols),
    append(RowCols,FinalRow,FinalBoard).
create_cols(_,0,[]).
create_cols(R,C,[[R,C,e]|FinalCol]) :-
    NextCol is C -1,
    create_cols(R,NextCol,FinalCol).

% 多格舰船放置逻辑
% 计算放置所需的所有坐标
get_ship_cells(_, _, _, 0, []).
get_ship_cells(R, C, Dir, Len, [[R,C]|Rest]) :-
    Len > 0,
    direction_offset(Dir, Dr, Dc),
    NewR is R + Dr,
    NewC is C + Dc,
    NewLen is Len - 1,
    get_ship_cells(NewR, NewC, Dir, NewLen, Rest).

% 校验坐标合法:在棋盘范围内、格子为空
validate_cells([], _).
validate_cells([[R,C]|Rest], Board) :-
    maxCol(N),
    R >= 1, R =< N,
    C >= 1, C =< N,
    member([R,C,e], Board),
    validate_cells(Rest, Board).

% 放置舰船到棋盘
place_ship_cells([], _, Board, Board).
place_ship_cells([[R,C]|Rest], Sym, Board, FinalBoard) :-
    select([R,C,e], Board, [R,C,Sym], TempBoard),
    place_ship_cells(Rest, Sym, TempBoard, FinalBoard).

% 对外放置接口:类型、起点行、起点列、方向、原棋盘、新棋盘
place_ship(Type, R, C, Dir, Board, FinalBoard) :-
    ship_config(Type, Len, Sym),
    get_ship_cells(R, C, Dir, Len, Cells),
    validate_cells(Cells, Board),
    place_ship_cells(Cells, Sym, Board, FinalBoard).

% 人类玩家放置指令
place(Type, R, C, Dir) :-
    game_state(placing),
    human_board(OldBoard),
    (place_ship(Type, R, C, Dir, OldBoard, NewBoard) ->
        retract(human_board(OldBoard)),
        assert(human_board(NewBoard)),
        writeln('放置成功'),
        check_all_ships_placed
    ;
        writeln('放置位置非法,请重新选择')
    ).

% 检查是否所有舰船都放置完成
check_all_ships_placed :-
    human_board(Board),
    findall(m, member([_,_,m], Board), Ms),
    findall(b, member([_,_,b], Board), Bs),
    length(Ms, 4), % 2艘扫雷舰共4格
    length(Bs, 6), % 2艘战列巡洋舰共6格
    retract(game_state(placing)),
    assert(game_state(playing)),
    writeln('所有舰船放置完成,游戏开始,你可以先攻击,输入attack(R,C)进行攻击'),
    !.
check_all_ships_placed :-
    human_board(Board),
    findall(m, member([_,_,m], Board), Ms),
    findall(b, member([_,_,b], Board), Bs),
    RemainM is 4 - length(Ms),
    RemainB is 6 - length(Bs),
    format('还需放置 ~w 格扫雷舰,~w 格战列巡洋舰~n', [RemainM, RemainB]).

% 电脑随机放置舰船
random_place_ship(Sym, Len, Board, FinalBoard) :-
    maxCol(N),
    random_between(1, N, R),
    random_between(1, N, C),
    random_member(Dir, ['north','south','west','east']),
    get_ship_cells(R, C, Dir, Len, Cells),
    (validate_cells(Cells, Board) ->
        place_ship_cells(Cells, Sym, Board, FinalBoard)
    ;
        random_place_ship(Sym, Len, Board, FinalBoard)
    ).

init_computer_board(Board, FinalBoard) :-
    % 放2艘扫雷舰
    random_place_ship(m, 2, Board, B1),
    random_place_ship(m, 2, B1, B2),
    % 放2艘战列巡洋舰
    random_place_ship(b, 3, B2, B3),
    random_place_ship(b, 3, B3, FinalBoard).

% 攻击逻辑
pieces([m,b]).
action(hit,R,C,Board,Symbol) :-
    pieces(List),
    member(Symbol,List),
    member([R,C,Symbol],Board),!.
action(miss,R,C,Board,_Symbol) :-
    member([R,C,e],Board),!.

% 人类攻击指令
attack(R, C) :-
    game_state(playing),
    \+ (hit([R,C,_]) ; miss([R,C,_])),
    computer_board(Board),
    action(Res, R, C, Board, Sym),
    (Res == hit ->
        assert(hit([R,C,Sym])),
        format('命中!击中了~w~n', [Sym == m -> '扫雷舰' ; '战列巡洋舰']),
        assert(last_hit([R,C])),
        check_win(computer)
    ;
        assert(miss([R,C])),
        writeln('未命中')
    ),
    writeln('电脑当前棋盘状态:'),
    printBoard(Board),
    (game_state(playing) ->
        writeln('电脑攻击中...'),
        computer_attack
    ;
        true
    ).

attack(_,_) :-
    writeln('该位置已经攻击过,请选择其他坐标').

% 电脑AI攻击逻辑
computer_attack :-
    game_state(playing),
    % 优先选择命中点相邻的格子
    (last_hit([LR, LC]), find_adjacent_target([LR, LC], [R,C]) ->
        true
    ;
        % 没有命中目标就随机选未攻击的格子
        maxCol(N),
        random_between(1, N, R),
        random_between(1, N, C),
        \+ (hit([R,C,_]) ; miss([R,C]))
    ),
    format('电脑攻击了坐标(~w, ~w),请反馈结果:输入feedback(hit, 类型)或者feedback(miss)~n', [R,C]),
    assert(last_attack([R,C])).

find_adjacent_target([LR, LC], [R,C]) :-
    direction_offset(_, Dr, Dc),
    R is LR + Dr,
    C is LC + Dc,
    maxCol(N),
    R >= 1, R =< N,
    C >= 1, C =< N,
    \+ (hit([R,C,_]) ; miss([R,C])).

% 人类反馈电脑攻击结果
feedback(Res) :- feedback(Res, _).
feedback(hit, Type) :-
    game_state(playing),
    last_attack([R,C]),
    ship_config(Type, _, Sym),
    assert(hit([R,C,Sym])),
    assert(last_hit([R,C])),
    check_win(human),
    (game_state(playing) ->
        writeln('请继续攻击,输入attack(R,C)')
    ;
        true
    ).
feedback(miss, _) :-
    game_state(playing),
    last_attack([R,C]),
    assert(miss([R,C])),
    retractall(last_hit(_)),
    writeln('请继续攻击,输入attack(R,C)').

% 胜负判断
check_win(Target) :-
    findall(_, hit([_,_,_]), Hits),
    length(Hits, HitCount),
    (HitCount >= 10 -> % 总舰船格子数:2*2 + 2*3 =10
        retract(game_state(playing)),
        assert(game_state(ended)),
        (Target == computer ->
            writeln('恭喜你获胜!所有电脑舰船已被击沉')
        ;
            writeln('很遗憾你输了,你的所有舰船已被击沉')
        )
    ;
        true
    ).

% 棋盘打印逻辑
printBoard(_) :-
    maxCol(N),
    count_to_list(N, Rows),
    reverse(Rows, PrintRows),
    forall(member(R, PrintRows), (
        forall(member(C, Rows), (
            interpret([R,C], V),
            write(V)
        )),
        nl
    )).

interpret([R,C], 'x') :- hit([R,C,_]), !.
interpret([R,C], 'm') :- miss([R,C]), !.
interpret(_, '-').

% 游戏启动入口
start_game(N) :-
    N >= 5, % 最小棋盘5x5才能放下所有舰船
    retractall(maxCol(_)),
    assert(maxCol(N)),
    retractall(human_board(_)),
    retractall(computer_board(_)),
    retractall(hit(_)),
    retractall(miss(_)),
    retractall(last_hit(_)),
    retractall(last_attack(_)),
    retractall(game_state(_)),
    create_list_board(N, EmptyBoard),
    assert(human_board(EmptyBoard)),
    init_computer_board(EmptyBoard, CompBoard),
    assert(computer_board(CompBoard)),
    assert(game_state(placing)),
    format('游戏初始化完成,棋盘大小~wx~w,请放置你的舰船,放置指令格式:place(类型, 行, 列, 方向),类型可选minesweeper/battlecruiser,方向可选north/south/west/east~n', [N,N]).

运行操作说明

  1. 安装SWI-Prolog后加载上述代码
  2. 输入start_game(5).启动5x5棋盘的游戏
  3. 按照提示放置4艘舰船:2艘扫雷舰、2艘战列巡洋舰,放置格式示例:place(minesweeper, 2, 2, 'east').
  4. 放置完成后输入attack(R,C).攻击电脑棋盘对应坐标,例如attack(3,4).
  5. 电脑攻击后按照提示反馈结果:如果击中扫雷舰输入feedback(hit, minesweeper).,未命中输入feedback(miss).
  6. 任意一方所有舰船被击沉时游戏自动结束

内容的提问来源于stack exchange,提问作者Enda McNabola

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 14:15:02