Trying to implement War card game in Elixir

That’s a good solution! I like it, although it’s still failing on test cases. I’m trying to understand if those tests could wrong

can you show me those?

Those test cases handle only the winner’s deck, so I modified your code a little

Here’s the code:

defmodule War do
  import Enum

  @trace false

  defp play(decks) do
    round_(:normal, decks, [])
  end

  defp round_(state, {deck_1, deck_2}, pot) do
    if @trace, do: dbg({state, {deck_1, deck_2}, pot})
    round(state, {deck_1, deck_2}, pot)
  end

  defp round(_, {[], []}, _), do: :draw
  defp round(_, {p1, []}, _), do: p1
  defp round(_, {[], p2}, _), do: p2

  defp round(:war, {[c, c1 | d1], [c, c2 | d2]}, pot) do
    round_(:war, {d1, d2}, pot ++ [c, c1, c, c2])
  end

  defp round(:war, {[c11, c12 | d1], [c21, c22 | d2]}, pot) do
    round_(:normal, decks(c11, c21, d1, d2, pot ++ [c11, c12, c21, c22]), [])
  end

  defp round(_, {[c | d1], [c | d2]}, pot) do
    round_(:war, {d1, d2}, pot ++ [c, c])
  end

  defp round(_, {[c1 | d1], [c2 | d2]}, pot) do
    round_(:normal, decks(c1, c2, d1, d2, [c1, c2]), pot)
  end

  defp decks(c1, c2, d1, d2, pot) do
    pot = sort(pot, :desc)
    if c1 > c2, do: {d1 ++ pot, d2}, else: {d1, d2 ++ pot}
  end

    defp apply_ace_weight(card) do
    (card == 1 && 14) || card
  end

  defp remove_ace_weight(card) do
    (card == 14 && 1) || card
  end

  def deal(deck) do
    deck
    |> Enum.map(&apply_ace_weight/1)
    |> split(length(deck) * 2)
    |> play()
    |> case do
      xs when is_list(xs) -> Enum.map(xs, &remove_ace_weight/1)
      r -> r
    end
  end
end

ExUnit.start()

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

And running with elixir war.ex:

  1) test War deal_5 (WarTest)
     war.ex:90
     Assertion with == failed
     code:  assert War.deal(t5) == r5
     left:  [
              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
            ]
     right: [
              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
            ]
     stacktrace:
       war.ex:93: (test)



  2) test War deal_2 (WarTest)
     war.ex:72
     Assertion with == failed
     code:  assert War.deal(t2) == r2
     left:  [
              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
            ]
     right: [
              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
            ]
     stacktrace:
       war.ex:75: (test)



  3) test War deal_3 (WarTest)
     war.ex:78
     Assertion with == failed
     code:  assert War.deal(t3) == r3
     left:  [
              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
            ]
     right: [
              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
            ]
     stacktrace:
       war.ex:81: (test)

.

  4) test War deal_1 (WarTest)
     war.ex:66
     Assertion with == failed
     code:  assert War.deal(t1) == r1
     left:  [
              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
            ]
     right: [
              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
            ]
     stacktrace:
       war.ex:69: (test)



  5) test War deal_4 (WarTest)
     war.ex:84
     Assertion with == failed
     code:  assert War.deal(t4) == r4
     left:  [
              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
            ]
     right: [
              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
            ]
     stacktrace:
       war.ex:87: (test)


Finished in 0.03 seconds (0.03s on load, 0.00s async, 0.00s sync)
7 tests, 5 failures

Where are these tests from?
I do not see anything about an “ace weight” in the requirements.

What I did not consider is that the deck comes in already shuffled.
Its not clear to me whats happening here:

test "deal_4" do
      t4 = [10,11,12,13 ...]
      r4 = [1,1,13,12,9 ....]
      assert War.deal(t4) == r4
    end

what does deal do? It gets one list and returns it shuffled as it seems. How is that useful, how should that be testable?

These tests already exist! They were provided to me

The deal function receives a shuffled deck and returns the winner’s deck.

The ace weight is needed because the provided shuffled deck has aces as 1, but it needs to be the highest rank

OK, great naming… not.

|> split(length(deck) * 2)

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.

I did just split the deck in two, because the alternated dealing does not add randomness. But if you get the deck from the test you have to do it, otherwise this cant succeed.

1 Like

the deck from tests are already shuffled

yes, but feeding play with

iex(1)> [1,2,3,4] |> Enum.split(2)
{[1, 2], [3, 4]}

and

iex(2)> {[1,2,3,4] |> Enum.take_every(2), [1,2,3,4] |> Enum.drop_every(2)}
{[1, 3], [2, 4]}

will yield different results.

yes, of course! the decks needs to be splitter by the second way

1 Like

I tried to dig on these test cases but no success :confused:

how does your deal function look like now?

like that:


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

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

  def deal(deck) do
    {p1, p2} =
      deck
      |> Enum.map(&apply_ace_weight/1)
      |> deal_deck()

    winner = play_game(p1, p2)

    Enum.map(winner, &remove_ace_weight/1)
  end

  def deal_deck(deck) do
    {Enum.take_every(deck, 2), Enum.drop_every(deck, 2)}
  end

  defp play_game(p1, p2, tied \\ [])

  defp play_game([], p2, tied), do: p2 ++ tied
  defp play_game(p1, [], tied), do: p1 ++ tied

  # War, cards tied
  defp play_game([c | xs], [c | ys], tied) do
    cards = Enum.sort([c, c] ++ tied, :desc)
    play_game(xs, ys, cards)
  end

  # Normal game turn
  defp play_game([x | xs], [y | ys], tied) do
    cards = Enum.sort([x, y] ++ tied, :desc)

    if x > y do
      play_game(xs ++ cards, ys)
    else
      play_game(xs, ys ++ cards)
    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
end

Are you sure this matches the requirements given?
did you try to step through this with a really small deck?

Yeah, I will pass through each requirement and show my code.

the full requirement is here:

So let’s dig it:

The game starts with a shuffled deck of cards. The deck will be passed into your program already shuffled
Ok, nothing to do here!

The cards are dealt in an alternating fashion to each player, so that each player has 26 cards.
So, talking about code, the first player will get cards with a odd indexes and player two cards with even indexes, which results in:

 def deal_deck(deck) do
    {Enum.take_every(deck, 2), Enum.drop_every(deck, 2)}
  end

In each round, both players reveal the top card of their pile

defp play_game([x | xs], [y | ys], tied)

The player with the higher card (by rank) wins both cards, placing them at the bottom of their pile

if x > y do
  play_game(xs ++ cards, ys)
else
  play_game(xs, ys ++ cards)
end

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)

# War, cards tied
  defp play_game([c | xs], [c | ys], tied) do
    cards = Enum.sort([c, c] ++ tied, :desc)
    play_game(xs, ys, cards)
  end

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.

defp play_game([], p2, tied), do: p2 ++ tied
defp play_game(p1, [], tied), do: p1 ++ tied

When cards are added to the bottom of a player’s pile, they should be added in decreasing order by rank. That is, first place the highest ranked card on the bottom, then place the next highest ranked card beneath that. This is true of wars as well.
I sort all cards always in descending order by rank, both in war and normal rounds

# War, cards tied
  defp play_game([c | xs], [c | ys], tied) do
    cards = Enum.sort([c, c] ++ tied, :desc)
    play_game(xs, ys, cards)
  end

  # Normal game turn
  defp play_game([x | xs], [y | ys], tied) do
    cards = Enum.sort([x, y] ++ tied, :desc)

    if x > y do
      play_game(xs ++ cards, ys)
    else
      play_game(xs, ys ++ cards)
    end
  end

So it matches all the requirements, so I can’t see why those test cases are failing (considering they are right)

I understood this part differently. Where is your second card?

These face-up cards will be the next two to be compared, so I call play_game/3 again so the next round will firstly be treated as a normal one

If one player wins the war in the first war-turn he will then get the two tied cards from before and the two currently compared. A total of four. The rules explicitly state that it should be six. You are missing two. I may also be wrong, I did this a long time ago. Nearly a week, who can understand code that he wrote a week ago, certainly not me.

1 Like

I had some time this morning to look into this again.

This is a great exercise, as you really have to read the instructions carefully and also have to apply some common knowledge. There was an error in the solution I posted: when there is WAR, the first card is face down, the second is compared.

When getting a shuffled deck and you only have to deal, you have to keep in mind, that the cards are dealt on top of each other (assumed common knowledge). So the last two cards in the deck are those, that are used in the first round (hint: reverse).
Also, it is assumed, that player one is the dealer and so player 2 gets the first card!

Long time ago, I did some work on client card game project, I did something like that.

defmodule War do

def play_game(deck) do
[player1, player2] = deal_cards(deck)

result = play_rounds(player1, player2)
IO.inspect(result)
result

end

def deal_cards(deck) do
deck |> Enum.chunk_every(26)
end

def play_rounds(player1, player2) do
case {player1, player2} do
{[], []} →
{:tie, []}

  {[], player2_deck} ->
    {:player2, player2_deck}

  {player1_deck, []} ->
    {:player1, player1_deck}

  {[card1 | hand1_without_card], [card2 | hand2_without_card]} ->
    case compare_cards(card1, card2) do
      :win ->
        play_rounds(hand1_without_card ++ [card1, card2], hand2_without_card)

      :lose ->
        play_rounds(hand1_without_card, hand2_without_card ++ [card1, card2])

      :tie ->
        play_war(hand1_without_card, hand2_without_card, [card1, card2])
    end
end

end

We assume both cards are at same rank for a WAR.

def compare_cards(rank1, rank2) when rank1 == rank2, do: :tie
def compare_cards(1, ), do: :win
def compare_cards(
, 1), do: :lose
def compare_cards(rank1, rank2) when rank1 > rank2 or rank1 == 1, do: :win
def compare_cards(rank1, rank2) when rank1 < rank2 or rank2 == 1, do: :lose

When both the players have only one card in their hand in war.

def play_war([a], [b], _) when a == b and is_integer(a), do: {:tie, []}
def play_war([a], [b], _) when a > b and is_integer(a), do: :win
def play_war([a], [b], _) when a < b and is_integer(a), do: :lose

When 1 of the player have only one card.

def play_war([a], [b | _], _) when a > b and is_integer(a), do: :win
def play_war([a | _], [b], _) when a < b and is_integer(a), do: :lose

def play_war([], [], ), do: {:tie, []}
def play_war(
, [], _), do: :win
def play_war([], _, _), do: :lose

def play_war(hand1, hand2, pile) do
[face_down_c1, card1 | rest1] = hand1
[face_down_c2, card2 | rest2] = hand2

face_down_cards = [face_down_c1, face_down_c2]

case compare_cards(card1, card2) do
  :win ->
    play_rounds(rest1 ++ pile ++ face_down_cards ++ [card1, card2], rest2)

  :lose ->
    play_rounds(rest1, rest2 ++ pile ++ [card1, card2] ++ face_down_cards)

  :tie ->
    # Again a war.
    play_war(rest1, rest2, pile)
end

end
end

could you please fix your codeblocks?

1 Like