如何基于Prolog实现符合指定规则的Battleships战舰游戏?
Prolog战舰游戏完整实现方案
规则对齐说明
补全后的代码完全匹配所有要求的规则特性:
- 支持2艘扫雷舰(占2格)、2艘战列巡洋舰(占3格)的横竖放置,禁止斜放
- 人类玩家放置指令支持
place(minesweeper,2,2,'west')格式 - 电脑自动随机合法放置所有战舰,不会出现越界、重叠问题
- 人类攻击后自动反馈命中/未命中,命中时告知舰船类型,同时展示电脑棋盘状态:
x为命中位置,m为未命中位置,-为未探索格子 - 电脑AI攻击逻辑:未命中时随机选未探索格子,命中后优先攻击相邻格子直到击沉舰船
- 自动判断胜负,任意一方所有舰船被全击沉时结束游戏
核心补全逻辑
- 多格舰船合法放置校验
定义舰船长度、方向偏移量,放置前计算所有占用格子,校验是否在棋盘范围内、是否无重叠、无超出边界 - 玩家放置指令封装
将人类输入的舰船类型、坐标、方向映射为内部放置逻辑,合法则更新玩家棋盘,非法直接提示错误 - 电脑随机放置逻辑
循环随机生成起点、方向,直到满足放置条件,依次放置4艘舰船 - 攻击流程处理
人类攻击时校验坐标合法性,更新命中/未命中记录,打印电脑棋盘;电脑攻击时按照AI策略选点,等待人类反馈后更新攻击记录 - 胜负判断
统计双方所有舰船格子的命中数量,达到总舰船格子数(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]).
运行操作说明
- 安装SWI-Prolog后加载上述代码
- 输入
start_game(5).启动5x5棋盘的游戏 - 按照提示放置4艘舰船:2艘扫雷舰、2艘战列巡洋舰,放置格式示例:
place(minesweeper, 2, 2, 'east'). - 放置完成后输入
attack(R,C).攻击电脑棋盘对应坐标,例如attack(3,4). - 电脑攻击后按照提示反馈结果:如果击中扫雷舰输入
feedback(hit, minesweeper).,未命中输入feedback(miss). - 任意一方所有舰船被击沉时游戏自动结束
内容的提问来源于stack exchange,提问作者Enda McNabola
相关产品推荐
相关产品推荐

