342 lines
8.0 KiB
Plaintext
342 lines
8.0 KiB
Plaintext
|
|
|
|
(** {1 Two-Player Games
|
|
|
|
MPRI course 2-36-1 Proof of Programs - Project 2023-2024
|
|
|
|
*)
|
|
|
|
|
|
(** {2 Dummy symbols to fill holes in the remainder} *)
|
|
|
|
module DummySymbols
|
|
|
|
predicate __FORMULA_TO_BE_COMPLETED__
|
|
constant __TERM_TO_BE_COMPLETED__ : 'a
|
|
constant __VARIANT_TO_BE_COMPLETED__ : int
|
|
let function __EXPRESSION_TO_BE_COMPLETED__ () : 'a = absurd
|
|
let constant __CONDITION_TO_BE_COMPLETED__ : bool = False
|
|
let constant __CODE_TO_BE_COMPLETED__ : unit = ()
|
|
|
|
end
|
|
|
|
|
|
(** {2 The "Min" Game} *)
|
|
|
|
module MinGame
|
|
|
|
use export int.Int
|
|
use export array.Array
|
|
use export option.Option
|
|
use export DummySymbols
|
|
|
|
|
|
(** type for players *)
|
|
type player = Alice | Bob
|
|
|
|
(** type for configurations of the game *)
|
|
type config = {
|
|
stack : array int; (* stack of numbers, e.g. [ 20 ; 17 ; 24 ; -30 ; 27 ] *)
|
|
pos : int; (* position in the stack, i.e. index of the first number
|
|
to be picked *)
|
|
turn : player; (* player who will play next move *)
|
|
score_alice : int; (* current score of Alice *)
|
|
score_bob : int; (* current score of Bob *)
|
|
}
|
|
|
|
(** initial configuration from a given stack of numbers *)
|
|
function initial_config (stack: array int) : config =
|
|
{ stack = stack; pos = 0; turn = Alice; score_alice = 0; score_bob = 0 }
|
|
|
|
(** QUESTION 1 *)
|
|
|
|
(** invariant on configurations *)
|
|
predicate inv(c:config) =
|
|
__FORMULA_TO_BE_COMPLETED__
|
|
|
|
(** type for possible moves, here only two choices: pick one or pick
|
|
two numbers on top of the stack. *)
|
|
type move = PickOne | PickTwo
|
|
|
|
(** QUESTION 2 *)
|
|
|
|
(** set of possible moves in a given configuration. It is either empty,
|
|
represented as `None` or a pair of two moves represented as
|
|
`Some(m1,m2)` *)
|
|
let function legal_moves (c:config) : option (move, move) =
|
|
if __CONDITION_TO_BE_COMPLETED__ then None else __EXPRESSION_TO_BE_COMPLETED__ ()
|
|
|
|
(** QUESTION 3 *)
|
|
|
|
(** `do_move c m` returns the new configuration obtained from
|
|
configuration `c` after playing move `m` *)
|
|
let function do_move (c:config) (m:move) : config
|
|
requires { inv c }
|
|
requires { __FORMULA_TO_BE_COMPLETED__ }
|
|
ensures { inv result }
|
|
=
|
|
match m, c.turn with
|
|
| PickOne, Alice ->
|
|
let x = c.stack[c.pos] in
|
|
{ c with turn = Bob; pos = c.pos + 1; score_alice = c.score_alice + x }
|
|
| PickOne, Bob ->
|
|
let x = c.stack[c.pos] in
|
|
__EXPRESSION_TO_BE_COMPLETED__ ()
|
|
| PickTwo, Alice ->
|
|
__EXPRESSION_TO_BE_COMPLETED__ ()
|
|
| PickTwo,Bob ->
|
|
__EXPRESSION_TO_BE_COMPLETED__ ()
|
|
end
|
|
|
|
(** `config_value c` is an heuristic evaluation of the config `c`,
|
|
Alice wants to minimize it whereas Bob wants to maximize it *)
|
|
let function config_value (c:config) : int =
|
|
c.score_alice - c.score_bob
|
|
|
|
use array.Init
|
|
|
|
(* QUESTION 4 *)
|
|
let config0 () : config
|
|
ensures { result.stack.length = 5 }
|
|
ensures { result.stack[0] = 20 }
|
|
ensures { result.stack[1] = 17 }
|
|
ensures { result.stack[2] = 24 }
|
|
ensures { result.stack[3] = -30 }
|
|
ensures { result.stack[4] = 27 }
|
|
ensures { result.pos = 0 }
|
|
ensures { result.turn = Alice }
|
|
ensures { result.score_alice = 0 }
|
|
ensures { result.score_bob = 0 }
|
|
ensures { inv result }
|
|
=
|
|
let s = init 5 [| 20; 17; 24; -30; 27 |] in
|
|
{ stack = s;
|
|
pos = 0;
|
|
turn = Alice;
|
|
score_alice = 0;
|
|
score_bob = 0;
|
|
}
|
|
|
|
(* You can run the test above using:
|
|
why3 execute min_game.mlw --use MinGame "config0 ()"
|
|
*)
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
(** {2 The MiniMax value of a tree of moves} *)
|
|
|
|
module MiniMax
|
|
|
|
use MinGame
|
|
use int.MinMax
|
|
|
|
(* QUESTION 5 *)
|
|
|
|
(** [minimax c d] returns the minimax evaluation of config [p]
|
|
at depth [d]. *)
|
|
let rec function minimax (c:config) (d:int) : int
|
|
requires { inv c }
|
|
variant { __VARIANT_TO_BE_COMPLETED__ }
|
|
= if d <= 0 then __EXPRESSION_TO_BE_COMPLETED__ () else
|
|
match legal_moves c with
|
|
| None -> __EXPRESSION_TO_BE_COMPLETED__ ()
|
|
| Some(m1,m2) ->
|
|
match c.turn with
|
|
| Alice -> (* Alice wants to minimize the config value *)
|
|
min (minimax (do_move c m1) (d-1)) (minimax (do_move c m2) (d-1))
|
|
| Bob -> (* Bob wants to maximize the config value *)
|
|
__EXPRESSION_TO_BE_COMPLETED__ ()
|
|
end
|
|
end
|
|
|
|
(* QUESTION 6 *)
|
|
|
|
(** Testing `minimax` on the sample configuration by concrete execution *)
|
|
let test0 () =
|
|
let c0 = config0 () in
|
|
(minimax c0 0,
|
|
minimax c0 1,
|
|
minimax c0 2,
|
|
minimax c0 3,
|
|
minimax c0 4,
|
|
minimax c0 5)
|
|
|
|
(* You can run the tests above using:
|
|
why3 execute min_game.mlw --use MiniMax "test0 ()"
|
|
*)
|
|
|
|
(** Testing `minimax` on the sample configuration with provers *)
|
|
let test_minimax () =
|
|
let c0 = config0 () in
|
|
assert { minimax c0 0 = 0 };
|
|
assert { minimax c0 1 = 20 };
|
|
assert { minimax c0 2 = 3 };
|
|
assert { minimax c0 3 = -3 }
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
(** {2 The alpha-beta algorithm} *)
|
|
|
|
module AlphaBeta
|
|
|
|
use int.MinMax
|
|
use MinGame
|
|
use MiniMax
|
|
|
|
(** `lt_alpha_n alpha n` means `alpha < n` *)
|
|
let predicate lt_alpha_n (alpha: option int) (n:int) =
|
|
match alpha with
|
|
| None -> true
|
|
| Some a -> a < n
|
|
end
|
|
|
|
(** `lt_n_beta n beta` means `n < beta` *)
|
|
let predicate lt_n_beta (n:int) (beta: option int) =
|
|
match beta with
|
|
| None -> true
|
|
| Some b -> n < b
|
|
end
|
|
|
|
(** `max_alpha alpha n` is the maximum of `alpha` and `n` *)
|
|
let max_alpha (alpha: option int) (n:int) : option int =
|
|
match alpha with
|
|
| None -> Some n
|
|
| Some a -> Some (max a n)
|
|
end
|
|
|
|
(** `min_beta n beta` is the minimum of `n` and `beta` *)
|
|
let min_beta (n:int) (beta: option int) : option int =
|
|
match beta with
|
|
| None -> Some n
|
|
| Some b -> Some (min n b)
|
|
end
|
|
|
|
(* QUESTIONS 7 and 8 *)
|
|
|
|
let rec alpha_beta (alpha beta:option int) (c:config) (d:int) : int
|
|
requires { inv c }
|
|
requires { __FORMULA_TO_BE_COMPLETED__ }
|
|
variant { __VARIANT_TO_BE_COMPLETED__ }
|
|
ensures { __FORMULA_TO_BE_COMPLETED__ }
|
|
= __EXPRESSION_TO_BE_COMPLETED__ ()
|
|
|
|
(* QUESTIONS 9 and 10 *)
|
|
|
|
exception GameEnded
|
|
|
|
let best_move (c:config) (d:int) : move
|
|
requires { inv c }
|
|
ensures { __FORMULA_TO_BE_COMPLETED__ }
|
|
raises { GameEnded -> __FORMULA_TO_BE_COMPLETED__ }
|
|
= raise GameEnded (* TO BE COMPLETED *)
|
|
|
|
let test_alphabeta () =
|
|
let c0 = config0 () in
|
|
try
|
|
(best_move c0 1,
|
|
best_move c0 2,
|
|
best_move c0 3,
|
|
best_move c0 4)
|
|
with GameEnded -> absurd
|
|
end
|
|
|
|
(* You can run the test above using:
|
|
why3 execute num_game.mlw --use AlphaBeta "test_alphabeta ()"
|
|
*)
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(** {2 An efficient algorithm for the Min game} *)
|
|
|
|
(** Best move found by dynamic programming *)
|
|
|
|
|
|
|
|
module Dynamic
|
|
|
|
|
|
|
|
use int.Int
|
|
use int.MinMax
|
|
use array.Array
|
|
|
|
use MinGame
|
|
use MiniMax
|
|
|
|
|
|
(** QUESTION 13 *)
|
|
|
|
(* Lemma relating the minimax value of a configuration to the minimax
|
|
value of the same configuration with score set to zero. *)
|
|
|
|
let rec lemma minimax_independence (c:config) (d:int) : unit
|
|
requires { inv c }
|
|
requires { __FORMULA_TO_BE_COMPLETED__ }
|
|
variant { __VARIANT_TO_BE_COMPLETED__ }
|
|
ensures { minimax c d =
|
|
minimax { c with score_alice = 0 ; score_bob = 0 } d +
|
|
__TERM_TO_BE_COMPLETED__ }
|
|
= __EXPRESSION_TO_BE_COMPLETED__ ()
|
|
|
|
|
|
|
|
|
|
(* QUESTION 16 *)
|
|
|
|
predicate dynamic_inv_at_index (stack:array int) (i:int) (a:array int) =
|
|
forall t:player, sa sb : int, d:int.
|
|
let c =
|
|
{ stack = stack; pos = i; turn = t; score_alice = sa; score_bob = sb }
|
|
in
|
|
(* relating `minimax c d` to a[i] *)
|
|
__FORMULA_TO_BE_COMPLETED__
|
|
|
|
(* QUESTION 15 and 17 *)
|
|
|
|
let dynamic (stack : array int) : int
|
|
requires { __FORMULA_TO_BE_COMPLETED__ }
|
|
ensures { forall d:int. __FORMULA_TO_BE_COMPLETED__ }
|
|
(* relating the result to minimax *)
|
|
=
|
|
let l = stack.length in
|
|
let a = Array.make (l+1) 0 in
|
|
for n = l-2 downto 0 do
|
|
invariant { __FORMULA_TO_BE_COMPLETED__ }
|
|
a[n] <- __EXPRESSION_TO_BE_COMPLETED__ ();
|
|
done;
|
|
a[0]
|
|
|
|
let test () =
|
|
let c0 = config0 () in
|
|
dynamic c0.stack
|
|
(* You can run the test above using:
|
|
why3 execute min_game.mlw --use Dynamic "test ()"
|
|
*)
|
|
|
|
|
|
|
|
(* QUESTION 18 *)
|
|
|
|
let dynamic_opt (stack : array int) : int
|
|
requires { __FORMULA_TO_BE_COMPLETED__ }
|
|
ensures { __FORMULA_TO_BE_COMPLETED__ }
|
|
= __EXPRESSION_TO_BE_COMPLETED__ ()
|
|
|
|
|
|
end
|