RosettaCodeData/Task/2048/Prolog/2048.pro

159 lines
5.8 KiB
Prolog

/* -------------------------------------------------------------
Entry point, just create a blank grid and enter a 'game loop'
-------------------------------------------------------------*/
play_2048 :-
welcome_msg,
length(Grid, 16), maplist(=(' '), Grid), % create a blank grid
play(Grid, yes), !. % don't have to cut here but it makes the exit cleaner
/* -----------------------------------------------
Messages that will be printed at various points
-----------------------------------------------*/
welcome_msg :-
format('~nWelcome to the Prolog version of 2048~n~n'),
format('To play using w,s,a,d keys for movement, q to quit~n~n').
contrats_msg :- format('Congratulations, you reached 2048!~n~n').
loser_msg :- format('Uh Oh, you could not quite make it to 2048...~n~n').
quit_msg :- format('Bye then!~n~n').
/* -------------------
End game conditions
-------------------*/
player_not_won_yet(Grid) :- maplist(dif(2048), Grid).
player_wins(Grid) :- member(2048, Grid).
player_loses(G) :- move(up, G, G), move(down, G, G), move(left, G, G), move(right, G, G).
/* ---------
Game loop
---------*/
% First check if the player has reached the win condition, if not find how many spaces are left
play(Grid, _) :-
player_wins(Grid),
draw_grid(Grid),
contrats_msg.
play(Grid, CreateNewNum) :-
player_not_won_yet(Grid),
include(=(' '), Grid, Spaces), length(Spaces, NSpaces), % the number of spaces in the grid
play(Grid, CreateNewNum, NSpaces).
% knowing the number of spaces, determine if there is a space, and if we need a new number, and generate.
play(Grid, no, _) :- play(Grid).
play(Grid, yes, 0) :- play(Grid).
play(Grid, yes, NSpaces) :-
dif(NSpaces, 0),
random_space(NSpaces, Grid, GridWithRandom),
play(GridWithRandom).
% with the new number on the grid we can tell if the player has lost the game yet
% if not, draw the grid and get the next action by the player
play(Grid) :-
player_loses(Grid),
draw_grid(Grid),
loser_msg.
play(Grid) :-
\+ player_loses(Grid),
draw_grid(Grid),
next_move_by_player(Move),
player_made_move(Grid, Move).
% determine the result of player move
player_made_move(_, quit).
player_made_move(Grid, Move) :-
dif(Move, quit),
move(Move, Grid, Grid), % The move creating the same grid indicates that no merge was done
play(Grid, no). % don't create a new number
player_made_move(Grid, Move) :-
dif(Move, quit),
move(Move, Grid, MovedGrid),
dif(Grid, MovedGrid),
play(MovedGrid, yes).
/* ---------------------------------------
Get the next move from the player input
---------------------------------------*/
next_move_by_player(Move) :-
repeat,
get_single_char(Char),
key_move(Char, Move).
% valid keys are: up = 'w', down = 's', left = 'a' right = 'd', quit = 'q'
key_move(119, up). key_move(115, down). key_move(97, left). key_move(100, right). key_move(113, quit).
/* ------------------
Draw the Game grid
------------------*/
draw_grid([A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4]) :-
format( '+-------------------+~n'),
row([A1,A2,A3,A4]),
row([B1,B2,B3,B4]),
row([C1,C2,C3,C4]),
maplist(draw, [D1,D2,D3,D4]),
format('¦~n+-------------------+~n~n~n').
row([A,B,C,D]) :- maplist(draw, [A,B,C,D]), format('¦~n¦----+----+----+----¦~n').
draw(' ') :- format('¦ ').
draw(X) :- member(X,[2,4,8]), format('¦ ~d ', X).
draw(X) :- member(X,[16,32,64]), format('¦ ~d ', X).
draw(X) :- member(X,[128,256,512]), format('¦ ~d', X).
draw(X) :- member(X,[1024,2048]), format('¦~d', X).
/* ----------------------------------------
Populate a random space with a new value
----------------------------------------*/
random_space(0, G, G).
random_space(1, Grid, GridWithRandom) :-
four_or_two(V),
select(' ', Grid, V, GridWithRandom).
random_space(N, Grid, GridWithRandom) :-
N > 1,
four_or_two(V),
random(1, N, P),
replace_space(P, V, Grid, GridWithRandom).
replace_space(0, V, [' '|T], [V|T]).
replace_space(P, V, [' '|T], [' '|R]) :- succ(NextP, P), replace_space(NextP, V, T, R).
replace_space(P, V, [H|T], [H|R]) :- dif(' ', H), replace_space(P, V, T, R).
four_or_two(V) :- random(1, 10, IsFour), IsFour = 1 -> V = 4 ; V = 2.
/* ------------------------------------------
Process a game move based on the direction
------------------------------------------*/
move(Direction, UnMoved, Moved) :-
map_move(Direction, UnMoved, UnMovedMapped),
maplist(combine_row, UnMovedMapped, MovedMapped),
map_move(Direction, Moved, MovedMapped).
% convert the array to a set of lists that can be moved in the same
% direction. This can be reversed after the move is completed.
map_move(up, [A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4], [[D1,C1,B1,A1],[D2,C2,B2,A2],[D3,C3,B3,A3],[D4,C4,B4,A4]]).
map_move(down, [A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4], [[A1,B1,C1,D1],[A2,B2,C2,D2],[A3,B3,C3,D3],[A4,B4,C4,D4]]).
map_move(left, [A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4], [[A4,A3,A2,A1],[B4,B3,B2,B1],[C4,C3,C2,C1],[D4,D3,D2,D1]]).
map_move(right, [A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4], [[A1,A2,A3,A4],[B1,B2,B3,B4],[C1,C2,C3,C4],[D1,D2,D3,D4]]).
% remove all the spaces, then put them at the front of the list
combine_row(UnMoved, Moved) :-
partition(=(' '), UnMoved, Blank, Set),
append(Blank, Set, ReadyToMove),
combine(ReadyToMove, Moved).
% combine based on the rules of the game.
combine([A,B,C,D], [A,B,C,D]) :- dif(A,B), dif(B,C), dif(C,D).
combine([A,A,B,B], [' ',' ',Ad,Bd]) :- dbl(A,Ad), dbl(B,Bd).
combine([A,B,C,C], [' ',A,B,Cd]) :- dif(A,B), dbl(C,Cd).
combine([A,B,B,C], [' ',A,Bd,C]) :- dif(B,C), dbl(B,Bd).
combine([A,A,B,C], [' ',Ad,B,C]) :- dif(A,B), dif(B,C), dbl(A, Ad).
combine([A,B,C,C], [' ',A,B,Cd]) :- dif(A,B), dif(B,C), dbl(C,Cd).
% this could be done using maths, but it is more prology this way.
dbl(' ', ' ').
dbl(2,4). dbl(4,8). dbl(8,16). dbl(16,32). dbl(32,64). dbl(64,128). dbl(128,256). dbl(256,512). dbl(512,1028). dbl(1028,2048).