(**************************************************************************)
(*    Lablgtk - Examples                                                  *)
(*                                                                        *)
(*    This code is in the public domain.                                  *)
(*    You may freely copy parts of it in your application.                *)
(*                                                                        *)
(**************************************************************************)

let piece_size = 50

let piece_color nb =
  let y = nb / 4 in
  let x = nb mod 4 in
  let r = (4 - x) * 255 / 4 in
  let g = (4 - y) * 255 / 4 in
  let b = 128 in
  Printf.sprintf "#%02x%02x%02x" r g b

type config = {
    canvas : GnoCanvas.canvas ;
    board : (GnoCanvas.group * GnoCanvas.text) array ;
    pos : int array ;
    mutable hole : int ;
  }
    

let move config num dpos =
  assert(List.mem dpos [ -1; 1; -4; 4]) ;
  let (it, _ ) = config.board.(num) in
  it#move 
    ~x:(float (dpos mod 4 * piece_size))
    ~y:(float (dpos / 4 * piece_size))
    

let item_event config num ev =
  begin match ev with
  | `ENTER_NOTIFY _ ->
      let (_, text) = config.board.(num) in
      text#set [ `FILL_COLOR "white" ]
  | `LEAVE_NOTIFY _ ->
      let (_, text) = config.board.(num) in
      text#set [ `FILL_COLOR "black" ]
  | `BUTTON_PRESS _ ->
      let pos = config.pos.(num) in
      if List.mem (config.hole - pos) [ -1; 1; 4; -4; ]
      then
	let dpos = config.hole - pos in
	config.hole <- config.hole - dpos ;
	config.pos.(num) <- config.pos.(num) + dpos ;
	move config num dpos ;
	config.canvas#update_now ()
  | _ -> ()
  end ;
  false

let scramble_moves = 128

let array_find a v =
  let imax = Array.length a in
  let rec proc = function
    | i when i = imax -> raise Not_found
    | i when a.(i) = v -> i
    | i -> proc (succ i) in
  proc 0

let scramble config () =
  for i = 1 to scramble_moves do
    let new_pos = ref (-1) in
    let ok = ref false in
    while not !ok do
      let dpos = Array.get [| -1; 1; -4; 4|] (Random.int 4) in
      new_pos := config.hole + dpos ;
      if not ((config.hole mod 4 = 0 && dpos = -1) ||
              (config.hole mod 4 = 3 && dpos =  1) ||
              !new_pos < 0 || !new_pos > 15)
      then ok := true
    done ;
    let num = array_find config.pos !new_pos in
    move config num (config.hole - !new_pos) ;
    config.pos.(num) <- config.hole ;
    config.hole <- !new_pos ;
    config.canvas#update_now ()
  done


let create_canvas_fifteen window = 
  let vbox = GPack.vbox ~border_width:4 ~packing:window#add () in
  let align = GBin.alignment ~packing:vbox#add () in
  let frame = GBin.frame ~shadow_type:`IN ~packing:align#add () in
  let dim = piece_size * 4 + 1 in
  let canvas = GnoCanvas.canvas
      ~width:dim ~height:dim
      ~packing:frame#add () in
  canvas#set_scroll_region 0. 0. (float dim) (float dim) ;
  let board = Array.init 15
      (fun i ->
	let x = i mod 4 in
	let y = i / 4 in
	let tile = 
	  GnoCanvas.group 
	    ~x:(float (x * piece_size)) ~y:(float (y * piece_size))
	    canvas#root in
	GnoCanvas.rect tile
	  ~props:[ `X1 0.; `Y1 0. ; `X2 (float piece_size) ; `Y2 (float piece_size) ;
		   `FILL_COLOR (piece_color i) ; `OUTLINE_COLOR "black" ;
		   `WIDTH_PIXELS 0 ] ;
	let text = 
	  GnoCanvas.text tile
	    ~props:[ `TEXT (string_of_int (succ i)) ; 
		     `X (float piece_size /. 2.) ;
		     `Y (float piece_size /. 2.) ;
		     `FONT "Sans bold 24" ;
		     `FILL_COLOR "black" ;
		     `ANCHOR `CENTER ] in
	(tile, text)) in
  let config = {
    canvas = canvas ;
    board = board ;
    pos = Array.init 15 (fun i -> i) ;
    hole = 15 ;
  } in
  Array.iteri
    (fun i ((tile : GnoCanvas.group), _) -> tile#connect#event (item_event config i) ; ())
    config.board ;
  let button = GButton.button ~label:"Scramble" ~packing:vbox#add () in
  button#connect#clicked (scramble config)



let main_1 () =
  Random.self_init () ;
  let window = GWindow.window () in
  create_canvas_fifteen window ;
  window#connect#destroy ~callback:GMain.Main.quit ;
  window#show () ;
  GMain.Main.main ()

let _ = 
   main_1 ()


(* Local Variables: *)
(* compile-command: "ocamlopt -w s -i -I ../../src lablgtk.cmxa gtkInit.cmx lablgnomecanvas.cmxa canvas-fifteen.ml" *)
(* End: *)
