(*
 * lablai - An ML Artificial Inteligence library
 * Copyright (C) 2006  Till Crueger
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *)

 
(* File $RCSfile$ *)
(* last edited by $Author: till_crueger $ *)
(* $Date: 2008-01-24 13:46:37 +0100 (Do, 24 Jan 2008) $, $Revision: 48 $ *)

module type Game =
   sig
      type gameField;;
      type move;;
      
      val get_moves : gameField -> move list;;
      val make_move : move -> gameField -> gameField;;
      val is_endpos : gameField -> bool;;
   end;;

module type M =
   sig
      type gameField;;
      type move;;
      
      val byDepth : (gameField -> float) -> int -> gameField -> move;;
      val byTime : (gameField -> float) -> float -> gameField -> move;;
      val byNodes : (gameField -> float) -> int -> gameField -> move;;
   end;;
   
module Make (GGame) =
   struct 
   
      type gameField = G.gameField;;
      type move = G.move;;
      
      exception Abort;;

      let negaMax estimator startPos check =
         let rec search poss mv_num depth alpha beta sgn =
            match poss with
               pos :: poss -> 
                  (* Generate this position, if not done already *)
                  let pos = Lazy.force pos in
                  let est =
                     if (not (check (depth+1))) || (G.is_endpos pos) then
                        sgn *. (estimator pos)
                     else
                        (* generate moves after this one *)
                        let moves = G.get_moves pos in
                        let next_poss = List.map (fun m -> lazy (G.make_move m pos)) moves in
                        (* Search on by taking this move*)
                        (* See negaMax explanation to understand the sign reversals *)
                        let (est,_) = search next_poss 0 (depth+1) (-.beta) (-.alpha) (-.sgn) in
                        -.est
                  in
                  (* see if we can improve and search other moves on this level *)
                  if est > alpha then
                     (* Alpha-Beta-Cut with fail-hard *)
                     if est > beta then
                        (* this move is so good, our opponent will never
                           let us do this *)

                           (beta,-2)
                     else
                        (* Yes we have improved, but not too far *)
                        search poss 0 depth est beta sgn
                  else
                     search poss (mv_num+1) depth alpha beta sgn
            |  [] -> (alpha,mv_num)
         in
         let moves = G.get_moves startPos in
         let poss = List.map (fun m -> lazy (G.make_move m startPos)) moves in
         (* we are only interested in the number of the chosen move *)
         let (_,mv_num) = search poss 0 0 neg_infinity infinity 1.0 in
         let rec get_mv mvs mv_num =
            match mvs with
               mv :: mvs -> if mv_num=0 then mv else get_mv mvs (mv_num-1)
            |  [] -> failwith "This should not happen"
         in
         get_mv (List.rev moves) mv_num
         ;;
      
      let byDepth estimator max_depth startPos =
         let check depth =
            max_depth >= depth
         in
         negaMax estimator startPos check
      ;;
      
      let byTime estimator max_time startPos =
         let startTime = Sys.time () in
               let rec loop n =
                  let check depth = 
               if depth > n then 
                  false
               else if (startTime -. Sys.time ()) > max_time then
                  (* Quick Abort in case time runs out *)
                  raise Abort
               else
                  true
            in
            let est = negaMax estimator startPos check in
            try loop (n+1)
            with Abort -> est
         in
         loop 0
         ;;
        
      let byNodes estimator max_Nodes startPos =
         let rec loop n =
            let check () =
               let count = ref 0 in
               (fun depth ->
                  count := !count +1; 
                  if depth > n then
                     false
                  else if !count > max_Nodes then
                     raise Abort
                  else
                     true
               )
            in
            let est = negaMax estimator startPos (check ()) in
            try loop (n+1)
            with Abort -> est
         in
         loop 0
         ;;

   end;;

(* 
 * $Log$
 * Revision 1.2  2008/01/20 15:43:17  till_crueger
 * - Rebuilt MiniMax search using the NegaMax variant with Alpha-Beta-Pruning
 *
 * Revision 1.1  2008/01/13 22:02:10  till_crueger
 *
 * - Added begin of framework for minimax search on 2-Player-Adversary Games
 *
 *)