%
% ECLiPSe Nonogram Solver
%
% by Joachim Schimpf, IC-Parc, Imperial College, London, January 2001
%
% Problem:
%
% Nonograms are a popular puzzle, which goes by different names in
% different countries.  The player has to shade in squares in a grid so
% that blocks of consecutive shaded squares satisfy constraints given
% for each row and column.  Constraints typically indicate the sequence
% of shaded blocks (e.g. [3,1,2] means that there is a block of 3, then
% a gap of unspecified size, a block of length 1, another gap, and then
% a block of length 2). Data for sample problems is at the end of this file,
% for more see e.g. http://www.puzzle.gr.jp/nonogram/prob/0200_e.html
%
% Solution:
%
% This code solves all the problems below, the hardest one so far
% being p200 (25x25):
%
%	ps,n2-n16	by propagation alone
%	p197,p199,p200	with search, takes a while
%
% The main idea here is to have a powerful constraint (line_lookahead/4)
% which solves a single-line subproblem and exports the generalised
% result (using ECLiPSe's propia library).
%
% No particularly clever search strategy is used, just first-fail.
%

:- lib(ic).
:- lib(propia).

go(Name, Board) :-
data(Name, M, N, RowBlocks, ColBlocks),		% get the data
check_data(M, N, RowBlocks, ColBlocks),

dim(Board, [M,N]),
(						% row constraints
for(I,1,M),
foreach(Blocks,RowBlocks),
foreach(Positions,RowPositions),
param(Board,N)
do
matrix_row(Board, I, Line),
line_setup(N, Line, Blocks, Positions),
),
(						% column constraints
for(J,1,N),
foreach(Blocks,ColBlocks),
foreach(Positions,ColPositions),
param(Board,M)
do
matrix_column(Board, J, Line),
line_setup(M, Line, Blocks, Positions),
),
%	pretty_print(Board),

flatten([RowPositions,ColPositions], AllPositions),	% search
search(AllPositions, 0, first_fail, indomain, complete, []),
pretty_print(Board).

% setup constraints on one line (row or column)
%
% Line is an array of boolean variables
% Blocks is a list of block sizes (integers)
% Positions is a list of variables representing the block positions
% Gaps is a list of variables representing the gap sizes

line_setup(NFields, Line, Blocks, Positions) :-
length(Blocks, NBlocks),
dim(Line, [NFields]),			% field variables
Line[1..NFields] :: 0..1,
length(Positions, NBlocks),		% position variables
Positions :: 1..NFields,
NGaps is NBlocks+1,			% gap variables
length(Gaps, NGaps),
Gaps = [Gap1|Gaps2N],
once append(InnerGaps, [GapN], Gaps2N),
[Gap1,GapN] :: 0..NFields,		% outer gaps can be empty
InnerGaps :: 1..NFields,		% inner gaps must exist

sum(Line[1..NFields]) #= sum(Blocks),
(
foreach(Position,Positions),
fromto(Blocks, RightBlocks, RightBlocks1, []),
fromto([], LeftBlocks, [Block|LeftBlocks], _BlocksReverse),
fromto(Gaps2N, RightGaps, RightGaps1, []),
fromto([Gap1], LeftGaps, [RightGap|LeftGaps], _GapsReverse),
param(NFields,Line)
do
RightBlocks = [Block|RightBlocks1],
RightGaps = [RightGap|RightGaps1],
LeftGaps = [LeftGap|_],
Position #= 1 + sum(LeftBlocks) + sum(LeftGaps),
Position #= 1 + NFields - (sum(RightBlocks) + sum(RightGaps)),
place_block(Line, Position, LeftGap, Block, RightGap)
).

% constraint to update the Line-booleans that correspond
% to the block at Position and the adjacent gaps

place_block(Line, Position, LeftGap, BlockSize, RightGap) :-
nonvar(Position),
get_bounds(LeftGap, MinLeftGap, _),
( for(I,Position-MinLeftGap,Position-1), param(Line) do
arg(I, Line, 0)
),
( for(I,Position,Position+BlockSize-1), param(Line) do
arg(I, Line, 1)
),
get_bounds(RightGap, MinRightGap, _),
( for(I,Position+BlockSize,Position+BlockSize+MinRightGap-1), param(Line) do
arg(I, Line, 0)
).
place_block(Line, Position, LeftGap, BlockSize, RightGap) :-
var(Position),
suspend(place_block(Line, Position, LeftGap, BlockSize, RightGap), 2,
[Position->inst]).

% Lookahead constraint for one line:
% This uses propia to compute the most general solution
% for the single line subproblem

suspend(
solve_line_problem(NFields, Line, Positions, Blocks),
7,
[Line->inst,Positions->ic:min,Positions->ic:max]
) infers most.

solve_line_problem(NFields, Line, Positions, Blocks) :-
line_setup(NFields, Line, Blocks, Positions),
labeling(Positions).

%----------------------------------------------------------------------
% Auxiliaries
%----------------------------------------------------------------------

matrix_row(Mat, I, Row) :-
Row is Mat[I].

matrix_column(Mat, J, Col) :-
dim(Mat, [M, _N]),
ColList is Mat[1..M,J],
Col =.. [[]|ColList].

pretty_print(Board) :-
dim(Board, [M,N]),
( for(I,1,M), param(Board,N) do
( for(J,1,N), param(Board,I) do
X is Board[I,J],
( X==0 -> write("  ")
; X==1 -> write(" *")
;         write(" ?")
)
), nl
), nl.

%----------------------------------------------------------------------
% sample problems
%
% data(ProblemName, NRows, NColumns, RowBlocks, ColumnBlocks)
%----------------------------------------------------------------------

% from http://www-lp.doc.ic.ac.uk/UserPages/staff/ft/alp/humour/visual/nono.html
data(ps, 9, 8,
[[3],[2,1],[3,2],[2,2],[6],[1,5],[6],[1],[2]],	% row blocks
[[1,2],[3,1],[1,5],[7,1],[5],[3],[4],[3]]		% column blocks
).

% from http://www.pro.or.jp/~fuji/java/puzzle/nonogram/index-eng.html
data(n2, 10, 10,
[[1],[3],[1,3],[2,4],[1,2],[2,1,1],[1,1,1,1],[2,1,1],[2,2],[5]],
[[4],[1,3],[2,3],[1,2],[2,2],[1,1,1],[1,1,1,1],[1,1,1],[1,2],[5]]
).
data(n3, 10, 15,
[[4],[1,1,6],[1,1,6],[1,1,6],[4,9],[1,1],[1,1],[2,7,2],[1,1,1,1],[2,2]],
[[4],[1,2],[1,1],[5,1],[1,2],[1,1],[5,1],[1,1],[4,1],[4,1],[4,2],[4,1],[4,1],[4,2],[4]]
).
data(n4, 6, 6,
[[2,1],[1],[2],[2],[1],[1,2]],
[[1,2],[1],[2],[2],[1],[2,1]]
).
data(n5, 10, 10,
[[3],[3],[1],[3],[6],[3],[3],[3,3],[2,2],[2,1]],
[[1],[1,2],[1,2],[1,1],[2,5],[7],[2,5],[1],[2],[2]]
).
data(n6, 15, 15,
[[5],[2,2],[1,1],[1,1],[4,4],[2,2,1,2],[1,3,1],[1,1,1,1],[2,7,2],[4,1,5],[2,1,1],[1,1,2],[1,1,1],[2,5,2],[3,4]],
[[4],[2,2],[1,5],[1,2,2],[5,2,1],[2,1,1,2],[1,3,1],[1,1,6],[1,3,1],[2,1,2,2],[4,2,1],[1,1,1],[1,3,2],[2,2,3],[4]]
).
data(n16, 15, 15,
[[4],[2,2],[2,2],[2,4,2],[2,1,1,2],[2,4,2],[1,2],[4,4,4],[1,1,1,1,1,1],[4,1,1,4],[1,1,1],[1,1,3],[10],[2,1],[4,1]],
[[5,1],[2,1,1,1],[2,1,1,2],[2,3,3],[2,1],[2,3,6],[1,1,1,1,1],[1,1,1,1,1],[2,3,6],[2,1],[2,3,1],[2,1,1,1],[2,1,1,4],[7],[1,1]]
).
data(n19, R, C, RB, CB) :-
data(p199, R, C, RB, CB).

% from http://www.puzzle.gr.jp/nonogram/prob/0200_e.html
data(p197, 20, 15,	% difficulty 7
[[3],[1,2],[1,4],[1,1,2],[1,1,1,1],[1,3,2],[2,3,1],[1,1,1,2],[2,2,2],[1,1,2,2],[1,1,2,2],[1,1,1,1],[4,1,1],[2,2,2,1],[2,3,3],[2,2,3],[1,3,1,1],[2,1,1,1,2],[1,2,3],[1,6]],
[[4,3],[6,1,2,3],[2,3],[6],[1,2,2],[1,1,2],[2,4,1,1],[1,1,2,2,2,1],[1,1,1,2,1,1],[1,3,2,3],[3,2,2],[4,3,4,2],[1,3,4,5],[2,2],[3]]
).
data(p199, 20, 20,	% difficulty 8
[[1,1,4],[1,6],[1,1,1,1,2,3],[1,1,2,3],[3,1,2,3],[4,5,2,2],[7,3,2],[3,5,1,2],[2,2,4,1],[2,2,3,4],[2,5,2],[2,1,5,1],[2,2,3,1],[6,2,2],[1,7],[2,2,2],[1,4],[3,1,1],[1,1],[1,1]],
[[6,1],[8,3],[3,2,1],[1,1,2,2,1],[1,2,2,1,1],[1,1,1,1],[2,3],[4,1,2,2],[5,2,1],[8,1,1],[7,2],[3,5,2],[2,5],[2,1,4],[2,2,2,2],[2,2,1,1,1],[3,1,1,1,1],[5,4,2,1],[7,4,1,1],[4]]
).
data(p200, 25, 25,	% difficulty 9
[[1,1,2,2],[5,5,7],[5,2,2,9],[3,2,3,9],[1,1,3,2,7],[3,1,5],[7,1,1,1,3],[1,2,1,1,2,1],[4,2,4],[1,2,2,2],[4,6,2],[1,2,2,1],[3,3,2,1],[4,1,15],[1,1,1,3,1,1],[2,1,1,2,2,3],[1,4,4,1],[1,4,3,2],[1,1,2,2],[7,2,3,1,1],[2,1,1,1,5],[1,2,5],[1,1,1,3],[4,2,1],[3]],
[[2,2,3],[4,1,1,1,4],[4,1,2,1,1],[4,1,1,1,1,1,1],[2,1,1,2,3,5],[1,1,1,1,2,1],[3,1,5,1,2],[3,2,2,1,2,2],[2,1,4,1,1,1,1],[2,2,1,2,1,2],[1,1,1,3,2,3],[1,1,2,7,3],[1,2,2,1,5],[3,2,2,1,2],[3,2,1,2],[5,1,2],[2,2,1,2],[4,2,1,2],[6,2,3,2],[7,4,3,2],[7,4,4],[7,1,4],[6,1,4],[4,2,2],[2,1]]
).

% the example quoted in Optima#65, Mathematical Programming Society Newsletter
data(optima, 20, 20,
[[7,1],[1,1,2],[2,1,2],[1,2,2],[4,2,3],[3,1,4],[3,1,3],[2,1,4],[2,9],[2,1,5],[2,7],[14],[8,2],[6,2,2],[2,8,1,3],[1,5,5,2],[1,3,2,4,1],[3,1,2,4,1],[1,1,3,1,3],[2,1,1,2]],
[[1,1,1,2],[3,1,2,1,1],[1,4,2,1,1],[1,3,2,4],[1,4,6,1],[1,11,1],[5,1,6,2],[14],[7,2],[7,2],[6,1,1],[9,2],[3,1,1,1],[3,1,3],[2,1,3],[2,1,5],[3,2,2],[3,3,2],[2,3,2],[2,6]]
).

% simple check for typos in the data

check_data(M, N, RowBlocks, ColBlocks) :-
length(RowBlocks, M),
length(ColBlocks, N),
( foreach(Blocks,RowBlocks), fromto(0,S0,S1,RowTotal) do
S1 is S0+sum(Blocks)
),
( foreach(Blocks,ColBlocks), fromto(0,S0,S1,ColTotal) do
S1 is S0+sum(Blocks)
),
RowTotal = ColTotal,
!.
check_data(_,_,_,_) :-
writeln("Inconsistent input data!"),
abort.