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
open Core

type t = Tile.t list [@@deriving to_yojson]
type distribution = { letter : char option; points : int; count : int }

let empty = []
let is_empty = List.is_empty
let length = List.length
let of_tile_list tiles = tiles
let to_tile_list bag = bag

let tile_distribution =
  [
    { letter = Some 'A'; points = 1; count = 9 };
    { letter = Some 'B'; points = 3; count = 2 };
    { letter = Some 'C'; points = 3; count = 2 };
    { letter = Some 'D'; points = 2; count = 4 };
    { letter = Some 'E'; points = 1; count = 12 };
    { letter = Some 'F'; points = 4; count = 2 };
    { letter = Some 'G'; points = 2; count = 3 };
    { letter = Some 'H'; points = 4; count = 2 };
    { letter = Some 'I'; points = 1; count = 9 };
    { letter = Some 'J'; points = 8; count = 1 };
    { letter = Some 'K'; points = 5; count = 1 };
    { letter = Some 'L'; points = 1; count = 4 };
    { letter = Some 'M'; points = 3; count = 2 };
    { letter = Some 'N'; points = 1; count = 6 };
    { letter = Some 'O'; points = 1; count = 8 };
    { letter = Some 'P'; points = 3; count = 2 };
    { letter = Some 'Q'; points = 10; count = 1 };
    { letter = Some 'R'; points = 1; count = 6 };
    { letter = Some 'S'; points = 1; count = 4 };
    { letter = Some 'T'; points = 1; count = 6 };
    { letter = Some 'U'; points = 1; count = 4 };
    { letter = Some 'V'; points = 4; count = 2 };
    { letter = Some 'W'; points = 4; count = 2 };
    { letter = Some 'X'; points = 8; count = 1 };
    { letter = Some 'Y'; points = 4; count = 2 };
    { letter = Some 'Z'; points = 10; count = 1 };
    { letter = None; points = 0; count = 2 };
  ]

let shuffle ?(rng = Random.State.default) arr =
  let a = Array.of_list arr in
  for i = Array.length a - 1 downto 1 do
    let j = Random.State.int rng (i + 1) in
    Array.swap a i j
  done;
  Array.to_list a

let create_tile_bag ?rng () =
  tile_distribution
  |> List.concat_map ~f:(fun { letter; points; count } ->
         List.init count ~f:(fun _ -> Tile.make letter points))
  |> shuffle ?rng

let draw_tiles bag count =
  let rec take n acc rest =
    if n = 0 || List.is_empty rest then (List.rev acc, rest)
    else take (n - 1) (List.hd_exn rest :: acc) (List.tl_exn rest)
  in
  take (Int.min count (List.length bag)) [] bag

let fill_rack rack bag =
  let needed = 7 - Rack.length rack in
  if needed <= 0 then (rack, bag)
  else
    let drawn, remaining = draw_tiles bag needed in
    (Rack.(rack @ drawn), remaining)

let exchange_tiles (rack : Rack.t) bag tiles_to_exchange =
  if List.length bag < 7 then None
  else
    match Rack.remove_all rack tiles_to_exchange with
    | None -> None
    | Some reduced_rack ->
        let drawn, remaining_bag =
          draw_tiles bag (List.length tiles_to_exchange)
        in
        let final_bag = shuffle (remaining_bag @ tiles_to_exchange) in
        Some (Rack.(reduced_rack @ drawn), final_bag)

let tile_count bag = List.length bag