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
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
open Core

type t = Place of Placed_tile.t list | Pass | Exchange of Tile.t list

let apply_place_tiles game_state placed_tiles =
  let valid, error, _ = Validation.validate_move game_state placed_tiles in
  if not valid then (false, error, None, None, None)
  else
    let current_player =
      List.nth_exn game_state.players game_state.current_player
    in
    let board_with_tiles =
      List.fold placed_tiles ~init:game_state.board ~f:(fun b { tile; pos } ->
          Board.place_tile_on_board b pos tile)
    in
    let extracted =
      Word_extraction.extract_words board_with_tiles placed_tiles
    in
    let score = Scoring.calculate_move_score extracted placed_tiles in
    let used_tiles = List.map placed_tiles ~f:(fun pt -> pt.tile) in
    let rack_without =
      Player.remove_tiles_from_rack (Player.get_rack current_player) used_tiles
    in
    let filled_rack, remaining_bag =
      Tile_bag.fill_rack rack_without game_state.tile_bag
    in
    let updated_player =
      Player.add_points (Player.set_rack current_player filled_rack) score
    in
    let new_players =
      Game_state.update_players game_state.players game_state.current_player
        updated_player
    in
    let next_state =
      {
        game_state with
        board = board_with_tiles;
        players = new_players;
        tile_bag = remaining_bag;
        current_player =
          (game_state.current_player + 1) mod List.length game_state.players;
        turn = game_state.turn + 1;
        consecutive_passes = 0;
      }
    in
    let end_state =
      Game_state.check_game_end next_state updated_player
        (List.length placed_tiles)
    in
    let next_state =
      match end_state with
      | None -> next_state
      | Some e -> { next_state with end_state = Some e }
    in
    let word_strings = List.map extracted ~f:(fun w -> w.word) in
    (true, None, Some next_state, Some score, Some word_strings)

let apply_pass game_state =
  let consecutive = Game_state.(game_state.consecutive_passes + 1) in
  let next_state =
    {
      game_state with
      current_player =
        (game_state.current_player + 1) mod List.length game_state.players;
      turn = game_state.turn + 1;
      consecutive_passes = consecutive;
    }
  in
  let maybe_end : End_state.t option =
    if consecutive >= List.length game_state.players * 2 then
      let winner = Game_state.find_winner game_state.players in
      Some
        {
          winner;
          margin = Game_state.calculate_margin game_state.players winner;
          reason = Bag_exhausted_stall consecutive;
        }
    else None
  in
  let next_state =
    match maybe_end with
    | None -> next_state
    | Some e -> { next_state with end_state = Some e }
  in
  (true, None, Some next_state)

let apply_exchange game_state tiles =
  let open Game_state in
  let current = List.nth_exn game_state.players game_state.current_player in
  match
    Tile_bag.exchange_tiles (Player.get_rack current) game_state.tile_bag tiles
  with
  | None -> (false, Some "Not enough tiles in bag", None)
  | Some (new_rack, new_bag) ->
      let updated = Player.set_rack current new_rack in
      let players =
        update_players game_state.players game_state.current_player updated
      in
      let next_state =
        {
          game_state with
          players;
          tile_bag = new_bag;
          current_player =
            (game_state.current_player + 1) mod List.length game_state.players;
          turn = game_state.turn + 1;
          consecutive_passes = game_state.consecutive_passes + 1;
        }
      in
      (true, None, Some next_state)

let apply game_state move =
  match move with
  | Place tiles ->
      let success, error, new_state, score, words =
        apply_place_tiles game_state tiles
      in
      (success, error, new_state, score, words)
  | Pass ->
      let success, error, new_state = apply_pass game_state in
      (success, error, new_state, None, None)
  | Exchange tiles ->
      let success, error, new_state = apply_exchange game_state tiles in
      (success, error, new_state, None, None)