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.
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
I tried to dig on these test cases but no success
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.
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?