Download
/*
 * SICSTUS CLPFD DEMONSTRATION PROGRAM
 * Purpose   : Solitaire Battleships
 * Author    : Mats Carlsson
 *
 * | ?- torpedo(id2794).
 */

:- module(torpedo, [torpedo/1]).

:- use_module(library(lists)).
:- use_module(library(clpfd)).

torpedo(ID) :-
	data(ID, PerRowData, PerColData, ShipData),
	length(PerRowData, NRows),
	length(PerColData, NCols),
	% set up domains for the battleship variables
	% a zero tally => that row or column must be empty
	(   foreach(X1,PerColData),
	    count(J1,1,_),
	    fromto(NoCols1,S0a,Sa,[])
	do  (X1=:=0 -> S0a = [J1|Sa] ; S0a = Sa)
	),
	list_to_fdset(NoCols1, NoCols2),
	fdset_parts(XDom1, 1, NCols, []),
	fdset_subtract(XDom1, NoCols2, XDom2),
	(   foreach(X2,PerRowData),
	    count(J2,1,_),
	    fromto(NoRows1,S0b,Sb,[])
	do  (X2=:=0 -> S0b = [J2|Sb] ; S0b = Sb)
	),
	list_to_fdset(NoRows1, NoRows2),
	fdset_parts(YDom1, 1, NRows, []),
	fdset_subtract(YDom1, NoRows2, YDom2),
	(   foreach(X3,ShipData),
	    fromto(TShips1,S0c,S1c,[]),
	    fromto(Coords,C0,C1,[]),
	    param([NCols,NRows,XDom2,YDom2])
	do  ship(X3, NCols, NRows, XDom2, YDom2, S0c, S1c, C0, C1)
	),
	% constrain wrt. the row and column tallies
	(   foreach(K-V,Coords),
	    foreach(K,Xs),
	    foreach(V,Ys)
	do  true
	),
	(   foreach(X4,PerColData),
	    foreach(J3-X4,XC),
	    count(J3,1,_)
	do  true
	),
	(   foreach(X5,PerRowData),
	    foreach(J4-X5,YC),
	    count(J4,1,_)
	do  true
	),
	global_cardinality(Xs, XC),
	global_cardinality(Ys, YC),
	% constrain ships to be non-adjacent
	(   foreach(_-Ship,TShips1),
	    foreach(Ship,Ships),
	    fromto(Vars,[W,H,X6,Y6|S],S,[])
	do  Ship = ship(X6,W,Y6,H)
	),
	disjoint2(Ships),
	% break symmetries: lex order ships of the same class
	keysort(TShips1, TShips2),
	keyclumped(TShips2, Groups),
	(   foreach(_-Group,Groups)
	do  (   foreach(ship(X7,_,Y7,_),Group),
		foreach([X7,Y7],Origs)
	    do  true
	    ),
	    lex_chain(Origs, [op(#<),global(true)])
	),
	% search: largest ship first
	labeling([bisect], Vars),
	% display solution
	draw(Ships, NRows, NCols).

ship(1, _NCols, _NRows, XDom2, YDom2, [1-ship(X,2,Y,2)|Ships], Ships, [X-Y|Coords], Coords) :-
	X in_set XDom2,
	Y in_set YDom2.
ship(2, NCols, NRows, XDom2, YDom2, [2-ship(X,W,Y,H)|Ships], Ships, [X-Y,X1-Y1|Coords], Coords) :-
	X in_set XDom2,
	Y in_set YDom2,
	W in {2,3},
	H in {2,3},
	X+W #=< NCols+2,
	Y+H #=< NRows+2,
	H #= 2 #<=> Horiz,
	W #= 2 #<=> Vert,
	Horiz #\ Vert,
	X1 #= X+Horiz,
	Y1 #= Y+Vert.
ship(3, NCols, NRows, XDom2, YDom2, [3-ship(X,W,Y,H)|Ships], Ships, [X-Y,X1-Y1,X2-Y2|Coords], Coords) :-
	X in_set XDom2,
	Y in_set YDom2,
	W in {2,4},
	H in {2,4},
	X+W #=< NCols+2,
	Y+H #=< NRows+2,
	H #= 2 #<=> Horiz,
	W #= 2 #<=> Vert,
	Horiz #\ Vert,
	X1 #= X+Horiz,
	Y1 #= Y+Vert,
	X2 #= X1+Horiz,
	Y2 #= Y1+Vert.
ship(4, NCols, NRows, XDom2, YDom2, [4-ship(X,W,Y,H)|Ships], Ships, [X-Y,X1-Y1,X2-Y2,X3-Y3|Coords], Coords) :-
	X in_set XDom2,
	Y in_set YDom2,
	W in {2,5},
	H in {2,5},
	X+W #=< NCols+2,
	Y+H #=< NRows+2,
	H #= 2 #<=> Horiz,
	W #= 2 #<=> Vert,
	Horiz #\ Vert,
	X1 #= X+Horiz,
	Y1 #= Y+Vert,
	X2 #= X1+Horiz,
	Y2 #= Y1+Vert,
	X3 #= X2+Horiz,
	Y3 #= Y2+Vert.

draw(Ships, NRows, NCols) :-
	format('+~*c+\n', [NCols,0'-]),
	draw_lines(0, NRows, NCols, Ships),
	format('+~*c+\n', [NCols,0'-]).

draw_lines(NR, NR, _, _) :- !.
draw_lines(I,  NR, NC, Ships) :-
	R is I+1,
	ascii_line(0, NC, R, Ships, String, "|\n"),
	format([0'||String], []),
	draw_lines(R, NR, NC, Ships).

ascii_line(NC, NC, _, _) --> !.
ascii_line(I, NC, R, Ships) -->
	{J is I+1},
	ascii_cell(R, J, Ships),
	ascii_line(J, NC, R, Ships).

ascii_cell(R, J, Ships) -->
	{Template = ship(X,W,Y,H),
	 member(Template, Ships),
	 X=<J, J<X+W-1,
	 Y=<R, R<Y+H-1}, !,
	"#".
ascii_cell(_, _, _) --> " ".

% A couple of items from Moshe Rubin's
% "List of unsolvable Solitaire Battleship boards"

:- dynamic
	data/3.
data(id113, % said to have 70 solutions
     [2,4,3,3,2,4,1,1,0,0],
     [0,5,0,2,2,3,1,3,2,2],
     [4,3,3,2,2,2,1,1,1,1]).
data(id1337, % said to have 49874 solutions
     [1,3,2,2,2,2,2,3,1,2],
     [3,0,4,0,3,0,3,1,2,4],
     [4,3,3,2,2,2,1,1,1,1]).
data(id2794, % said to have 1 solution
     [0,0,1,4,1,5,2,1,6,0],
     [1,3,3,1,1,4,0,1,1,5],
     [4,3,3,2,2,2,1,1,1,1]).