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
open Core
open Placed_tile
open Game_state
let dictionary =
lazy
(let path = Sys.getenv "SCRABBLE_DICTIONARY" in
Dictionary.load ?path ())
let validate_words words =
let dict = Lazy.force dictionary in
List.for_all words ~f:(fun w -> Dictionary.is_valid ~dict w)
let is_connected_to_board board placed =
let touches_existing pos =
let offsets = [ (-1, 0); (1, 0); (0, -1); (0, 1) ] in
List.exists offsets ~f:(fun (dr, dc) ->
let neighbor = { row = pos.row + dr; col = pos.col + dc } in
Board.get_tile_at board neighbor |> Option.is_some)
in
List.exists placed ~f:(fun { pos; _ } -> touches_existing pos)
let validate_move game_state placed_tiles =
let board = game_state.board in
let error msg = (false, Some msg, []) in
if List.is_empty placed_tiles then error "Must place at least one tile"
else if not (Word_extraction.are_tiles_in_line placed_tiles) then
error "Tiles must be placed in a straight line"
else if
List.exists placed_tiles ~f:(fun { pos; _ } ->
Board.get_tile_at board pos |> Option.is_some)
then error "Position already occupied"
else if not (Word_extraction.are_tiles_contiguous board placed_tiles) then
error "Tiles must be placed contiguously"
else
let board_empty = Board.is_board_empty board in
let center = { row = 7; col = 7 } in
let covers_center =
List.exists placed_tiles ~f:(fun { pos; _ } ->
pos.row = center.row && pos.col = center.col)
in
let connected = is_connected_to_board board placed_tiles in
if board_empty && not covers_center then
error "First word must cover the center square"
else if (not board_empty) && not connected then
error "New tiles must connect to existing tiles"
else
let temp_board =
List.fold placed_tiles ~init:board ~f:(fun b { tile; pos } ->
Board.place_tile_on_board b pos tile)
in
let extracted = Word_extraction.extract_words temp_board placed_tiles in
if List.is_empty extracted then error "Must form at least one valid word"
else
let words = List.map extracted ~f:(fun w -> w.word) in
let dict = Lazy.force dictionary in
let invalid_words =
List.filter words ~f:(fun w -> not (Dictionary.is_valid ~dict w))
in
if not @@ List.is_empty invalid_words then
error
("Invalid word(s) formed: " ^ String.concat ~sep:", " invalid_words)
else
let current_player =
List.nth_exn game_state.players game_state.current_player
in
if
not
(Rack.has_required_tiles
(Player.get_rack current_player)
placed_tiles)
then error "Player does not have required tiles"
else (true, None, words)