(very long) development styles

Sami Mäkelä sajuma@utu.fi
Mon, 21 Aug 2000 00:35:39 +0300 (EET DST)

```Here is a solution (I hope) in mostly functional style using O'Caml. It
was very hard for me to find a way to solve the problem, but FP helped
to write the code easily. I think there must be a faster and
more complete solution. (How good the SELF solution is?) I'd also like
to know if the problem can be solved using constraint programming.

------------------------------------------------------------------

(* First I defined the board as an ADT. It shouldn't be possible to make
illegal moves or have a board at bad state. *)
module Board : sig

type location
type move
type board

val start : board
val possible_moves : location -> board -> move list
val do_move : move -> board -> board
val buttons : board -> int
val empty_nodes : board -> location list
val print_board : board -> unit

end = struct

(* The structure of the board. *)
let structure =
[|
(* row 1 *) (2, 3);
(* row 2 *) (2, 3);
(* row 3 *) (0, 7);
(* row 4 *) (0, 7);
(* row 5 *) (0, 7);
(* row 6 *) (2, 3);
(* row 7 *) (2, 3);
|]

type location = int * int
type move = location * (location -> location)

(* State of the board is the list of empty locations. *)
type board = location list

(* An optimized List.mem. *)
let rec mmem ((x,y):int*int) = function
| [] -> false
| (x1,y1)::tl -> if x = x1 & y = y1 then true else mmem (x,y) tl

(* All the holes on the board. *)
let all_locs =
let lst = ref [] in
for i = 0 to Array.length structure - 1 do
let (a,b) = structure.(i) in
for j = a to a + b - 1 do
lst := (i,j) :: !lst
done
done;
!lst

(* How many holes there are. *)
let holes = List.length all_locs

(* Print the board. *)
let print_board board =
for i = 0 to Array.length structure - 1 do
let (a,b) = structure.(i) in
if a <> 0 then print_string (String.make a ' ');
for j = a to a + b - 1 do
if mmem (i,j) board then print_string "."
else print_string "X"
done;
print_string "\n"
done

(* Test if a location has a hole. *)
let at_board (x,y) =
if y < 0 or y >= Array.length structure then false else
let (a,b) = structure.(y) in
if x < a or x >= a+b then false else true

(* The directions. *)
let right (x,y) = (x+1,y)
let left (x,y) = (x-1,y)
let down (x,y) = (x,y+1)
let up (x,y) = (x,y-1)

(* Check what moves can be done into a hole. *)
let possible_moves loc board =
let try_move moves move =
let m1 = move loc in
let m2 = move m1 in
if at_board m2 & not (mmem m2 board) & not (mmem m1 board) then
(loc, move) :: moves else moves in
List.fold_left try_move [] [left;up;right;down]

(* Make a move at the board. *)
let do_move (loc, move) board =
move loc :: move (move loc) :: List.filter (fun k -> k <> loc) board

(* How many buttons left. *)
let buttons board = holes - List.length board

(* Empty holes at the board. *)
let empty_nodes board = board

(* The start state. *)
let start = [3,3]

end

(* Return all moves that can be made on the board. *)
let find_moves board =
let get_moves loc = Board.possible_moves loc board in
List.flatten (List.map get_moves (Board.empty_nodes board))

(* First I implemented a simple exhaustive search, it would take a very long
time to find a solution with this (I think it is about as inefficient
as a straighforward PROLOG solution). *)
let rec dfs_search_board choices path board =
if Board.buttons board = 1 then board :: path else
let get_choice () =
match choices with
| [] -> raise Not_found
| (move,board,path) :: b ->
dfs_search_board b (board::path) (Board.do_move move board) in
match find_moves board with
| [] -> get_choice ()
| [move] -> dfs_search_board choices (board::path) (Board.do_move move board)
| move :: tl ->
let new_choices = List.map (fun a -> (a,board,path)) tl @ choices in
dfs_search_board new_choices (board::path) (Board.do_move move board)

(* Next I added some randomization to the search, but it still couldn't find
solutions, it was able to find solutions that had two buttons left though.
*)

(* Count how many moves can be made on a board. *)
let count_moves board = List.length (find_moves board)

(* Return a random item from a list. *)
let get_one_random lst =
let nth = Random.int (List.length lst) in
let rec split_nth x = function
| (a::b) -> if x = nth then a else split_nth (x+1) b in
split_nth 0 lst

(* Then I realized that there is more likely a better solution if
there are more possible moves left. It should take about a minute to find a
solution with this tactic. *)
let rec open_most_moves path board =
if Board.buttons board < 10 then dfs_search_board [] path board else
match find_moves board with
| [] -> raise Not_found
| hmove :: tl ->
let get_best (a,moves) move =
let num = count_moves (Board.do_move move board) in
if a = num then (a, move::moves) else
if num > a then (num, [move]) else (a,moves) in
let _, lst = List.fold_left
get_best (count_moves (Board.do_move hmove board),[hmove]) tl in
let move = get_one_random lst in
open_most_moves (board::path) (Board.do_move move board)

(* Main program. *)
let _ =
Random.self_init ();
while true do
try
let res = open_most_moves [] Board.start in
List.iter (fun b -> Board.print_board b; print_string "\n") res;
flush stdout;
exit 0
with Not_found -> ()
done

```