207 lines
5.8 KiB
Prolog
207 lines
5.8 KiB
Prolog
:- use_module(library(clpfd)).
|
|
|
|
% Play - run the minesweeper game with a specified width and height
|
|
play(W,H) :-
|
|
format('
|
|
Welcome to prolog minesweeper!
|
|
: o X Y exposes a cell of the grid
|
|
: m X Y marks bombs
|
|
|
|
Any else to quit.
|
|
'),
|
|
|
|
make_grid(W, H, Grid),
|
|
!,
|
|
play(Grid),
|
|
!.
|
|
|
|
play(Grid) :- % win condition is true
|
|
map_grid(won, Grid),
|
|
map_grid(print_cell, Grid),
|
|
writeln('you won!').
|
|
|
|
play(Grid) :- % lose condition is true
|
|
\+ map_grid(still_playing, Grid),
|
|
map_grid(print_cell, Grid),
|
|
writeln('you hit a bomb!').
|
|
|
|
play(Grid) :- % stil playing
|
|
map_grid(print_cell, Grid),
|
|
parse_input(Op, X, Y),
|
|
do_op(Op, p(X,Y), Grid, Grid2),
|
|
!,
|
|
play(Grid2).
|
|
|
|
/* Create a new Grid
|
|
*
|
|
* The grid is created initially as a flat list, and after everything
|
|
* has been populated is converted into a 2 dimensional array.
|
|
*/
|
|
make_grid(W, H, grid(W,H,MappedCells)) :-
|
|
% create a flat list
|
|
Len is W * H,
|
|
length(Cells, Len),
|
|
|
|
% create a list of bombs that is 20% of the grid list
|
|
NBombs is W * H / 5,
|
|
floor(NBombs, NBint),
|
|
format('There are ~w bombs on the grid~n', NBint),
|
|
length(AllBombs, NBint),
|
|
maplist(=('?'), AllBombs),
|
|
% add the bombs to the start of the grid list
|
|
map_bombs_to_cells(Cells, AllBombs, NewC),
|
|
|
|
% randomise and convert to a 2D array
|
|
random_permutation(NewC, RCells),
|
|
convert_col(RCells, W, H, CreatedCells),
|
|
|
|
% populate the hidden part of the grid with number of bombs next to each cell
|
|
map_grid(adj_bomb(grid(W,H,CreatedCells)), grid(W,H,CreatedCells), grid(W,H,MappedCells)).
|
|
|
|
% puts the bombs at the start of the flat list before shuffling.
|
|
map_bombs_to_cells(C, [], C).
|
|
map_bombs_to_cells([_|Ct], [B|Bt], [B|R]) :- map_bombs_to_cells(Ct, Bt, R).
|
|
|
|
convert_row(T, 0, [], T).
|
|
convert_row([H|T], W, [H|R], Rem) :-
|
|
dif(W, 0), succ(W1, W),
|
|
convert_row(T, W1, R, Rem).
|
|
|
|
convert_col([], _, 0, []).
|
|
convert_col(C, W, H, [Row|MoreCells]) :- dif(H, 0), succ(H1, H),
|
|
convert_row(C, W, Row, Rem),
|
|
convert_col(Rem, W, H1, MoreCells).
|
|
|
|
% determine the number of bombs next to a cell (use mapgrid)
|
|
adj_bomb(_, _, _, C, cell('.',C)) :- C =@= '?'.
|
|
adj_bomb(Grid, p(X,Y), D, Cell, cell('.',NBombs)) :-
|
|
dif(Cell, '?'),
|
|
findall(p(Ax,Ay), (
|
|
adj(p(X,Y), D, p(Ax,Ay)),
|
|
indomain(Ax), indomain(Ay),
|
|
grid_val_xy(Grid, p(Ax,Ay), Val),
|
|
Val =@= '?'
|
|
), Bombs),
|
|
length(Bombs, NBombs).
|
|
|
|
% Print the grid (use mapgrid)
|
|
print_cell(p(X,_), dim(X,_), cell(C,A), cell(C,A)) :- format("~w~n", C).
|
|
print_cell(p(X,_), dim(W,_), cell(C,A), cell(C,A)) :- dif(X,W), format("~w ", C).
|
|
|
|
% determine if we have lost yet or not (use mapgrid).
|
|
still_playing(_,_,cell(A,_),_) :- A \= '*'.
|
|
|
|
% determine if we have won yet or not (use mapgrid).
|
|
won(_,_,cell(N,N),_) :- integer(N).
|
|
won(_,_,cell('?','?'),_).
|
|
|
|
% Operate on all cells in a grid, this is a meta predicate that is
|
|
% applied several times throughout the code
|
|
map_grid(Goal, G) :- map_grid(Goal, G, G).
|
|
|
|
map_grid(Goal, grid(W,H,Cells), grid(W,H,OutCells)) :-
|
|
map_grid_col(Cells, 1, dim(W,H), Goal, OutCells).
|
|
|
|
map_grid_col([], _, _, _, []).
|
|
map_grid_col([H|T], Y, D, Goal, [NRow|NCol]) :-
|
|
map_grid_row(H, p(1, Y), D, Goal, NRow),
|
|
succ(Y, Y1),
|
|
map_grid_col(T, Y1, D, Goal, NCol).
|
|
|
|
map_grid_row([], _, _, _, []).
|
|
map_grid_row([H|T], p(X, Y), D, Goal, [Cell|R]) :-
|
|
call(Goal, p(X, Y), D, H, Cell),
|
|
succ(X, X1),
|
|
map_grid_row(T, p(X1, Y), D, Goal, R).
|
|
|
|
% Get a value from the grid by X Y
|
|
grid_val_xy(grid(_,_,Cells), p(X,Y), Val) :-
|
|
nth1(Y, Cells, Row),
|
|
nth1(X, Row, Val).
|
|
|
|
% Set a value on the grid by X Y
|
|
grid_set_xy(grid(W,H,Cells), p(X,Y), Val, grid(W,H,NewCells)) :- grid_set_col(Cells, X, Y, Val, NewCells).
|
|
grid_set_col([H|T], X, 1, Val, [Row|T]) :- grid_set_row(H, X, Val, Row).
|
|
grid_set_col([H|T], X, Y, Val, [H|New]) :- dif(Y, 0), succ(Y1, Y), grid_set_col(T, X, Y1, Val, New).
|
|
grid_set_row([_|T], 1, Val, [Val|T]).
|
|
grid_set_row([H|T], X, Val, [H|New]) :- dif(X, 0), succ(X1, X), grid_set_row(T, X1, Val, New).
|
|
|
|
% All coordinates adjacent to an x,y position
|
|
adj(p(X,Y), dim(W,H), p(Ax,Ay)) :-
|
|
|
|
dif(p(X,Y),p(Ax,Ay)),
|
|
|
|
% adjacent X
|
|
Ax in 1..W,
|
|
Xmin #= X-1, Xmax #= X+1,
|
|
Ax in Xmin..Xmax,
|
|
|
|
% adjacent Y
|
|
Ay in 1..H,
|
|
Ymin #= Y-1, Ymax #= Y+1,
|
|
Ay in Ymin..Ymax.
|
|
|
|
% get user operation from input
|
|
parse_input(Op, X, Y) :-
|
|
read_line_to_codes(user_input, In),
|
|
maplist(char_code, InChars, In),
|
|
phrase(mine_op(Op, X, Y), InChars, []).
|
|
|
|
mine_op(mark, X, Y) --> [m], [' '], coords(X, Y).
|
|
mine_op(open, X, Y) --> [o], [' '], coords(X, Y).
|
|
coords(Xi, Yi) --> number_(X), { number_chars(Xi, X) }, [' '], number_(Y), { number_chars(Yi, Y) }.
|
|
|
|
number_([D|T]) --> digit(D), number_(T).
|
|
number_([D]) --> digit(D).
|
|
digit(D) --> [D], { char_type(D, digit) }.
|
|
|
|
|
|
% Do mark operation
|
|
do_op(mark, P, G, Ng) :-
|
|
grid_val_xy(G, P, cell(_,A)),
|
|
grid_set_xy(G, P, cell('?',A), Ng).
|
|
|
|
% Do open operation, opening a bomb
|
|
do_op(open, P, G, Ng) :-
|
|
grid_val_xy(G, P, cell(_,'?')),
|
|
grid_set_xy(G, P, cell('*','?'), Ng).
|
|
|
|
% Do open operation, not a bomb
|
|
do_op(open, P, G, Ng) :-
|
|
grid_val_xy(G, P, cell(_,A)),
|
|
dif(A, '?'),
|
|
grid_set_xy(G, P, cell(A,A), Ng1),
|
|
expose_grid(P, Ng1, Ng).
|
|
|
|
% expose the grid by checking all the adjacent cells and operating
|
|
% appropriately
|
|
expose_grid(p(X,Y), grid(W,H,Cells), Ng2) :-
|
|
findall(p(Ax,Ay), (
|
|
adj(p(X,Y), dim(W,H), p(Ax,Ay)),
|
|
indomain(Ax), indomain(Ay)
|
|
), Coords),
|
|
expose_grid_(Coords, grid(W,H,Cells), Ng2).
|
|
|
|
expose_grid_([], G, G).
|
|
|
|
expose_grid_([H|T], G, Ng) :- % this cell has already been exposed, continue
|
|
grid_val_xy(G, H, cell(A,B)),
|
|
member(A, [B,'?']),
|
|
expose_grid_(T, G, Ng).
|
|
|
|
expose_grid_([H|T], G, Ng) :- % ignore bombs
|
|
grid_val_xy(G, H, cell(_,'?')),
|
|
expose_grid_(T, G, Ng).
|
|
|
|
expose_grid_([H|T], G, Ng) :- % is an integer, expose and continue
|
|
grid_val_xy(G, H, cell(_,N)),
|
|
integer(N),
|
|
N #> 0,
|
|
grid_set_xy(G, H, cell(N,N), Ng1),
|
|
expose_grid_(T, Ng1, Ng).
|
|
|
|
expose_grid_([H|T], G, Ng) :- % is a space, recurse
|
|
grid_val_xy(G, H, cell('.',0)),
|
|
grid_set_xy(G, H, cell(0,0), Ng1),
|
|
expose_grid(H, Ng1, Ng2),
|
|
expose_grid_(T, Ng2, Ng).
|