1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
open Core
type t = {
board : Board.t;
players : Player.t list;
current_player : int;
tile_bag : Tile_bag.t;
end_state : End_state.t option;
turn : int;
consecutive_passes : int;
}
[@@deriving to_yojson]
let create_initial_game ?rng player_names =
let rec build_players idx names bag acc =
match names with
| [] -> (List.rev acc, bag)
| name :: rest ->
let player = Player.make idx name in
let rack, remaining = Tile_bag.fill_rack (Player.get_rack player) bag in
let player = Player.set_rack player rack in
build_players (idx + 1) rest remaining (player :: acc)
in
let bag = Tile_bag.create_tile_bag ?rng () in
let players, bag = build_players 0 player_names bag [] in
{
board = Board.create_empty_board ();
players;
current_player = 0;
tile_bag = bag;
end_state = None;
turn = 1;
consecutive_passes = 0;
}
let find_winner players =
List.fold players ~init:(List.hd_exn players) ~f:(fun best p ->
if Player.get_score p > Player.get_score best then p else best)
let calculate_margin players winner =
let others =
List.filter players ~f:(fun p -> Player.get_id p <> Player.get_id winner)
in
let second =
List.fold others ~init:(List.hd_exn others) ~f:(fun best p ->
if Player.get_score p > Player.get_score best then p else best)
in
Player.get_score winner - Player.get_score second
let apply_final_scoring players player_went_out =
let total_remaining =
List.fold players ~init:0 ~f:(fun sum p ->
sum + Rack.get_total_points (Player.get_rack p))
in
players
|> List.map ~f:(fun p ->
let deducted =
Player.get_score p - Rack.get_total_points (Player.get_rack p)
in
if Player.get_id p = Player.get_id player_went_out then
Player.set_score p (deducted + total_remaining)
else Player.set_score p deducted)
let check_game_end state last_player tiles_placed =
if tiles_placed = 0 then None
else if
Rack.is_empty (Player.get_rack last_player)
&& Tile_bag.is_empty state.tile_bag
then
let adjusted_players = apply_final_scoring state.players last_player in
let winner = find_winner adjusted_players in
Some
End_state.
{
winner;
margin = calculate_margin adjusted_players winner;
reason = Rack_out last_player;
}
else None
let update_players players idx updated =
List.mapi players ~f:(fun i p -> if i = idx then updated else p)
let get_current_player state = List.nth_exn state.players state.current_player
let is_game_over state = Option.is_some state.end_state