M README.md +6 -0
@@ 18,4 18,10 @@ I get it to play enough games I can use
- Model a decent gamestate, flow, render, REPL? Very bare minimum.
- Start filling it in with proper cards, characters, actions.
+## High-level strategy
+
+- MAXIMIZE cure research
+- MINIMIZE outbreak potential
+- MINIMIZE outbreak damage
+
[1]: https://git.sr.ht/~srpablo/ScrabbleCheat
A => src/panacea_lib/gameplay.ml +57 -0
@@ 0,0 1,57 @@
+open Gamestate
+
+(**
+ * Do 4 actions.
+ * Draw 2 Player cards.
+ * Infect cities.
+ *)
+
+(** What is a "move"?
+ * Bot receive a Gamestate and generate a list of moves for it.
+ * AI will then look at them and the Gamestate and select the "best" one.
+ * Visibility into the decks is disallowed.
+ *)
+
+type action =
+ | Drive of infection_city (* Move to a city connected to the one you are in. *)
+ | DirectFlight of infection_city (* Discard a city card to move to the city named on the card.*)
+ | CharterFlight of infection_city (* Discard the city card matching the city you are in to go to _any_ city *)
+ | ShuttleFlight of infection_city (* Move from a city with a research station to another with a research station. *)
+ | BuildResearch of infection_city (* Discard card matching city you're in, build a research station. *)
+ | TreatDisease of infection_city * infection_color (* Remove cubes *)
+ | ShareKnowledge of infection_city (* Give card to another player *)
+ | DiscoverCure of infection_color * infection_card list
+
+
+type move = (action * action * action * action)
+
+
+let apply_action gs action =
+ let active_player = Gamestate.active_player gs in
+ match action with
+ | Drive city ->
+ gs
+ |> Gamestate.move_player_to ~player:active_player ~city:city
+ |> Gamestate.decrement_actions_remaining
+ | DirectFlight city ->
+ gs
+ |> Gamestate.move_player_to ~player:active_player ~city:city
+ |> Gamestate.discard_player_card ~player:active_player ~card:(City city)
+ |> Gamestate.decrement_actions_remaining
+ | CharterFlight city ->
+ gs
+ |> Gamestate.move_player_to ~player:active_player ~city:city
+ |> Gamestate.discard_player_card_at_location ~player:active_player
+ |> Gamestate.decrement_actions_remaining
+ | ShuttleFlight city ->
+ gs
+ |> Gamestate.move_player_to ~player:active_player ~city:city
+ |> Gamestate.decrement_actions_remaining
+ | BuildResearch city ->
+ gs
+ |> Gamestate.place_research_station ~city:city
+ |> Gamestate.decrement_actions_remaining
+ | _ -> gs
+
+
+let generate_moves _gs = [5]
M src/panacea_lib/gamestate.ml +91 -24
@@ 2,15 2,33 @@ open Base
(**
* The Gamestate contains everything you need to properly model a game in-progress.
+ * Also contains functions to alter or change it.
*)
type difficulty =
- | Introductory (* 4 Epidemic cards *)
- | Standard (* 5 Epidemic cards *)
- | Heroic (* 6 Epidemic cards *)
+ | Introductory
+ | Standard
+ | Heroic
+
+
+type infection_color =
+ | Black
+ | Blue
+ | Red
+ | Yellow
[@@deriving show]
+type infection_city =
+ | Algiers | Atlanta | Baghdad | Bangkok | Beijing | Bogota | BuenosAires | Cairo
+ | Chennai | Chicago | Delhi | Essen | HoChiMinhCity | HongKong | Istanbul | Jakarta
+ | Johannesburg | Karachi | Khartoum | Kinshasa | Kolkata | Lagos | Lima | London
+ | LosAngeles | Madrid | Manila | MexicoCity | Miami | Milan | Montreal | Moscow
+ | Mumbai | NewYork | Osaka | Paris | Riyadh | SanFrancisco | Santiago | SaoPaolo
+ | Seoul | Shanghai | StPetersburg | Sydney | Taipei | Tehran | Tokyo | Washington
+ [@@deriving sexp, compare, show]
+
+
type role =
(* - As an action, take any discarded Event card and store it on this card.
* - When you play the stored Event card, remove it from the game.
@@ 50,21 68,7 @@ type role =
| Scientist
[@@deriving show]
-type infection_color =
- | Black
- | Blue
- | Red
- | Yellow
- [@@deriving show]
-type infection_city =
- | Algiers | Atlanta | Baghdad | Bangkok | Beijing | Bogota | BuenosAires | Cairo
- | Chennai | Chicago | Delhi | Essen | HoChiMinhCity | HongKong | Istanbul | Jakarta
- | Johannesburg | Karachi | Khartoum | Kinshasa | Kolkata | Lagos | Lima | London
- | LosAngeles | Madrid | Manila | MexicoCity | Miami | Milan | Montreal | Moscow
- | Mumbai | NewYork | Osaka | Paris | Riyadh | SanFrancisco | Santiago | SaoPaolo
- | Seoul | Shanghai | StPetersburg | Sydney | Taipei | Tehran | Tokyo | Washington
- [@@deriving sexp, compare, show]
type player_card_event = {
name: string;
@@ 96,7 100,7 @@ type board_city = {
num_yellow : int;
has_research_station : bool;
neighbors : infection_city list;
-} [@@deriving show]
+}
(* This is a bit of a nasty workaround to use a custom ADT as a key for a Base.Map
@@ 137,6 141,7 @@ type gamestate = {
num_outbreaks : int;
curr_turn : int;
actions_remaining : int;
+ infection_rate_marker_and_index : (int * int list);
research_stations_remaining : int;
infection_deck : infection_card list;
infection_discard : infection_card list;
@@ 152,9 157,13 @@ type gamestate = {
is_yellow_cured : bool;
}
+type t = gamestate
+
+let newline_split = String.concat ~sep:"\n"
+let comma_split = String.concat ~sep:","
let show_gamestate gs =
- let player_string = List.map ~f:show_player gs.players |> String.concat ~sep:"\n" in
+ let player_string = List.map ~f:show_player gs.players |> newline_split in
let board_string = show_board gs.board in
let turn_string = Printf.sprintf "Current Turn: %d. Number of outbreaks: %d. Actions remaining: %d. Research stations available: %d"
gs.curr_turn gs.num_outbreaks gs.actions_remaining gs.research_stations_remaining
@@ 165,7 174,23 @@ let show_gamestate gs =
let cure_string = Printf.sprintf "CURES: Red %B. Blue %B. Black %B. Yellow %B."
gs.is_red_cured gs.is_blue_cured gs.is_black_cured gs.is_yellow_cured
in
- String.concat ~sep:"\n" ["===== GAMESTATE ====="; player_string; board_string; turn_string; cube_string; cure_string]
+ let infection_deck_str = (List.cons "Infection Deck:" @@ List.map gs.infection_deck ~f:show_infection_card) |> comma_split in
+ let infection_discard_str = (List.cons "Infection Discard:" @@ List.map gs.infection_discard ~f:show_infection_card) |> comma_split in
+ let player_deck_str = (List.cons "Player Deck:" @@ List.map gs.player_deck ~f:show_player_card) |> comma_split in
+ let player_discard_str = (List.cons "Player Discard:" @@ List.map gs.player_discard ~f:show_player_card) |> comma_split in
+ newline_split
+ [
+ "===== GAMESTATE =====";
+ player_string;
+ board_string;
+ turn_string;
+ cube_string;
+ cure_string;
+ infection_deck_str;
+ infection_discard_str;
+ player_deck_str;
+ player_discard_str;
+ ]
let empty_board =
@@ 272,6 297,7 @@ let empty_gamestate =
board=empty_board;
curr_turn=0;
actions_remaining=4;
+ infection_rate_marker_and_index=(0, [2;2;2;3;3;4;4]);
research_stations_remaining=6;
infection_deck=starting_infection_deck;
infection_discard=[];
@@ 378,17 404,16 @@ let place_outbreaks difficulty gs =
(** Places the first research center in Atlanta. *)
-let place_research_station place gs =
+let place_research_station ~city gs =
let {board=b; research_stations_remaining=r;_} = gs in
let updated_board =
- Map.change b place ~f:(fun x -> match x with
+ Map.change b city ~f:(fun x -> match x with
| None -> None
| Some b_city -> Some {b_city with has_research_station=true})
in
{gs with research_stations_remaining=r-1; board=updated_board}
-(** Gives us a Gamestate that's ready to play! *)
let starting_game ~player_names ~difficulty =
let transformations =
[
@@ 398,10 423,52 @@ let starting_game ~player_names ~difficu
hand_out_cards;
infect_cities;
place_outbreaks difficulty;
- place_research_station Atlanta;
+ place_research_station ~city:Atlanta;
]
in
List.fold_left
~init:empty_gamestate
~f:(fun accum x -> x accum)
transformations
+
+
+let update_location city player = {player with location=city}
+
+let discard_card card player =
+ let new_hand = List.filter player.hand ~f:(fun x -> not (phys_equal x card)) in
+ {player with hand=new_hand}
+
+let discard_location_card player =
+ let location_card = City player.location in
+ discard_card location_card player
+
+
+let update_player_named (name : string) (func : player -> player) : (player -> player) =
+ fun p ->
+ match String.equal p.name name with
+ | true -> func p
+ | false -> p
+
+
+let move_player_to ~player ~city gs =
+ let updated_players = List.map ~f:(update_player_named player (update_location city)) gs.players in
+ {gs with players=updated_players}
+
+
+let discard_player_card ~player ~card gs =
+ let updated_players = List.map ~f:(update_player_named player (discard_card card)) gs.players in
+ {gs with players=updated_players}
+
+
+let discard_player_card_at_location ~player gs =
+ let updated_players = List.map ~f:(update_player_named player discard_location_card) gs.players in
+ {gs with players=updated_players}
+
+
+
+let decrement_actions_remaining gs =
+ {gs with actions_remaining=gs.actions_remaining - 1}
+
+
+let active_player gs =
+ (List.nth_exn gs.players gs.curr_turn).name
A => src/panacea_lib/gamestate.mli +62 -0
@@ 0,0 1,62 @@
+type t
+
+type difficulty =
+ | Introductory
+ | Standard
+ | Heroic
+
+
+type infection_city =
+ | Algiers | Atlanta | Baghdad | Bangkok | Beijing | Bogota | BuenosAires | Cairo
+ | Chennai | Chicago | Delhi | Essen | HoChiMinhCity | HongKong | Istanbul | Jakarta
+ | Johannesburg | Karachi | Khartoum | Kinshasa | Kolkata | Lagos | Lima | London
+ | LosAngeles | Madrid | Manila | MexicoCity | Miami | Milan | Montreal | Moscow
+ | Mumbai | NewYork | Osaka | Paris | Riyadh | SanFrancisco | Santiago | SaoPaolo
+ | Seoul | Shanghai | StPetersburg | Sydney | Taipei | Tehran | Tokyo | Washington
+
+
+type infection_color =
+ | Black
+ | Blue
+ | Red
+ | Yellow
+
+
+type infection_card = infection_city
+
+
+type player_card_event = {
+ name: string;
+ description: string;
+}
+
+
+type player_card =
+ | Event of player_card_event
+ | Epidemic
+ | City of infection_city
+
+
+(** Printable representation of a Gamestate. *)
+val show_gamestate : t -> string
+
+(** Gives us a Gamestate that's ready to play! *)
+val starting_game : player_names:string list -> difficulty:difficulty -> t
+
+(** Places the current active player to the location. *)
+val move_player_to : player:string -> city:infection_city -> t -> t
+
+(** Places the current active player to the location. *)
+val discard_player_card : player:string -> card:player_card -> t -> t
+
+(** Places the current active player to the location. *)
+val discard_player_card_at_location : player:string -> t -> t
+
+(** Use an action of this player's turn. *)
+val decrement_actions_remaining : t -> t
+
+(** Place a Research Station at this city. *)
+val place_research_station : city:infection_city -> t -> t
+
+(** Get the name of the player who's turn it is. *)
+val active_player : t -> string