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

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New
unaware8150
Hello folks! So at work, we are seeing some situations where we have to define some “fixed” strings that are used across the codebase in...
New
dillonoconnor
Is there any way to avoid the Hologram compiler running when using iex? It seems like the front-end code could potentially be disregarded...
New
thiagogsr
** (ArgumentError) expected :max_attempts to be a positive integer, got: {:@, [line: 10, column: 19], [{:max_attempts, [line: 10, column:...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
georgeguimaraes
Just published claude-code-elixir, a plugin marketplace for Claude Code with Elixir support. These are the plugins I’ve been using for my...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews