/
gui.ml
1519 lines (1362 loc) · 60.8 KB
/
gui.ml
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
open Lwt
open LTerm_widget
open LTerm_geom
open LTerm_key
open LTerm_event
type gui_event_t =
| Init
| JsonFnameTurnOn
| JsonFname of string
| PSelectTurnOn
| PSelect of (string * State.controller) list
| Comm of Command.command
(* [trade_mon_prop_t] represents the money and the properties in the offer. *)
type trade_mon_prop_t = {
mon : int;
prop_lst : string list;
}
(** [text_input]'s objects can be used to make editor widgets inside
* [LTerm_widget.modal_frame]. These editors will be single-line if the
* [LTerm_widget.box] containing them doesn't add the frame embedding the editor
* with free expansion. *)
class text_input = object(self)
inherit LTerm_edit.edit () as super
(** [size_request] is the dimensions of the editor window. *)
method size_request = {rows = 1; cols = 10}
end
(* [gui_data_t] is used to store information in the GUI instance. *)
type gui_data_t = {
(** [push_modals] is a mapping from the modal's description to a function
* that pushes the modal. [push_modals] will usually contain unchanging
* modal frames that can be pushed by [game_gui]'s methods on to the screen.
* For example, a modal frame for asking the user to input json file name
* can be pushed on to the screen. *)
mutable push_modals : (string * (unit -> unit)) list;
(** [event_buffer] is a list of unprocessed events from the GUI. *)
mutable event_buffer : gui_event_t list;
(** [s] is the game's state as referenced by the GUI instance. *)
mutable s : State.state option;
(** [player_controllers] is the array containing the Player's controllers that
* are used to initialize the game state. Its usage should become obsolete once
* the game state has been initialized. [player_controllers.(i)] is the
* controller for the ith player in the game. Used by [pselect_modal].
* restriction for our game: [Array.length player_controllers = 8]. *)
mutable player_controllers : State.controller option array;
(** [player_pieces] is the array containing the possible names of the players.
* Similar to [player_controllers], this array becomes obsolete when the game
* is initialized. [player_pieces.(i) is the name of ith player. Used by
* [pselect_modal].
* restriction for our game: [Array.length player_pieces = 8]. *)
player_pieces : string array;
(** [turns] is the number of turns taken by all players in the game. *)
mutable turns : int;
(** [history] is the history of commands directed by players in the game. *)
mutable history : string list;
}
type char_or_special =
| Ch of char
| Special of LTerm_key.code
type key_binding_t = {
key : char_or_special;
desc : string;
act : unit -> unit;
}
(** [string_of_preroll_comm pid c] is [Some s], where [s] is a rough description
* of [Command.Preroll_command c] in English, annotated with player's name [pid]. *)
let string_of_preroll_comm pid = function
| Command.Roll -> Some (pid^" rolled.")
| Command.SellProp s -> Some (pid^" attempted to sell "^s^".")
| Command.SellAndRoll s -> Some (pid^" attempted to sell "^s^" and rolled.")
| Command.Develop s -> Some (pid^" attempted to develop "^s^".")
| Command.DevelopAndRoll s -> Some (pid^" attempted to develop "^s^" and rolled.")
| Command.Trade (otherp, offer, return) ->
Some (pid^" wants to trade with "^otherp^".")
(** [string_of_injail_comm pid c] is [Some s], where [s] is a rough description
* of [Command.Preroll_in_jail_command c] in English, annotated with player's
* name [pid]. *)
let string_of_injail_comm pid = function
| Command.Roll_injail -> Some (pid^" tried rolling while in jail.")
| Command.UseCard -> Some (pid^" tried using their Get-Out-Of-Jail card.")
| Command.PayJailFee -> Some (pid^" tried paying to get out of jail.")
(** [string_of_trade_offer pid c] is [Some s], where [s] is a rough description
* of [Command.Trade_offer_command c] in English, annotated with player's name
* [pid]. *)
let string_of_trade_offer pid = function
| Command.AcceptTrade -> Some (pid^" accepted the trade.")
| Command.DeclineTrade -> Some (pid^" declined the trade.")
(** [string_of_unowned_prop pid c] is [Some s], where [s] is a rough description
* of [Command.Land_unowned_prop_command c] in English, annotated with player's
* name [pid]. *)
let string_of_unowned_prop pid = function
| Command.Buy -> Some (pid^" tried buying a property.")
| Command.NotBuy ->
Some (pid^" did not buy the property they landed on.")
(** [string_of_other_prop pid c] is [Some s], where [s] is a rough description
* of [Command.Land_other_prop_command c] in English, annotated with player's
* name [pid]. *)
let string_of_other_prop pid = function
| Command.PayRent -> Some (pid^" tried paying rent.")
| Command.Sell_PayRent s -> Some (pid^" tried selling "^s^" to pay rent.")
| Command.BankruptRent ->
Some (pid^" declared bankruptcy while paying rent.")
(** [string_of_transaction pid c] is [Some s], where [s] is a rough description
* of [Command.Transaction_command c] in English, annotated with player's name
* [pid]. *)
let string_of_transaction pid = function
| Command.PayTransaction -> Some (pid^" tried paying the bank.")
| Command.Sell_PayTransaction s ->
Some (pid^" tried selling "^s^" to pay the bank.")
| Command.BankruptTransaction ->
Some (pid^" declared bankruptcy while paying the bank.")
(** [string_of_command pid c] is [Some s], where [s] is a rough description
* of [c] in English, annotated with player's name [pid]. If
* [c = Command.End_turn_command], then [None] is returned. *)
let string_of_command pid = function
| Command.Preroll_command sub_c -> string_of_preroll_comm pid sub_c
| Command.Preroll_injail_command sub_c -> string_of_injail_comm pid sub_c
| Command.Trade_offer_command sub_c -> string_of_trade_offer pid sub_c
| Command.Land_unowned_prop_command sub_c -> string_of_unowned_prop pid sub_c
| Command.Land_other_prop_command sub_c -> string_of_other_prop pid sub_c
| Command.Transaction_command sub_c -> string_of_transaction pid sub_c
| Command.End_turn_command -> None
(** [key_assoc_opt k key_bindings] is the leftmost [(desc, act)] mapped to key
* [k] in [key_bindings]. *)
let rec key_assoc_opt k = function
| [] -> None
| {key; desc; act}::t ->
if key = k then Some (desc, act) else key_assoc_opt k t
class key_binding_functions = object(self)
val mutable key_bindings = []
method bind_keys lst =
key_bindings <- key_bindings @ lst
method bind_scroller (adj : scrollable) =
self#bind_keys [
{key=Special Down; desc="Scroll Down"; act=(fun () ->
adj#set_offset adj#incr)};
{key=Special Up; desc="Scroll Up"; act=(fun () ->
adj#set_offset adj#decr)};
]
method add_action_labels (box : LTerm_widget.box) =
box#add ~expand:false (new hline);
box#add ~expand:false (new hline);
List.iter (fun {key; desc; _} ->
match key with
| Ch c ->
let l = new label ("["^(String.make 1 c)^"] "^desc) in
l#set_alignment H_align_left;
box#add ~expand:false l
| _ -> ())
key_bindings
method add_false_action_labels (box : LTerm_widget.box) lst =
box#add ~expand:false (new hline);
box#add ~expand:false (new hline);
List.iter (fun (key, desc) ->
let l = new label ("["^key^"] "^desc) in
l#set_alignment H_align_left;
box#add ~expand:false l)
lst
(** [handle_key_input e] handles event [e] using the key bindings
* of this instance. Returns [true] if [e] matches a key binding's [key]
* in the key bindings, otherwise returns [false]. If [e] matches [key],
* then the corresponding [act] function associated with [key] is applied. *)
method private handle_key_input = function
| LTerm_event.Key {code = LTerm_key.Char c} ->
begin
let open CamomileLibrary.UChar in
match key_assoc_opt (Ch (char_of c)) key_bindings with
| Some (_, f) -> f (); true
| None -> false
end
| LTerm_event.Key {code = c} ->
begin
match key_assoc_opt (Special c) key_bindings with
| Some (_, f) -> f (); true
| None -> false
end
| _ -> false
end
class game_frame = object(self)
inherit LTerm_widget.frame
inherit key_binding_functions
initializer self#on_event self#handle_key_input
end
class game_modal_frame = object(self)
inherit LTerm_widget.modal_frame
inherit key_binding_functions
initializer self#on_event self#handle_key_input
end
(** [scrollable_lst scroll lst] is a vertically-scrollable widget
* listing the elements of [lst]. *)
class scrollable_lst (scroll : scrollable) lst = object(self)
(* Reference: Inspiration from
https://github.com/lambda-term/examples/scroll.ml. *)
inherit LTerm_widget.t "scrollable list"
initializer scroll#set_range (List.length lst)
(** [draw ctx _focused] draws the elements of [lst]. *)
method draw ctx _ =
(* this function tries to draw List.nth (0..end) inclusive,
which throws a failure as List.nth refers to one past the end
of the lst. Safeguard the lst by checking what to draw *)
let {rows; _} = LTerm_draw.size ctx in
for row = 0 to rows - 1 do
if (row + scroll#offset) < List.length lst
then LTerm_draw.draw_string ctx row 0
(List.nth lst (row + scroll#offset)) else ()
done;
end
class game_gui push_layer pop_layer exit' = object(self)
inherit game_frame as super
val data = {
push_modals = [];
event_buffer = [];
s = None;
player_controllers = Array.make 8 None;
player_pieces = [|" battleship "; " racecar ";
" top hat "; " thimble ";
" wheelbarrow "; " horse ";
" shoe "; " money bag "|];
turns = 0;
history = [];
}
(** [reset_data ()] resets the event buffer, state, # of turns, player
* controllers, and history in the instance's data fields. *)
method private reset_data () =
data.event_buffer <- [];
data.s <- None;
Array.iteri (fun i _ ->
data.player_controllers.(i) <- None)
data.player_controllers;
data.turns <- 0;
data.history <- []
(** [init_key_binding] is the key-binding to activate the re-initialize
* the game. *)
method private init_key_binding = {
key=Ch 'H'; desc="Go Home"; act=(fun () ->
pop_layer ();
self#reset_data ();
data.event_buffer <- Init::data.event_buffer)
}
method update_state s_opt =
data.s <- s_opt
method update_history pid c =
match string_of_command pid c with
| None -> ()
| Some mess -> data.history <- mess::data.history
method get_events =
let e = data.event_buffer in data.event_buffer <- []; e
(** [exit_term ()] disables mouse inputs (whether enabled or not)
* and exits the terminal. *)
method private exit_term () =
Lazy.force LTerm.stdout
>>= (fun term -> LTerm.disable_mouse term) |> ignore; exit' ()
method setup =
let get_json_fname_modal = self#one_line_textbox "Enter the Game's File Name"
~enter_callback:(fun str -> data.event_buffer <- JsonFname str::data.event_buffer) in
let modals_for_pushing = [
"json_fname", push_layer get_json_fname_modal;
"init", push_layer (self#create_init_modal ());
"pselect", push_layer (self#create_pselect_modal);
] in
data.push_modals <- modals_for_pushing;
self#bind_keys [
{key=Ch 'I'; desc="Initialize Game"; act=(fun () -> data.event_buffer <- Init::data.event_buffer)};
{key=Ch 'H'; desc="Help"; act=(fun () -> self#activate_help_modal (fun () ->
pop_layer ();
self#reset_data ();
data.event_buffer <- Init::data.event_buffer) ())};
{key=Ch 'Q'; desc="Quit"; act=(fun () -> self#exit_term ())};
];
(** [create_init_modal ()] creates the init modal frame that shows up during
* game initialization. *)
method private create_init_modal () =
let init_modal = new game_modal_frame in
let init_box = new vbox in
init_modal#set init_box;
self#add_heading init_box "Home";
let init_mess = "Welcome to Monopoly!\n\n\
Throughout the game, you can take actions on pop-ups by pressing\n\
the corresponding key on your keyboard.\n\n" in
init_box#add (new label init_mess);
init_modal#bind_keys [
{key=Ch 'N'; desc="New Game"; act=(fun () ->
pop_layer (); self#activate_json_fname_modal ())};
{key=Ch 'R'; desc="Rules"; act=(fun () ->
pop_layer (); self#activate_help_modal (fun () ->
pop_layer ();
self#reset_data ();
data.event_buffer <- Init::data.event_buffer) ())};
{key=Ch 'Q'; desc="Quit"; act=(fun () -> self#exit_term ())};
];
init_modal#add_action_labels init_box;
init_modal
(** [create_pselect_modal] creates a view that will prompt the use to
* assign controllers to the game pieces before the game starts. The
* options are:
* 1) Reset : Reset the player-controller bindings
* 2) Submit : Submit the player-controller bindings
* 3) Go Home : Return to the home screen *)
method private create_pselect_modal =
let vbox = new vbox in
let frame = new game_modal_frame in
frame#set vbox;
self#add_heading vbox "Select a Game Piece and Assign a Controller to it";
let key_bindings_activate_cselect =
Array.mapi (fun i pid -> {
(* i+1 is mapped to cselect i *)
key=Ch (char_of_int (49 + i)) (* 49 is 1 in ASCII *);
desc="Assign controller to "^(String.trim pid);
act=(fun () -> pop_layer ();
self#activate_cselect_modal i ();)
}) data.player_pieces
|> Array.fold_left (fun a x -> x::a) [] |> List.rev in
frame#bind_keys key_bindings_activate_cselect;
frame#add_action_labels vbox;
vbox#add ~expand:false (new hline);
(** [create_row_elts row_num col_cnt gui_element_type] creates a row
* of [col_cnt] radiobuttons corresponding to different elements as
* specified by [gui_element_type]:
* It returns the created [hbox] which represents the horizontal row.
* requires:
* - [row_num, col_cnt] such that [row_num * col_cnt + i], where
* [0 <= i < col_cnt], is a valid index of [radio_grp, ed_frames_grp]. *)
let create_row_elts row_num col_cnt gui_element_type =
let hbox = new hbox in
for col = 0 to (col_cnt - 1) do
begin
match gui_element_type with
| `Piece -> hbox#add (new label
(data.player_pieces.(row_num * col_cnt + col)))
| `Text ->
let controller_to_string = function
| None -> " "
| Some cnt -> State.string_of_controller cnt
in hbox#add (new label (
controller_to_string data.player_controllers.(row_num * col_cnt + col)))
end;
if col <> (col_cnt - 1) then hbox#add ~expand:false (new vline) else ()
done; hbox in
(* create rows of elements *)
for row = 0 to 1 do
create_row_elts row 4 `Piece |> vbox#add; (* player labels *)
create_row_elts row 4 `Text |> vbox#add; (* controller labels *)
vbox#add ~expand:false (new hline);
done;
vbox#add (new t "glue"); (* glues the vbox together *)
(* add more key bindings *)
frame#bind_keys [
{key=Ch 'S'; desc="Submit"; act=(fun () -> pop_layer ();
(* make a list out of player_controllers and player_pieces *)
let p_cnts_lst =
(* first create an array of players' names and controllers *)
let p_cnts_arr =
Array.map2 (fun p cnt_opt ->
(String.trim p, cnt_opt)) data.player_pieces data.player_controllers in
(* then create a list out of the array, ignoring controllers that were
chosen *)
Array.fold_left (fun a (p, cnt_opt) ->
match cnt_opt with
| None -> a
| Some cnt -> (p, cnt)::a) [] p_cnts_arr |> List.rev in
data.event_buffer <- PSelect p_cnts_lst::data.event_buffer)};
{key=Ch 'R'; desc="Reset"; act=(fun () -> pop_layer ();
for i = 0 to 7 do
data.player_controllers.(i) <- None
done;
push_layer self#create_pselect_modal ())};
self#init_key_binding;
];
frame#add_false_action_labels vbox [
"S", "Submit";
"R", "Reset";
"H", "Go Home";
];
frame
(** [create_cselect_modal pnum] creates a view that will prompt the user
* to assign a controller to player with id [pnum]. The options are :
* 1) Human Player : Human/GUI
* 2) AI1 : AI2
* 3) AI2 : AI2
* 4) AI3 : AI3
* 5) None : No controller *)
method private create_cselect_modal pnum =
let cselect_modal = new game_modal_frame in
let vbox = new vbox in
cselect_modal#set vbox;
let text =
(* show pnum + 1 because humans like indices from 1 *)
"Assign a Controller type to Player " ^ (string_of_int (pnum+1)) in
self#add_heading vbox text;
cselect_modal#bind_keys [
{key=Ch 'h'; desc="Human Player"; act=(fun () -> pop_layer ();
data.player_controllers.(pnum) <- (Some State.GUI);
push_layer self#create_pselect_modal ())};
{key=Ch '1'; desc="AI1"; act=(fun () -> pop_layer ();
data.player_controllers.(pnum) <- (Some State.AI1);
push_layer self#create_pselect_modal ())};
{key=Ch '2'; desc="AI2"; act=(fun () -> pop_layer ();
data.player_controllers.(pnum) <- (Some State.AI2);
push_layer self#create_pselect_modal ())};
{key=Ch '3'; desc="AI3"; act=(fun () -> pop_layer ();
data.player_controllers.(pnum) <- (Some State.AI3);
push_layer self#create_pselect_modal ())};
{key=Ch 'n'; desc="None"; act=(fun () -> pop_layer ();
data.player_controllers.(pnum) <- None;
push_layer self#create_pselect_modal ())};
];
cselect_modal#add_action_labels vbox;
cselect_modal
(** [create_pdisplay_modal st] creates the modal frame that displays
* the players' names and controllers that were used to initialize
* the game. *)
method private create_pdisplay_modal st =
let pdisplay_modal = new game_modal_frame in
let pdisplay_box = new vbox in
pdisplay_modal#set pdisplay_box;
self#add_heading pdisplay_box "Players in the Game";
State.get_all_players st
|> List.iteri (fun i (pid, cnt) ->
let p_mess = pid^" with a "^(State.string_of_controller cnt)^" controller." in
(* humans like indices starting from 1 *)
let l = new label ((string_of_int (i+1))^": "^p_mess) in
l#set_alignment H_align_left;
pdisplay_box#add l);
pdisplay_box#add (new label "");
pdisplay_modal#bind_keys [
{key=Ch 'S'; desc="Start Game"; act=(fun () ->
(* activate_game_modal requires that a modal layer is present previously,
let pdisplay be that layer. I.e., don't pop it here. *)
self#activate_game_modal ();)};
{key=Ch 'P'; desc="Select Players Again"; act=(fun () -> pop_layer ();
self#reset_data ();
data.event_buffer <- PSelectTurnOn::data.event_buffer)};
self#init_key_binding;
];
pdisplay_modal#add_action_labels pdisplay_box;
pdisplay_modal
method activate_game_modal () =
pop_layer (); (* pop the layer that was previously pushed*)
match data.s with
| None -> self#activate_error_modal
~disp:"State cannot be accessed" "" ()
| Some st ->
begin
if List.length (State.get_all_players st) = 1 then
let (pid, cnt) = State.get_all_players st |> List.hd in
self#activate_game_over_modal pid st ();
else begin
data.turns <- data.turns + 1;
match State.get_current_player st with
| pid, State.GUI ->
self#activate_human_modal pid st ();
| pid, State.AI1 ->
begin
let (c, s) = Controllers.AI1.eval st in
self#activate_ai_modal pid State.AI1 s ();
data.event_buffer <- Comm c::data.event_buffer;
end
| pid, State.AI2 ->
begin
let (c, s) = Controllers.AI2.eval st in
self#activate_ai_modal pid State.AI2 s ();
data.event_buffer <- Comm c::data.event_buffer;
end
| pid, State.AI3 ->
begin
let (c, s) = Controllers.AI3.eval st in
self#activate_ai_modal pid State.AI3 s ();
data.event_buffer <- Comm c::data.event_buffer;
end
end
end
(** [activate_human_modal pid st ()] activates the modal frame that waits for
* user input.
* requires:
* - [List.hd (State.get_all_players st) = (pid, GUI). *)
method private activate_human_modal pid st () =
let human_modal = new game_modal_frame in
let human_box = new vbox in
human_modal#set human_box;
let info_box = new hbox in
info_box#add (self#human_info_left_box pid st);
info_box#add ~expand:false (new vline);
info_box#add (self#human_info_right_box human_modal pid st);
human_box#add info_box;
human_box#add ~expand:false (new hline);
let comm_box = new hbox in
comm_box#add (self#hist_preview_box);
comm_box#add ~expand:false (new vline);
(* bind keys here *)
let bind_keys_simple_turntype lst =
let keys_to_bind =
List.map (fun (k, d, comm) ->
{key=k; desc=d; act=(fun () ->
data.event_buffer <- Comm comm::data.event_buffer;)}) lst in
(* add these keys to the view *)
comm_box#add (self#human_comm_box
(List.map (fun (k, d, _) -> (k, d)) lst));
(* associate key bindings *)
human_modal#bind_keys keys_to_bind in
let bind_keys_complex_turntype lst =
let keys_to_bind =
List.map (fun (k, d, thunk) ->
{key=k; desc=d; act=thunk}) lst in
(* add these keys to the view *)
comm_box#add (self#human_comm_box
(List.map (fun (k, d, _) -> (k, d)) lst));
(* associate key bindings *)
human_modal#bind_keys keys_to_bind in
begin
let open Command in
match State.get_turntype st with
| State.Preroll ->
begin
bind_keys_complex_turntype [
Ch 'r', "Roll", (fun () ->
data.event_buffer <- Comm (Preroll_command Roll)::data.event_buffer);
Ch 's', "Sell Property", (fun () -> pop_layer ();
self#activate_select_from_list pid st (State.get_player_properties pid st)
`Prop (fun str ->
data.event_buffer <- Comm (
Preroll_command (SellProp str))::data.event_buffer) None ()
);
Ch 'd', "Develop Property", (fun () -> pop_layer ();
self#activate_select_from_list pid st (State.get_player_properties pid st)
`Prop (fun str ->
data.event_buffer <- Comm (
Preroll_command (Develop str))::data.event_buffer) None ()
);
Ch 't', "Start a Trade", (fun () -> pop_layer ();
self#activate_make_trade pid st None
{mon=0; prop_lst=[]} {mon=0; prop_lst=[]} ()
);
]
end
| State.Preroll_injail ->
begin
let jail_fee = State.(get_jail_fee st |> string_of_int) in
bind_keys_simple_turntype [
Ch 'u', "Use Get-Out-of-Jail Card", Preroll_injail_command UseCard;
Ch 'p', "Pay Fee of $"^jail_fee^" to Get Out of Jail", Preroll_injail_command PayJailFee;
Ch 'r', "Roll", Preroll_injail_command Roll_injail;
]
end
| State.Trade_offer ->
begin
bind_keys_complex_turntype [
Ch 'v', "View Trade Details", (fun () -> pop_layer ();
self#activate_view_trade_details pid st (););
Ch 'a', "Accept Trade", (fun () ->
data.event_buffer <- Comm (Trade_offer_command
AcceptTrade)::data.event_buffer);
Ch 'd', "Decline Trade", (fun () ->
data.event_buffer <- Comm (Trade_offer_command
DeclineTrade)::data.event_buffer);
]
end
| State.Land_unowned_prop ->
begin
let curr_location = State.(get_player_location pid st
|> string_of_board_space) in
let location_cost = State.(get_property_cost curr_location st
|> string_of_int) in
bind_keys_simple_turntype [
Ch 'b', "Buy This Property for $"^location_cost, Land_unowned_prop_command Buy;
Ch 'd', "Don't Buy This Property", Land_unowned_prop_command NotBuy;
]
end
| State.Land_your_prop -> bind_keys_simple_turntype [
Ch 'n', "Next Turn", End_turn_command;
]
| State.Land_other_prop ->
begin
let curr_location = State.(get_player_location pid st
|> string_of_board_space) in
let prop_rent = State.(get_property_rent curr_location st
|> string_of_int) in
bind_keys_complex_turntype [
Ch 'p', "Pay Rent of $"^prop_rent, (fun () ->
data.event_buffer <- Comm (Land_other_prop_command PayRent)::data.event_buffer);
Ch 's', "Sell Property to Pay", (fun () -> pop_layer ();
self#activate_select_from_list pid st (State.get_player_properties pid st)
`Prop (fun str ->
data.event_buffer <- Comm (
Land_other_prop_command (Sell_PayRent str))::data.event_buffer) None ()
);
Ch 'b', "Go Bankrupt", (fun () ->
data.event_buffer <- Comm (Land_other_prop_command BankruptRent)::data.event_buffer);
]
end
| State.Land_other_injail_prop -> bind_keys_simple_turntype [
Ch 'n', "Next Turn", End_turn_command;
]
| State.Transaction i ->
begin
if i <= 0 then bind_keys_simple_turntype [
(* receiving money *)
Ch 'n', "Next Turn", End_turn_command;
]
else bind_keys_complex_turntype [
(* has to pay money *)
Ch 'p', "Pay Transaction", (fun () ->
data.event_buffer <- Comm (Transaction_command PayTransaction)::data.event_buffer);
Ch 's', "Sell Property to Pay", (fun () -> pop_layer ();
self#activate_select_from_list pid st (State.get_player_properties pid st)
`Prop (fun str ->
data.event_buffer <- Comm (
Transaction_command (Sell_PayTransaction str))::data.event_buffer) None ()
);
Ch 'b', "Go Bankrupt", (fun () ->
data.event_buffer <- Comm (Transaction_command BankruptTransaction)::data.event_buffer);
]
end
| State.Land_injail -> bind_keys_simple_turntype [
Ch 'n', "Next Turn", End_turn_command;
]
| State.Land_jail_visiting -> bind_keys_simple_turntype [
Ch 'n', "Next Turn", End_turn_command;
]
| State.Draw_GetOutOfJail_Card -> bind_keys_simple_turntype [
Ch 'n', "Next Turn", End_turn_command;
]
end;
(* end bind keys *)
human_box#add comm_box;
human_modal#bind_keys [
self#init_key_binding;
{key=Ch 'R'; desc="Rules"; act=(fun () -> pop_layer ();
self#activate_help_modal (fun () -> pop_layer ();
self#activate_human_modal pid st ()) ()
)}
];
human_modal#add_false_action_labels human_box [
"R", "Rules";
"H", "Go Home";
];
push_layer human_modal ();
(** [human_info_left_box pid st] is the top-left box of the modal frame
* for human players. This modal displays information such as the current
* player, their cash, and the number of properties they own. *)
method private human_info_left_box pid st =
let info_left_box = new vbox in
info_left_box#add ~expand:false (new label ("Howdy "^pid^"!\n"));
let p_money_in_hand = State.get_player_money pid st in
let p_num_prop = State.get_player_properties pid st |> List.length in
let p_location =
if State.get_player_in_jail pid st
then "In Jail."
else ("At "^(State.get_player_location pid st
|> State.string_of_board_space
|> String.capitalize_ascii)^".") in
let p_info =
("Where: "^p_location^"\n"
^"Money-in-hand: $"^(string_of_int p_money_in_hand)^".\n"
^"# Properties Owned: "^(string_of_int p_num_prop)^".") in
info_left_box#add ~expand:false (new label p_info);
info_left_box
(** [human_info_left_box pid st] is the top-right box of the modal frame
* for human players. This modal displays view options such as viewing
* properties, the board, the message log, etc. *)
method private human_info_right_box parent_modal pid st =
let info_right_box = new vbox in
(match State.get_message st with
| None -> ()
| Some curr_mess ->
info_right_box#add (new label ("Message: "^curr_mess));
info_right_box#add ~expand:false (new hline)
);
parent_modal#bind_keys [
{key=Ch '1'; desc=""; act=(fun () -> pop_layer ();
self#activate_select_from_list pid st (State.get_player_properties pid st)
`Prop (fun str -> pop_layer ();
self#activate_prop_details str pid st `Player) None ()
)};
{key=Ch '2'; desc=""; act=(fun () -> pop_layer ();
let (ids, cnts) = List.split (State.get_all_players st) in
self#activate_select_from_list pid st ids
`Player (fun str -> pop_layer ();
self#activate_player_details str st) None ()
)};
{key=Ch '3'; desc=""; act=(fun () -> pop_layer ();
let props = State.get_all_properties st in
self#activate_select_from_list pid st props
`Player (fun str -> pop_layer ();
self#activate_prop_details str pid st `All) None ()
)};
{key=Ch '4'; desc=""; act=(fun () -> pop_layer ();
self#activate_board_info st pid ()
)};
{key=Ch '5'; desc=""; act=(fun () -> pop_layer ();
self#activate_game_hist_log st pid ()
)};
];
parent_modal#add_false_action_labels info_right_box [
"1", "View My Property Info";
"2", "View All Player Info";
"3", "View All Property Info";
"4", "View Board";
"5", "View Entire History"
];
info_right_box
(** [hist_preview_box] is a vbox that displays at most the 5 recent
* gameplay messages from our log. *)
method private hist_preview_box =
let hist_preview_box = new vbox in
self#add_heading hist_preview_box "Recent History";
let history_len =
if List.length data.history >= 5 then 5
else List.length data.history in
if history_len = 0 then begin
hist_preview_box#add ~expand:false (new label "None")
end
else begin
for i = 0 to history_len - 1 do
let l = new label ("- "^List.nth data.history i) in
l#set_alignment H_align_left;
hist_preview_box#add ~expand:false l
done
end;
hist_preview_box
(** [human_comm_box key_binds] is a box displaying the available commands
* to the current player based on the type of turn they are in and uses
* [key_binds] to bind keys to those views. *)
method private human_comm_box key_binds =
let main_box = new vbox in
self#add_heading main_box "Moves";
for i = 0 to List.length key_binds - 1 do
match List.nth key_binds i with
| Special _, _ -> ()
| Ch c, desc ->
begin
let disp = "["^(String.make 1 c)^"] "^desc in
let l = new label disp in
l#set_alignment H_align_left;
main_box#add l;
end
done;
main_box
(** [activate_board_info st pid ()] displays a modal that lists
* all properties on the board as well as the locations of
* the players on the board. The options are :
* 1) Back : return to the human_modal *)
method private activate_board_info st pid () =
let spaces_modal = new game_frame in
let list_box = new vbox in
spaces_modal#set list_box;
let lst = State.get_all_board_spaces st in
let (box, adj) = self#scrollable_box `BoardSpaces lst st in
list_box#add ~expand:false (new hline);
list_box#add box;
spaces_modal#bind_scroller adj;
spaces_modal#bind_keys [
{key=Ch 'b'; desc="Back"; act=(fun () -> pop_layer ();
self#activate_human_modal pid st ())}
];
spaces_modal#add_action_labels list_box;
push_layer spaces_modal ()
(** [activate_prop_details prop_id pid st player] displays a modal
* with the information associated with [prop_id] from [st]. This
* includes the owner, property group, cost, current rent,
* development cost, and development stage. The options are :
* 1) Back : Returns to human_modal *)
method private activate_prop_details prop_id pid st player =
let prop_detail = new game_modal_frame in
let details = new vbox in
let owner =
match State.get_property_owner prop_id st with
| State.Player s -> s
| State.Bank -> "Bank" in
let propgroup = State.get_property_group prop_id st
|> State.string_of_property_group in
let cost = State.get_property_cost prop_id st
|> string_of_int in
let rent = State.get_property_rent prop_id st
|> string_of_int in
let development_cost =
match State.get_property_dev_cost prop_id st with
| None -> "Fully Developed"
| Some i -> string_of_int i in
let development_stage = State.get_property_dev_stage prop_id st
|> string_of_int in
details#add (new label ("Owner: " ^ owner));
details#add (new label ("Property Group: " ^ propgroup));
details#add (new label ("Cost: " ^ cost));
details#add (new label ("Current Rent: " ^ rent));
details#add (new label ("Development Cost: " ^ development_cost));
details#add (new label ("Development Stage: " ^ development_stage));
prop_detail#set details;
prop_detail#bind_keys [
{key=Ch 'b'; desc="Back"; act=(fun () -> pop_layer ();
match player with
| `Player ->
self#activate_select_from_list pid st (State.get_player_properties pid st)
`Prop (fun str -> pop_layer ();
self#activate_prop_details str pid st `Player) None ()
| `All ->
self#activate_select_from_list pid st (State.get_all_properties st)
`Prop (fun str -> pop_layer ();
self#activate_prop_details str pid st `All) None ()
)};
];
prop_detail#add_action_labels details;
push_layer prop_detail ()
(** [activate_player_details pid st] displays a modal
* with the information associated with [pid] from [st]. This
* includes the piece, location, cash holdings, jail status,
* card count, and properties. The options are :
* 1) Back : Returns to human_modal *)
method private activate_player_details pid st =
let player_detail = new game_modal_frame in
let detail = new vbox in
let in_jail = State.get_player_in_jail pid st in
let location = State.get_player_location pid st
|> State.string_of_board_space in
let money = State.get_player_money pid st |> string_of_int in
let cards = State.get_player_cards pid st |> string_of_int in
let properties = State.get_player_properties pid st in
detail#add (new label ("Piece: " ^ pid));
detail#add (new label ("Location: " ^ location));
detail#add (new label ("Money: " ^ money));
detail#add (new label ("In Jail: " ^ (string_of_bool in_jail)));
detail#add (new label ("Cards: " ^ cards));
detail#add (new label ("Properties: "));
let display_props lst =
match lst with
| [] -> ()
| h::t -> detail#add (new label h); in
display_props properties;
player_detail#bind_keys [
{key=Ch 'b'; desc="Go Back"; act=(fun () ->
pop_layer ();
let (ids, cnts) = List.split (State.get_all_players st) in
self#activate_select_from_list pid st ids
`Player (fun str ->
pop_layer ();
self#activate_player_details str st) None ()
)};
];
player_detail#set detail;
player_detail#add_action_labels detail;
push_layer player_detail ()
(** [activate_game_hist_log st pid ()] displays the entire list of
* gameplay messages from our log. *)
method private activate_game_hist_log st pid () =
let hist_view = new game_frame in
let list_box = new vbox in
hist_view#set list_box;
self#add_heading list_box "Game History";
let (box, adj) = self#scrollable_box `Message data.history st in
list_box#add ~expand:false (new hline);
list_box#add box; (* scrollable box must expand *)
hist_view#bind_scroller adj;
hist_view#bind_keys [
{key=Ch 'b'; desc="Go Back"; act=(fun () -> pop_layer ();
self#activate_human_modal pid st ())}
];
hist_view#add_action_labels list_box;
push_layer hist_view ()
(** [activate_ai_modal pid ai_cnt ()] activates the modal frame that
* shows the game players that the player with name [pid] and controller
* [ai_cnt] is playing. The AI modal is activated for at least 3 seconds.
* requires:
* - data.s <> None
* - [List.hd (State.get_all_players (extract data.s)) = (pid, ai_cnt). *)
method private activate_ai_modal pid cnt disp () =
let ai_modal = new game_modal_frame in
let ai_box = new vbox in
ai_modal#set ai_box;
ai_box#add (new label ("TURN "^(string_of_int data.turns)));
ai_box#add (new label disp);
ai_modal#bind_keys [self#init_key_binding;];
ai_modal#add_action_labels ai_box;
push_layer ai_modal ();
(* sleep for 3 sec to allow humans to see what the ai's are doing *)
Unix.sleepf 3.0
(** [activate_make_trade pid st pselected offer ret_offer ()] activates the
* modal frame when the player with controller GUI requests to make a trade.
* The modal frame displays the offer details, and can update their offer
* and return offer. The return offer section is enabled when
* [pselected <> None]. A key binding to finish the trade is only enabled when
* the [offer] and [ret_offer] are non-empty. *)
method private activate_make_trade pid st pselected
({mon=o_mon; prop_lst=o_props} as offer)
({mon=r_mon; prop_lst=r_props} as ret_offer) () =
let trade_modal = new game_modal_frame in
let trade_box = new vbox in
trade_modal#set trade_box;
self#add_heading trade_box "Make a Trade";
let l = new label "Steps to make a trade:\n\
- Select a Player\n\
- Determine your Offer\n\
- Determine the Return Offer\n" in
l#set_alignment H_align_left;
trade_box#add l;
let string_of_offer = function
| `Offer -> "You want to give $"^(string_of_int o_mon)^" in money and "
^(if List.length o_props = 0 then "no properties.\n"
else "these properties: "^String.concat ", " o_props^".\n")
| `Return -> "You want to receive $"^(string_of_int r_mon)^" in money and "
^(if List.length r_props = 0 then "no properties."
else "these properties: "^String.concat ", " r_props^".\n") in
let other_players = List.split (State.get_all_players st) |> fst
|> List.filter (fun s -> s <> pid) in
let players_not_in_trade =
match pselected with