Created
May 9, 2021 10:09
-
-
Save madgen/ef0c0a6f73ba7e34bf5cfbc4ee0877e0 to your computer and use it in GitHub Desktop.
Solver for tetris puzzles in Prolog
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
% Transitioning between steps | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
driver(History, History, (_,_,_,FinalBoard), FinalBoard). | |
driver(History, Trace, State, FinalBoard) :- | |
% print_state(State), | |
step(State, NewState), | |
driver([NewState|History], Trace, NewState, FinalBoard). | |
step(State, NewState) :- step_gravitate(State, NewState). | |
step(State, NewState) :- step_move_right(State, NewState). | |
step(State, NewState) :- step_move_left(State, NewState). | |
step(State, NewState) :- step_clear(State, NewState). | |
step(State, NewState) :- step_next(State, NewState). | |
step_gravitate( | |
(Tetrominos, _, N, Board), | |
(Tetrominos, [left, right], N, NewBoard) | |
) :- | |
gravitate(N, Board, NewBoard), | |
Board \= NewBoard, | |
conflict_free(N, Board, NewBoard). | |
step_move_right( | |
(Tetrominos, Direction, N, Board), | |
(Tetrominos, [right], N, NewBoard) | |
) :- | |
member(right, Direction), | |
movable(N, Board), | |
move_right(N, Board, NewBoard), | |
Board \= NewBoard, | |
conflict_free(N, Board, NewBoard). | |
step_move_left( | |
(Tetrominos, Direction, N, Board), | |
(Tetrominos, [left], N, NewBoard) | |
) :- | |
member(left, Direction), | |
movable(N, Board), | |
move_left(N, Board, NewBoard), | |
Board \= NewBoard, | |
conflict_free(N, Board, NewBoard). | |
step_clear( | |
(Tetrominos, Direction, N, Board), | |
(Tetrominos, Direction, M, NewBoard) | |
) :- | |
clear(Board, NewBoard), | |
M is N + 1. | |
step_next( | |
(Tetrominos, _, N, Board), | |
(RemainingTetrominos, [left, right], M, NewBoard) | |
) :- | |
M is N + 1, | |
select(Tetromino, Tetrominos, RemainingTetrominos), | |
tetromino(Tetromino, M, Pattern), | |
length(Board, Height), | |
position_pattern(Height, Pattern, PatternBoard), | |
overlay(PatternBoard, Board, NewBoard), | |
conflict_free(M, Board, NewBoard). | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
% Auxiliary definitions | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
wall_row([-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]). | |
empty_row([-1,0,0,0,0,0,0,0,0,0,0,-1]). | |
% Generate an empty board of a given height | |
empty_board(Height, [Wall|PartialState]) :- | |
wall_row(Wall), | |
empty_board_aux(Height,PartialState). | |
empty_board_aux(0, []). | |
empty_board_aux(Height, [EmptyRow|Rows]) :- | |
empty_row(EmptyRow), | |
Height > 0, | |
RemainingHeight is Height - 1, | |
empty_board_aux(RemainingHeight, Rows). | |
% Overlay two boards so long they agree on the cell value or one of the cells | |
% being overlayed is 0. | |
overlay(Board1, Board2, OverlayedBoard) :- | |
maplist(overlay_row, Board1, Board2, OverlayedBoard). | |
overlay_row([],YS,YS) :- !. | |
overlay_row(XS,[],XS) :- !. | |
overlay_row([0|XS],[Y|YS],[Y|ZS]) :- !, overlay_row(XS,YS,ZS). | |
overlay_row([X|XS],[0|YS],[X|ZS]) :- !, overlay_row(XS,YS,ZS). | |
overlay_row([X|XS],[X|YS],[X|ZS]) :- overlay_row(XS,YS,ZS). | |
% Create a board with only the pattern placed at the top of it | |
position_pattern(Height, Pattern, Board) :- | |
maplist(right_pad_pattern_row, Pattern, PatternRows), | |
length(Pattern, PatternHeight), | |
RemainingHeight is Height - PatternHeight - 1, | |
empty_board(RemainingHeight, PartialBoard), | |
append(PartialBoard, PatternRows, Board). | |
right_pad_pattern_row(PartialRow, Row) :- | |
empty_row(EmptyRow), | |
overlay_row([-1|PartialRow], EmptyRow, Row). | |
% Check if there is a conflict between two boards. | |
% Only overlapping of identical blocks and against a 0 cell is allowed. | |
conflict_free(N, OldBoard, NewBoard) :- | |
maplist(conflict_free_row(N), OldBoard, NewBoard). | |
conflict_free_row(N,OldRow, NewRow) :- | |
maplist(conflict_free_cell(N), OldRow, NewRow). | |
conflict_free_cell(N,M,N) :- !, (M = 0; M = N). | |
conflict_free_cell(_,_,_). | |
% Clear one line of a board | |
clear(Board, NewBoard) :- | |
select(Row, Board, Rest), | |
can_clear_row(Row), | |
empty_row(EmptyRow), | |
append(Rest, [EmptyRow], NewBoard). | |
can_clear_row(Row) :- | |
maplist(can_clear_cell, Row), | |
\+ sum_list(Row, -12). | |
can_clear_cell(-1). | |
can_clear_cell(N) :- N > 0. | |
% Make a block drop by one | |
gravitate(_, [Row], [Row]). | |
gravitate(N, [OldRow1,OldRow2|OldRest], [Row1|Rest]) :- | |
gravitate_row(N, OldRow1, OldRow2, Row1, Row2), | |
gravitate(N, [Row2|OldRest], Rest). | |
gravitate_row(_, [], [], [], []). | |
gravitate_row(N, [_|XS], [N|YS], [N|ZS], [0|WS]) :- !, gravitate_row(N, XS, YS, ZS, WS). | |
gravitate_row(N, [X|XS], [Y|YS], [X|ZS], [Y|WS]) :- gravitate_row(N, XS, YS, ZS, WS). | |
% Move the block to the right by one | |
move_right(N, State, NewState) :- | |
maplist(reverse, State, RevState), | |
move_left(N, RevState, NewRevState), | |
maplist(reverse, NewRevState, NewState). | |
% Move the block to the left by one | |
move_left(N, State, NewState) :- maplist(move_left_row(N), State, NewState). | |
move_left_row(_, [Cell], [Cell]). | |
move_left_row(N, [_,N|Rest], [N|NewRest]) :- !, | |
move_left_row(N, [0|Rest], NewRest). | |
move_left_row(N, [Cell1,Cell2|Rest], [Cell1|NewRest]) :- | |
move_left_row(N, [Cell2|Rest], NewRest). | |
% Height of the stable set of blocks | |
% The implementation is buggy when the currently moving is right above or | |
% overlapping with stable blocks | |
height([],0). | |
height([Row|_],0) :- empty_row(Row), !. | |
height([_|Rows],Height) :- height(Rows,OldHeight), Height is OldHeight + 1. | |
% Distance of a block from the bottom | |
block_distance(_,[],0). | |
block_distance(N,[Row|_],0) :- member(N,Row), !. | |
block_distance(N,[_|Rows],Height) :- | |
block_distance(N,Rows,OldHeight), | |
Height is OldHeight + 1. | |
movable(N,State) :- | |
height(State, Height), | |
block_distance(N, State, MinHeight), | |
MinHeight =< Height. | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
% Tetrominos | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
tetromino(o,N,[[N,N] | |
,[N,N] | |
]). | |
tetromino(i,N,[[N,N,N,N] | |
]). | |
tetromino(i,N,[[N] | |
,[N] | |
,[N] | |
,[N] | |
]). | |
tetromino(t,N,[[N,N,N] | |
,[0,N] | |
]). | |
tetromino(t,N,[[0,N] | |
,[N,N,N] | |
]). | |
tetromino(t,N,[[0,N] | |
,[N,N] | |
,[0,N] | |
]). | |
tetromino(t,N,[[N] | |
,[N,N] | |
,[N] | |
]). | |
tetromino(j,N,[[0,N] | |
,[0,N] | |
,[N,N] | |
]). | |
tetromino(j,N,[[N] | |
,[N,N,N] | |
]). | |
tetromino(j,N,[[N,N] | |
,[N] | |
,[N] | |
]). | |
tetromino(j,N,[[N,N,N] | |
,[0,0,N] | |
]). | |
tetromino(l,N,[[N] | |
,[N] | |
,[N,N] | |
]). | |
tetromino(l,N,[[0,0,N] | |
,[N,N,N] | |
]). | |
tetromino(l,N,[[N,N] | |
,[0,N] | |
,[0,N] | |
]). | |
tetromino(l,N,[[N,N,N] | |
,[N] | |
]). | |
tetromino(s,N,[[0,N,N] | |
,[N,N] | |
]). | |
tetromino(s,N,[[N] | |
,[N,N] | |
,[0,N] | |
]). | |
tetromino(z,N,[[N,N] | |
,[0,N,N] | |
]). | |
tetromino(z,N,[[0,N] | |
,[N,N] | |
,[N] | |
]). | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
% Printing | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
print_trace(Trace) :- | |
reverse(Trace, RevTrace), | |
writeln(''), | |
maplist(print_state, RevTrace). | |
print_state((_,_,_,State)) :- print_board(State). | |
print_board(Board) :- | |
reverse(Board, RevBoard), | |
maplist(print_row, RevBoard), | |
writeln(""). | |
print_row(Row) :- maplist(print_cell, Row), writeln(""). | |
print_cell(Cell) :- cell_to_char(Cell, Str), format('~w', [Str]). | |
cell_to_char(-1, 'X') :- !. | |
cell_to_char(0, ' ') :- !. | |
cell_to_char(1, 'A'). | |
cell_to_char(2, 'B'). | |
cell_to_char(3, 'C'). | |
cell_to_char(4, 'D'). | |
cell_to_char(5, 'E'). | |
cell_to_char(6, 'F'). | |
cell_to_char(7, 'G'). | |
cell_to_char(8, 'H'). | |
cell_to_char(9, 'I'). | |
cell_to_char(10, 'J'). | |
cell_to_char(11, 'K'). | |
cell_to_char(12, 'L'). | |
cell_to_char(13, 'M'). | |
cell_to_char(14, 'N'). | |
cell_to_char(15, 'O'). | |
cell_to_char(16, 'P'). | |
cell_to_char(17, 'Q'). | |
cell_to_char(18, 'R'). | |
cell_to_char(19, 'S'). | |
cell_to_char(20, 'T'). | |
cell_to_char(21, 'U'). | |
cell_to_char(22, 'V'). | |
cell_to_char(23, 'W'). | |
cell_to_char(24, 'Y'). | |
cell_to_char(25, 'Z'). | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
% Unit testing | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
prng(Seed, Random) :- Random is (Seed * 7 + 3) mod 9 + 1. | |
full_row(Seed, [-1,A,B,C,D,E,F,G,H,I,J,-1]) :- | |
prng(Seed, A), | |
prng(A, B), | |
prng(B, C), | |
prng(C, D), | |
prng(D, E), | |
prng(E, F), | |
prng(F, G), | |
prng(G, H), | |
prng(H, I), | |
prng(I, J). | |
test_board( | |
[WallRow | |
,[-1,0,0,0,1,0,0,4,0,0,0,-1] | |
,[-1,2,2,0,1,1,0,0,3,0,0,-1] | |
,[-1,0,2,0,0,1,0,3,3,0,0,-1] | |
,[-1,0,2,0,0,0,0,3,0,0,0,-1] | |
] | |
) :- wall_row(WallRow). | |
?- | |
write('Test: step clear single... '), | |
wall_row(WallRow), | |
empty_row(EmptyRow), | |
full_row(0,FullRow), | |
B0 = [WallRow,FullRow], | |
B1 = [WallRow,EmptyRow], | |
step_clear( | |
(_,_,1,B0), | |
(_,_,_,B1) | |
), | |
writeln('ok'). | |
?- | |
write('Test: step clear multi... '), | |
wall_row(WallRow), | |
empty_row(EmptyRow), | |
full_row(1,FullRow1), | |
full_row(2,FullRow2), | |
B0 = [WallRow,FullRow1,FullRow2], | |
B1 = [WallRow,FullRow2,EmptyRow], | |
B2 = [WallRow,EmptyRow,EmptyRow], | |
step_clear( | |
(_,_,1,B0), | |
(_,_,_,B1) | |
), | |
step_clear( | |
(_,_,1,B1), | |
(_,_,_,B2) | |
), | |
writeln('ok'). | |
?- | |
write('Test: step right 1... '), | |
test_board(B0), | |
wall_row(WallRow), | |
B1 = | |
[WallRow | |
,[-1,0,0,0,0,1,0,4,0,0,0,-1] | |
,[-1,2,2,0,0,1,1,0,3,0,0,-1] | |
,[-1,0,2,0,0,0,1,3,3,0,0,-1] | |
,[-1,0,2,0,0,0,0,3,0,0,0,-1] | |
], | |
step_move_right((_,_,1,B0),(_,_,_,B1)), | |
writeln(' ok'). | |
?- | |
write('Test: step right 2'), | |
test_board(B0), | |
wall_row(WallRow), | |
B1 = | |
[WallRow | |
,[-1,0,0,0,1,0,0,4,0,0,0,-1] | |
,[-1,0,2,2,1,1,0,0,3,0,0,-1] | |
,[-1,0,0,2,0,1,0,3,3,0,0,-1] | |
,[-1,0,0,2,0,0,0,3,0,0,0,-1] | |
], | |
step_move_right((_,_,2,B0),(_,_,_,B1)), | |
writeln('ok'). | |
?- | |
write('Test: step right 3... '), | |
test_board(B0), | |
wall_row(WallRow), | |
B1 = | |
[WallRow | |
,[-1,0,0,0,1,0,0,4,0,0,0,-1] | |
,[-1,2,2,0,1,1,0,0,0,3,0,-1] | |
,[-1,0,2,0,0,1,0,0,3,3,0,-1] | |
,[-1,0,2,0,0,0,0,0,3,0,0,-1] | |
], | |
step_move_right((_,_,3,B0),(_,_,_,B1)), | |
writeln('ok'). | |
?- | |
write('Test: step left 1... '), | |
test_board(B0), | |
step_move_right((_,_,1,B0),(_,_,_,B1)), | |
step_move_left((_,_,1,B1),(_,_,_,B0)), | |
writeln('ok'). | |
?- | |
write('Test: step left 2... '), | |
test_board(B0), | |
step_move_right((_,_,2,B0),(_,_,_,B1)), | |
step_move_left((_,_,2,B1),(_,_,_,B0)), | |
writeln('ok'). | |
?- | |
write('Test: step left 3... '), | |
test_board(B0), | |
step_move_right((_,_,3,B0),(_,_,_,B1)), | |
step_move_left((_,_,3,B1),(_,_,_,B0)), | |
writeln('ok'). | |
?- | |
write('Test: step gravitate... '), | |
test_board(B0), | |
wall_row(WallRow), | |
B1 = | |
[WallRow | |
,[-1,0,0,0,1,0,0,4,3,0,0,-1] | |
,[-1,2,2,0,1,1,0,3,3,0,0,-1] | |
,[-1,0,2,0,0,1,0,3,0,0,0,-1] | |
,[-1,0,2,0,0,0,0,0,0,0,0,-1] | |
], | |
step_gravitate((_,_,3,B0),(_,_,_,B1)), | |
writeln('ok'). | |
?- | |
write('Test: integration 1... '), | |
wall_row(WallRow), | |
empty_row(EmptyRow), | |
BasicRow = [-1,1,1,0,0,0,0,1,1,1,1,-1], | |
InitBoard = [WallRow, BasicRow, BasicRow, EmptyRow, EmptyRow], | |
empty_board(4, FinalBoard), | |
Tetrominos = [o,o], | |
InitState = (Tetrominos, [left,right], 2, InitBoard), !, | |
driver([InitState], Trace, InitState, FinalBoard), !, | |
writeln('ok'), | |
print_trace(Trace). | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
% Solve the fucking puzzle | |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
?- | |
empty_board(2, [Wall|EmptyRows]), | |
PuzzleRow = [-1,1,0,0,0,0,0,0,0,0,1,-1], | |
InitBoard = [Wall,PuzzleRow|EmptyRows], | |
empty_board(3, FinalBoard), | |
setof(Tetromino, N^Pattern^tetromino(Tetromino,N,Pattern), Tetrominos), | |
InitState = (Tetrominos, [left,right], 2, InitBoard), !, | |
driver([InitState], Trace, InitState, FinalBoard), !, | |
print_trace(Trace). |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment