⚠️ Warning: This is a draft ⚠️

This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.

If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.

{{collection|Go Fish}}

type pip = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | 
           Jack | Queen | King | Ace 
let pips = [Two; Three; Four; Five; Six; Seven; Eight; Nine; Ten;
            Jack; Queen; King; Ace]
 
type suit = Diamonds | Spades | Hearts | Clubs
let suits = [Diamonds; Spades; Hearts; Clubs]
 
type card = pip * suit

let string_of_pip = function
  | Two   -> "Two"
  | Three -> "Three"
  | Four  -> "Four"
  | Five  -> "Five"
  | Six   -> "Six"
  | Seven -> "Seven"
  | Eight -> "Eight"
  | Nine  -> "Nine"
  | Ten   -> "Ten"
  | Jack  -> "Jack"
  | Queen -> "Queen"
  | King  -> "King"
  | Ace   -> "Ace"
 
let string_of_suit = function
  | Diamonds -> "Diamonds"
  | Spades   -> "Spades"
  | Hearts   -> "Hearts"
  | Clubs    -> "Clubs"

let string_of_card (pip, suit) =
  (Printf.sprintf "(%s-%s)" (string_of_pip pip) (string_of_suit suit))

 
let pip_of_card (pip, _) = (pip)

let deck =
  List.concat (List.map (fun pip -> List.map (fun suit -> (pip, suit)) suits) pips)


type rank_state =
  | Unknown   (* Don't know if the opponent has any cards in that rank. *)
  | No_cards  (* Opponent has no cards there; I took them away, or I asked yet. *)
  | Has_cards (* Opponent has cards there; they tried to get them off me and haven't booked them yet. *)
  | Booked    (* Someone has booked the rank. *)

let state_score = function
  | Booked    -> 0
  | No_cards  -> 1
  | Unknown   -> 2
  | Has_cards -> 3

let string_of_state = function
  | Booked    -> "Booked"
  | No_cards  -> "No_cards"
  | Unknown   -> "Unknown"
  | Has_cards -> "Has_cards"

let replace ((rank,_) as state) opp =
  let rec aux acc = function
  | (_rank,_)::tl when _rank = rank -> List.rev_append acc (state::tl)
  | hd::tl -> aux (hd::acc) tl
  | [] -> assert(false)
  in
  aux [] opp ;;


class virtual abstract_player =
  object (s)
    val mutable virtual cards : card list
    val mutable virtual books : pip list
    method virtual ask_rank : unit -> pip
    method virtual give_rank : pip -> card list
    method virtual notify_booked : pip -> unit
    method virtual request_failed : pip -> unit

    method private cards_given rank =
      let matched, rest = List.partition (fun (pip,_) -> pip = rank) cards in
      if List.length matched = 4 then begin
        cards <- rest;
        books <- rank :: books;
        s#notify_booked rank;
        (Some rank)
      end
      else (None)

    method give_card (card : card) =
      let rank = pip_of_card card in
      cards <- card :: cards;
      s#cards_given rank

    method give_cards (_cards : card list) =
      let rank =
        match _cards with
        | [] -> invalid_arg "empty list"
        | hd::tl ->
            List.fold_left
              (fun rank1 (rank2,_) ->
                if rank1 <> rank2
                then invalid_arg "!= ranks"
                else (rank1)
              ) (pip_of_card hd) tl
      in
      cards <- _cards @ cards;
      s#cards_given rank

    method give_rank rank =
      let give, _cards = List.partition (fun (pip, _) -> pip = rank) cards in
      cards <- _cards;
      (give)

    method books_length =
      (List.length books)

    method empty_hand =
      cards = []

    method private dump_cards() =
      print_endline(String.concat ", " (List.map string_of_card cards));

  end



class human_player =
  object (s) inherit abstract_player

    val mutable cards = []
    val mutable books = []

    method ask_rank() =
      let ranks =
        List.fold_left (fun acc card ->
          let rank = pip_of_card card in
          if List.mem rank acc
          then (acc)
          else (rank::acc)
        )
        [] cards
      in
      s#dump_cards();
      Printf.printf "Ranks: %s\n%!" (String.concat ", " (List.map string_of_pip ranks));
      let n = List.length ranks in
      Printf.printf "choose from 1 to %d\n%!" n;
      let get_int() =
        try read_int()
        with Failure "int_of_string" -> raise Exit
      in
      let rec aux() =
        let d = get_int() in
        if d <= 0 || d > n then aux() else (pred d)
      in
      let d = aux() in
      (List.nth ranks d)

    method notify_booked rank =
      Printf.printf "Rank [%s] is now booked\n%!" (string_of_pip rank);

    method request_failed rank = ()
  end



class ai_player =
  object (s) inherit abstract_player as parent

    val mutable cards = []
    val mutable books = []
    val mutable opponent = List.map (fun rank -> (rank, Unknown)) pips

    method private dump_state() =
      let f (pip, state) =
        Printf.sprintf "{%s:%s}" (string_of_pip pip) (string_of_state state)
      in
      print_endline(String.concat ", " (List.map f opponent));

    method ask_rank() =
      let ranks =
        List.fold_left (fun acc card ->
          let rank = pip_of_card card in
          try
            let _,n = List.find (fun (_rank,_) -> _rank = rank) acc in
            (replace (rank, n+1) acc)
          with Not_found ->
            ((rank,1)::acc)
        )
        [] cards
      in
      let f (rank,_) =
        (state_score(List.assoc rank opponent))
      in
      let ranks = List.sort (fun a b -> (f b) - (f a)) ranks in
      (* DEBUG
      Printf.printf "Ranks: %s\n%!" (String.concat ", " (List.map string_of_pip ranks));
      s#dump_state();
      s#dump_cards();
      *)
      opponent <- List.sort (fun _ _ -> Random.int 9 - Random.int 9) opponent;
      match ranks with
      | [] -> Jack
      | (x,_)::_ -> x

    method give_cards (_cards : card list) =
      let rank = pip_of_card(List.hd _cards) in
      opponent <- replace (rank, No_cards) opponent;
      (parent#give_cards _cards)

    method give_rank rank =
      opponent <- replace (rank, Has_cards) opponent;
      (parent#give_rank rank)

    method notify_booked rank =
      opponent <- replace (rank, Booked) opponent

    method request_failed rank =
      opponent <- replace (rank, No_cards) opponent
  end



class random_player =
  object (s) inherit ai_player

    method ask_rank() =
      let ranks =
        List.fold_left (fun acc card ->
          let rank = pip_of_card card in
          if List.mem rank acc
          then (acc)
          else (rank::acc)
        )
        [] cards
      in
      let n = List.length ranks in
      let d = Random.int n in
      (List.nth ranks d)

  end



exception Empty_deck
let card_to_player deck player op =
  match deck with
  | card::deck ->
      begin match player#give_card card with
      | None -> ()
      | Some rank -> op#notify_booked rank
      end;
      (deck)
  | _ -> raise Empty_deck

let n_cards_to_player n deck player op =
  let rec aux i deck =
    if i >= n then (deck) else
      let deck = card_to_player deck player op in
      aux (succ i) deck
  in
  aux 0 deck ;;


let () =
  Random.self_init();
  let deck = List.sort (fun _ _ -> Random.int 9 - Random.int 9) deck in
  let player_a = new human_player
  and player_b = new ai_player in
  let deck = n_cards_to_player 9 deck player_a player_b in
  let deck = n_cards_to_player 9 deck player_b player_a in
  let deck = ref deck in
  let empty_hand player1 player2 =
    if player1#empty_hand
    then deck := card_to_player !deck player1 player2
  in
  let rec make_turn id1 id2 player1 player2 =
    print_newline();
    (try
       empty_hand player1 player2;
       empty_hand player2 player1;
     with Empty_deck -> ());
    if player1#books_length + player2#books_length <> 13
    then begin
      let rank = player1#ask_rank() in
      Printf.printf "player %s asked for %ss\n%!" id1 (string_of_pip rank);
      let cards = player2#give_rank rank in
      match cards with
      | [] ->
          Printf.printf "player %s has no %ss\n%!" id2 (string_of_pip rank);
          player1#request_failed rank;
          (try
             deck := card_to_player !deck player1 player2;
             make_turn id2 id1 player2 player1
           with Empty_deck -> ())

      | cards ->
          let given = String.concat ", " (List.map string_of_card cards) in
          Printf.printf "player %s gives %s\n%!" id2 given;
          begin match player1#give_cards cards with
          | None -> ()
          | Some rank ->
              Printf.printf "player %s booked [%s]\n%!" id1 (string_of_pip rank);
              player2#notify_booked rank;
          end;
          make_turn id1 id2 player1 player2
    end
  in
  (try
     if Random.bool()
     then make_turn "a" "b" player_a player_b
     else make_turn "b" "a" player_b player_a;
   with Exit -> ());

  Printf.printf "player a has %d books\n" (player_a#books_length);
  Printf.printf "player b has %d books\n" (player_b#books_length);
;;