zoedsoupe

zoedsoupe

I’m trying to implement the war game in Elixir using a Queue

This is the brief game description:

The game starts with a shuffled deck of cards. The deck will be passed into your program already shuffled (details below). The cards are dealt in an alternating fashion to each player, so that each player has 26 cards.
In each round, both players reveal the top card of their pile. The player with the higher card (by rank) wins both cards, placing them at the bottom of their pile. Aces are considered high, meaning the card ranks in ascending order are 2-10, Jack, Queen, King, Ace.
If the revealed cards are tied, there is war! Each player turns up one card face down followed by one card face up. The player with the higher face-up card takes both piles (six cards – the two original cards that were tied, plus the four cards from the war). If the turned-up cards are again the same rank, each player places another card face down and turns another card face up. The player with the higher card takes all 10 cards, and so on.
When one player runs out of cards, they are the loser, and the other the winner. If, during a war, a player runs out of cards, this counts as a loss as well.

This is my Queue implementation:

defmodule Queue do
  use GenServer

  @intial_state %{
    size: 0,
    queue: [],
  }

  # Client - Public and high level API

  def start_link do
    GenServer.start_link(__MODULE__, @intial_state)
  end

  def enqueue(pid, elems) do
    GenServer.cast(pid, {:enqueue, elems})
  end

  def dequeue(pid) do
    GenServer.call(pid, :dequeue)
  end

  def dequeue(pid, many) do
    GenServer.call(pid, {:dequeue, many})
  end

  def size(pid) do
    GenServer.call(pid, :size)
  end

  def front(pid) do
    GenServer.call(pid, :front)
  end

  def rear(pid) do
    GenServer.call(pid, :rear)
  end

  def flush(pid) do
    GenServer.call(pid, :flush)
  end

  # Server - Public but internal API
  # handle_cast - handle the demand asynchronously
  # handle_call - handle the demand eagerly

  @impl true
  def init(init) do
    {:ok, init}
  end

  @impl true
  def handle_call(:size, _from, state) do
    {:reply, state.size, state}
  end

  def handle_call(:front, _from, state) do
    %{queue: xs} = state

    case xs do
      [] -> {:reply, nil, state}
      [x | _] -> {:reply, x, state}
    end
  end

  def handle_call(:rear, _from, state) do
    %{queue: xs} = state

    case xs do
      [] -> {:reply, nil, state}
      xs -> {:reply, List.last(xs), state}
    end
  end

  def handle_call(:flush, _from, state) do
    {elems, state} = deq_many(state, state.size)

    {:reply, elems, state}
  end

  def handle_call(:dequeue, _from, state) do
    {elem, state} = deq(state)

    {:reply, elem, state}
  end

  def handle_call({:dequeue, many}, _from, state) do
    {elems, state} = deq_many(state, many)

    {:reply, Enum.reverse(elems), state}
  end

  defp deq_many(state, n) do
    Enum.reduce(1..n, {[], state}, fn
      _, {elems, state} ->
        {elem, state} = deq(state)

        {[elem | elems], state}
    end)
  end

  defp deq(state) do
    %{queue: xs, size: size} = state

    case xs do
      [] -> {nil, state}
      [x | xs] -> {x, %{state | queue: xs, size: size - 1}}
    end
  end

  @impl true
  def handle_cast({:enqueue, elems}, state) when is_list(elems) do
    %{queue: xs, size: x} = state

    xs = List.foldr(elems, xs, &[&1 | &2])

    {:noreply,
      %{state | size: x + length(elems), queue: xs}}
  end

  def handle_cast({:enqueue, elem}, state) do
    %{queue: xs, size: x} = state

    {:noreply, %{state | size: x + 1, queue: Enum.reverse([elem | xs])}}
  end
end

This is my current implementation so far of war game (it fails with timeout):

defmodule War do
  @doc """
  The main module to the challenge.
  This module exposes a deal/1 function to play the game.

  You can run all tests executing `elixir war.ex`.
  """
  require Integer

  # prefers to use a weight as we don't represent the cards
  @ace_weight 14

  def deal(deck) do
    {deck_1, deck_2} = deal_deck(deck)

    {:ok, player_1} = Queue.start_link()
    {:ok, player_2} = Queue.start_link()

    :ok = Queue.enqueue(player_1, deck_1)
    :ok = Queue.enqueue(player_2, deck_2)

    winner = play_game(player_1, player_2)

    Queue.size(winner)
    |> then(&Queue.dequeue(winner, &1))
    |> Enum.map(&remove_ace_weight/1)
  end

  defp deal_deck(deck) do
    List.foldr(deck, {[], []}, &deal_player/2)
  end

  defp play_game(p1, p2) do
    if winner = maybe_get_winner(p1, p2) do
      winner
    else
      case play_turn(p1, p2) do
        {winner, []} ->
          winner

        {turn_winner, cards} ->
          push_cards(turn_winner, cards)
          play_game(p1 ,p2)
      end
    end
  end

  defp play_turn(p1, p2, x \\ nil, y \\ nil, tied \\ []) do
    x = x || Queue.dequeue(p1)
    y = y || Queue.dequeue(p2)
    cards = [x, y]

    cond do
      x > y -> {p1, cards ++ tied}
      x < y -> {p2, cards ++ tied}
      x == y -> war(p1, p2, cards ++ tied)
    end
  end

  defp war(p1, p2, tied) do
    [x, y] = Enum.take(tied, 2)
    tied = Enum.drop(tied, 2)

    cond do
      !able_to_war?(p1) ->
        cards = tied ++ Queue.flush(p1)
        push_cards(p2, cards)
        {p2, []}


      !able_to_war?(p2) ->
        cards = tied ++ Queue.flush(p2)
        push_cards(p1, cards)
        {p1, []}

      true ->
        {turn_winner, cards} = play_turn(p1, p2, x, y, tied)
        push_cards(turn_winner, cards)
        play_game(p1, p2)
    end
  end

  defp deal_player(card, {p1, p2}) do
    if length(p1) == length(p2) do
      {[apply_ace_weight(card) | p1], p2}
    else
      {p1, [apply_ace_weight(card) | p2]}
    end
  end

  defp able_to_war?(player) do
    Queue.size(player) > 3
  end

  # The game ends when a player losses all their cards
  # so their Stack is empty
  defp maybe_get_winner(player_1, player_2) do
    cond do
      Queue.size(player_1) == 0 -> player_2
      Queue.size(player_2) == 0 -> player_1
      true -> nil
    end
  end

  defp apply_ace_weight(card) do
    (card == 1 && @ace_weight) || card
  end

  defp remove_ace_weight(card) do
    (card == @ace_weight && 1) || card
  end

  # Cards won from a war needs to be pushed in descending order
  defp push_cards(player, cards) do
    cards = Enum.sort(cards, :desc)

    Queue.enqueue(player, cards)
  end
end

And these are the tests cases:

defmodule WarTest do
  use ExUnit.Case

  describe "War" do
    test "deal_1" do
      t1 = [1,1,1,1,13,13,13,13,11,11,11,11,12,12,12,12,10,10,10,10,9,9,9,9,7,7,7,7,8,8,8,8,6,6,6,6,5,5,5,5,4,4,4,4,3,3,3,3,2,2,2,2]
      r1 = [1,1,1,1,13,13,13,13,12,12,12,12,11,11,11,11,10,10,10,10,9,9,9,9,8,8,8,8,7,7,7,7,6,6,6,6,5,5,5,5,4,4,4,4,3,3,3,3,2,2,2,2]
      assert War.deal(t1) == r1
    end

    test "deal_2" do
      t2 = [1,13,1,13,1,13,1,13,12,11,12,11,12,11,12,11,10,9,10,9,10,9,10,9,8,7,8,7,8,7,8,7,6,5,6,5,6,5,6,5,4,3,4,3,4,3,4,3,2,2,2,2]
      r2 = [4,3,2,2,2,2,4,3,4,3,4,3,6,5,6,5,6,5,6,5,8,7,8,7,8,7,8,7,10,9,10,9,10,9,10,9,12,11,12,11,12,11,12,11,1,13,1,13,1,13,1,13]
      assert War.deal(t2) == r2
    end

    test "deal_3" do
      t3 = [13,1,13,1,13,1,13,1,11,12,11,12,11,12,11,12,9,10,9,10,9,10,9,10,7,8,7,8,7,8,7,8,5,6,5,6,5,6,5,6,3,4,3,4,3,4,3,4,2,2,2,2]
      r3 = [4,3,2,2,2,2,4,3,4,3,4,3,6,5,6,5,6,5,6,5,8,7,8,7,8,7,8,7,10,9,10,9,10,9,10,9,12,11,12,11,12,11,12,11,1,13,1,13,1,13,1,13]
      assert War.deal(t3) == r3
    end

    test "deal_4" do
      t4 = [10,11,12,13,1,2,3,4,5,6,7,8,9,10,11,12,13,1,2,3,4,5,6,7,8,9,10,11,12,13,1,2,3,4,5,6,7,8,9,10,11,12,13,1,2,3,4,5,6,7,8,9]
      r4 = [1,1,13,12,9,5,11,4,9,3,8,7,7,2,13,10,12,5,10,4,9,6,8,3,1,1,13,12,7,5,11,4,9,3,8,6,7,2,13,10,12,5,11,11,10,8,6,4,6,3,2,2]
      assert War.deal(t4) == r4
    end

    test "deal_5" do
      t5 = [1,2,3,4,5,6,7,8,9,10,11,12,13,1,2,3,4,5,6,7,8,9,10,11,12,13,1,2,3,4,5,6,7,8,9,10,11,12,13,1,2,3,4,5,6,7,8,9,10,11,12,13]
      r5 = [1,10,13,8,11,9,8,7,11,8,13,7,13,6,12,6,9,5,8,5,7,4,7,4,11,6,12,10,6,3,2,2,12,5,9,3,10,4,9,2,10,3,5,2,1,1,1,13,12,11,4,3]
      assert War.deal(t5) == r5
    end

    defp create_deck do
      deck =
        for n <- 1..13, _ <- 1..4 do
          n
        end

      Enum.shuffle(deck)
    end

    test "should return the same number of cards after deal" do
      deck = create_deck()

      assert length(deck) == 52
      assert length(War.deal(deck)) == 52
    end

    test "should remove the ace weight after a win" do
      deck = create_deck()

      assert Enum.all?(War.deal(deck), &(&1 != 14))
    end
  end
end

I also made a public repo of this challenge: GitHub - zoedsoupe/war.ex: War card game implemented in Elixir · GitHub

I’m trying to understand why my code fails and how I could improve it to achieve the desired result.

Showing Posts 1 to 10

soup

soup

Hope this is helpful and that I haven’t misunderstood, otherwise apologies.

Consider these tests

  test "enqueue returns correct order" do
    {:ok, pid} = Queue.start_link()

    assert :ok = Queue.enqueue(pid, [1])
    assert Queue.size(pid) == 1
    assert :ok = Queue.enqueue(pid, [2])
    assert Queue.size(pid) == 2
    assert :ok = Queue.enqueue(pid, [3])
    assert Queue.size(pid) == 3
    assert Queue.flush(pid) == [1, 2, 3]
    assert Queue.size(pid) == 0

    assert :ok = Queue.enqueue(pid, [1])
    assert Queue.size(pid) == 1
    assert :ok = Queue.enqueue(pid, [2, 3])
    assert Queue.size(pid) == 3
    assert Queue.flush(pid) == [1, 2, 3]
    assert Queue.size(pid) == 0
  end
    test "minor hand" do
      deck = [10,10,1,1]
      expect = [1,1,10,10]
      # :: deal deck
      # p1      p2
      # [10, 1] [10, 1]
      #
      # :: play turn
      # p1      p2
      # [1]     [1]
      # 10 vs 10 <- even, war
      #
      # :: war
      # p1 size < 3, unable to war,
      # cards = flush + tied
      #       = [1] + [10, 10]
      #       = [1, 10, 10]
      #
      # p1     p2   float
      # []     [1]  [1, 10, 10]
      #
      # push_cards(p2, [1, 10, 10])
      #
      # :: push cards
      #
      # sorted = [1, 10, 10] (where 1 is actually 14)
      # enqueue p2 sorted
      #
      # -> [1, 1, 10, 10]
      # 1 <- held in hand cause p2 never flushed
      # 1 <- flush from p2
      # 10 <- collected tied
      # 10 <- collected tied
      assert War.deal(deck) == expect
    end
Check queue#flush and its test, spoiler

A queue should be FIFO, so if we enqueue 1 then 2, then 3, when we flush it (assuming you mean this to be “dequeue everything”), we should get 1 (first in, first out), then 2, then 3.

  test "flush/1" do
    {:ok, pid} = Queue.start_link()

    assert :ok = Queue.enqueue(pid, [1, 2, 3])
    assert Queue.size(pid) == 3
    assert Queue.flush(pid) == [3, 2, 1] # 🤔
    assert Queue.size(pid) == 0
  end
Check {:enqueue, elem}, does the queue look correct?, spoiler
def handle_cast({:enqueue, elem}, state) do
    %{queue: xs, size: x} = state
   {:noreply, %{state | size: x + 1, queue: Enum.reverse([elem | xs])}} # 🤔
 end

consider

elem, xs = 3, [1, 2]

 [3 | [1, 2]]
 = [3, 1, 2]
   |> reverse()
 = [2, 1, 3]

elem, xs = 4, [2, 1, 3]

 [4 | [2, 1, 3]]
 = [4, 2, 1, 3]
   |> reverse()
 = [3, 1, 2, 4]

Something doesn’t seem quite right ey?

patch, spoiler

Note this patch does not make your tests pass, but see the comment, I could not be bothered to manually play out the hand and check the expected result.

diff --git a/lib/queue.ex b/lib/queue.ex
index 07baee9..843e7e6 100644
--- a/lib/queue.ex
+++ b/lib/queue.ex
@@ -84,19 +84,22 @@ defmodule Queue do
     {:reply, elem, state}
   end
 
-  def handle_call({:dequeue, many}, _from, state) do
-    {elems, state} = deq_many(state, many)
+  def handle_call({:dequeue, count}, _from, state) do
+    {elems, state} = deq_many(state, count)
 
-    {:reply, Enum.reverse(elems), state}
+    {:reply, elems, state}
   end
 
   defp deq_many(state, n) do
-    Enum.reduce(1..n, {[], state}, fn
-      _, {elems, state} ->
-        {elem, state} = deq(state)
+    {elems, state} =
+      Enum.reduce(1..n, {[], state}, fn
+        _, {elems, state} ->
+          {elem, state} = deq(state)
+          {[elem | elems], state}
+      end)
 
-        {[elem | elems], state}
-    end)
+    # Opinion: the reverse is an impl detail of deq_many, should not be exposed
+    {Enum.reverse(elems), state}
   end
 
   defp deq(state) do
@@ -111,15 +114,16 @@ defmodule Queue do
   @impl true
   def handle_cast({:enqueue, elems}, state) when is_list(elems) do
     %{queue: xs, size: x} = state
-
-    xs = List.foldr(elems, xs, &[&1 | &2])
-
+    # FIFO, so given xs = [1, 2], elems = [3, 4]
+    # this should be equivalent to enqueue(3), enqueue(4)
+    # which should give us [1,2,3,4], so a simple concat is fine.
+    xs = xs ++ elems
     {:noreply, %{state | size: x + length(elems), queue: xs}}
   end
 
   def handle_cast({:enqueue, elem}, state) do
     %{queue: xs, size: x} = state
-
-    {:noreply, %{state | size: x + 1, queue: Enum.reverse([elem | xs])}}
+    # You could also use List.insert_at(xs, elem, -1)
+    {:noreply, %{state | size: x + 1, queue: xs ++ [elem]}}
   end
 end
diff --git a/test/queue_test.exs b/test/queue_test.exs
index 586185f..f8d2354 100644
--- a/test/queue_test.exs
+++ b/test/queue_test.exs
@@ -81,4 +81,24 @@ defmodule QueueTest do
     assert Queue.flush(pid) == [3, 2, 1]
     assert Queue.size(pid) == 0
   end
+
+  test "enqueue returns correct order" do
+    {:ok, pid} = Queue.start_link()
+
+    assert :ok = Queue.enqueue(pid, [1])
+    assert Queue.size(pid) == 1
+    assert :ok = Queue.enqueue(pid, [2])
+    assert Queue.size(pid) == 2
+    assert :ok = Queue.enqueue(pid, [3])
+    assert Queue.size(pid) == 3
+    assert Queue.flush(pid) == [1, 2, 3]
+    assert Queue.size(pid) == 0
+
+    assert :ok = Queue.enqueue(pid, [1])
+    assert Queue.size(pid) == 1
+    assert :ok = Queue.enqueue(pid, [2, 3])
+    assert Queue.size(pid) == 3
+    assert Queue.flush(pid) == [1, 2, 3]
+    assert Queue.size(pid) == 0
+  end
 end
diff --git a/test/war_test.exs b/test/war_test.exs
index 04d34b8..602857b 100644
--- a/test/war_test.exs
+++ b/test/war_test.exs
@@ -2,9 +2,51 @@ defmodule WarTest do
   use ExUnit.Case
 
   describe "War" do
+    test "minor hand" do
+      deck = [10,10,1,1]
+      expect = [1,1,10,10]
+
+      # :: deal deck
+      # p1      p2
+      # [10, 1] [10, 1]
+      #
+      # :: play turn
+      # p1      p2
+      # [1]     [1]
+      # 10 vs 10 <- even, war
+      #
+      # :: war
+      # p1 size < 3, unable to war,
+      # cards = flush + tied
+      #       = [1] + [10, 10]
+      #       = [1, 10, 10]
+      #
+      # p1     p2   float
+      # []     [1]  [1, 10, 10]
+      #
+      # push_cards(p2, [1, 10, 10])
+      #
+      # :: push cards
+      #
+      # sorted = [1, 10, 10] (where 1 is actually 14)
+      # enqueue p2 sorted
+      #
+      # -> [1, 1, 10, 10]
+      # 1 <- held in hand cause p2 never flushed
+      # 1 <- flush from p2
+      # 10 <- collected tied
+      # 10 <- collected tied
+
+      assert War.deal(deck) == expect
+    end
+
     test "deal_1" do
       t1 = [1,1,1,1,13,13,13,13,11,11,11,11,12,12,12,12,10,10,10,10,9,9,9,9,7,7,7,7,8,8,8,8,6,6,6,6,5,5,5,5,4,4,4,4,3,3,3,3,2,2,2,2]
       r1 = [1,1,1,1,13,13,13,13,12,12,12,12,11,11,11,11,10,10,10,10,9,9,9,9,8,8,8,8,7,7,7,7,6,6,6,6,5,5,5,5,4,4,4,4,3,3,3,3,2,2,2,2]
+      # this test fails, but its possible that the test is incorrect, it
+      # appears that the first 3 cards in the deck are the last 3 cards in the
+      # winners hand, with the rest of the game placed beneath them, which
+      # seems a plausible state given the code and "able to war" checks.
       assert War.deal(t1) == r1
     end

As an aside, using a GenServer for this looks a bit like trying to treat them as OOP Objects, where the data structure should probably be represented by a struct and module with supporting functions. This also lets you use pattern matching when writing your game functions (you cant match on a gen servers state outside of the genserver for example).

This is 100% ok if your simply using it to learn bits of elixir (I am aware the first GenServer tutorial represents a stack) or have a larger abstraction in mind (eg an email send queue probably would be a genserver), but best practices … well, imagine if any List spawned a GenServer to match it, things would get a bit messy!

Hope that helps.

Sebb

Sebb

Do I understand the rules right, that there are no decisions the players can make?

zoedsoupe

zoedsoupe OP

Thank you so much for your comment! It really helped me with the Queue data structure.

So a converted it to a simple module:

defmodule Queue do
  defstruct list: [], size: 0

  def new do
    struct!(__MODULE__)
  end

  def enqueue(%Queue{} = q, elems) when is_list(elems) do
    List.foldl(elems, q, &enq/2)
  end

  def enqueue(%Queue{} = q, elem) do
    enq(elem, q)
  end

  defp enq(elem, %Queue{list: xs, size: x} = q) do
    %{q | list: List.insert_at(xs, -1, elem), size: x + 1}
  end

  def dequeue(%Queue{} = q, n) do
    {elems, q} =
      Enum.reduce(1..n, {[], q}, fn
        _, {elems, state} ->
          {elem, state} = deq(state)
          {[elem | elems], state}
      end)

    {Enum.reverse(elems), q}
  end

  def dequeue(%Queue{} = q) do
    deq(q)
  end

  defp deq(%Queue{list: []} = q) do
    {nil, q}
  end

  defp deq(%Queue{list: xs, size: x} = q) do
    {hd(xs), %{q | list: tl(xs), size: x - 1}}
  end

  def size(%Queue{} = q) do
    q.size
  end

  def front(%Queue{} = q) do
    List.first(q.list)
  end

  def rear(%Queue{} = q) do
    List.last(q.list)
  end

  def flush(%Queue{} = q) do
    {q.list, new()}
  end

  def inspect(%Queue{} = q) do
    q.list
  end
end

and this is my new war.ex implementation:

defmodule War do
  @doc """
  The main module to the challenge.
  This module exposes a deal/1 function to play the game.

  You can run all tests executing `elixir war.ex`.
  """
  require Integer

  # prefers to use a weight as we don't represent the cards
  @ace_weight 14

  def deal(deck) do
    {d1, d2} = deal_deck(deck)

    p1 = Queue.enqueue(Queue.new(), d1)
    p2 = Queue.enqueue(Queue.new(), d2)

    winner = play_game(p1, p2)

    Queue.size(winner)
    |> then(&Queue.dequeue(winner, &1))
    |> then(fn {elems, _q} ->
      Enum.map(elems, &remove_ace_weight/1)
    end)
  end

  defp deal_deck(deck) do
    List.foldr(deck, {[], []}, &deal_player/2)
  end

  defp deal_player(card, {p1, p2}) do
    if length(p1) == length(p2) do
      {[apply_ace_weight(card) | p1], p2}
    else
      {p1, [apply_ace_weight(card) | p2]}
    end
  end

  defp play_game(p1, p2) do
    if winner = maybe_get_winner(p1, p2) do
      winner
    else
      {turn_winner, cards} = play_turn(p1, p2)
      push_cards(turn_winner, cards)
      play_game(p1, p2)
    end
  end

  defp play_turn(p1, p2, x \\ nil, y \\ nil, tied \\ []) do
    {x, p1} = (x && {x, p1}) || Queue.dequeue(p1)
    {y, p2} = (y && {y, p2}) || Queue.dequeue(p1)

    cards = [x, y]

    cond do
      x > y -> {p1, cards ++ tied}
      x < y -> {p2, cards ++ tied}
      x == y -> war(p1, p2, cards ++ tied)
    end
  end

  defp war(p1, p2, tied) do
    cond do
      !able_to_war?(p1) ->
        {cards, p2} = Queue.flush(p2)
        {p2, cards ++ tied}

      !able_to_war?(p2) ->
        {cards, p1} = Queue.flush(p1)
        {p1, cards ++ tied}

      true ->
        {[d1, n1], p1} = Queue.dequeue(p1, 2)
        {[d2, n2], p2} = Queue.dequeue(p2, 2)
        play_turn(p1, p2, n1, n2, tied ++ [d1, d2])
    end
  end

  defp able_to_war?(player) do
    Queue.size(player) > 3
  end

  # The game ends when a player losses all their cards
  # so their Stack is empty
  defp maybe_get_winner(player_1, player_2) do
    cond do
      Queue.size(player_1) == 0 -> player_2
      Queue.size(player_2) == 0 -> player_1
      true -> nil
    end
  end

  defp apply_ace_weight(card) do
    (card == 1 && @ace_weight) || card
  end

  defp remove_ace_weight(card) do
    (card == @ace_weight && 1) || card
  end

  # Cards won from a war needs to be pushed in descending order
  defp push_cards(player, cards) do
    cards = Enum.sort(cards, :desc)

    Queue.enqueue(player, cards)
  end
end

it seems to infinite loop into even decks…

I also thought those test cases are wrong, so I’ll confirm it

Now I’ll try to run manually the program or debug in more depth to try understand the errors

zoedsoupe

zoedsoupe OP

yeah, no decisions

soup

soup

I can see two issues,

One is an easy to make copy-paste mistake in the first two lines of play_turn.

The other is a mistake regarding immutability. (These might read as trick questions, they don’t intend to be mean.)

Consider

x = [1,2,3]
x ++ [4, 5]

What is x?

Consider

p1 = Queue.new()
Queue.enqueue(p1, 1)

What does p1 contain?

Hint

Remember before all our state was maintained in a genserver, but now we must maintain it ourselves.

Hint

Look at push_cards and when you call it, maybe we are losing some state here?

Fixing this might need some broader redesign, but you can make a hack-job fix with case and the ^ pin operator…

Sebb

Sebb

OK, so what when the game is a state where there will never be a winner?

soup

soup

Woke up realizing this wont get you there because the player is modified in the call so you’ll have to find another way.

zoedsoupe

zoedsoupe OP

when both players runs out of cards at the same time

zoedsoupe

zoedsoupe OP

So what I made was to add a key :name to the Queue struct, to identify which queue I’m dealing of.

push_cards/2 returns the final new queue, and then I pattern match its name to know which player was the turn winner!

New queue.ex (only diifs):

defmodule Queue do
  @fields ~w(list size name)a
  @enforce_keys ~w(name)a
  defstruct @fields

  def new(name, list \\ [])

  def new(name, []) do
    struct!(__MODULE__, name: name, list: [], size: 0)
    f
  end

  def new(name, list) do
    struct!(__MODULE__, name: name, list: list, size: length(list))
  end

and the play_game/2:

defp play_game(p1, p2) do
    if winner = maybe_get_winner(p1, p2) do
      winner
    else
      {turn_winner, cards} = play_turn(p1, p2)

      case push_cards(turn_winner, cards) do
        %{name: :player_1} = turn_winner -> play_game(turn_winner, p2)
        %{name: :player_2} = turn_winner -> play_game(p1, turn_winner)
      end
    end
  end

Although I now have a infinite loop haha. At least the Queue seems to be correctly implemented now

Sebb

Sebb

I think its possible that this will end in a loop. Image you only play with four cards:

player_1  player_2 
[1, 2]    [2, 1]    # player 2 get the cards [1, 2]
[2]       [1, 1, 2] # player 1 get the cards [2, 1]
[2, 1]    [1, 2]    # back to start

hmm maybe its possible to find a rule to sort the cards in the pot, which never leads to a loop but still is fair…

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews