Powered by AppSignal & Oban Pro

Day 02

2022/elixir/day02.livemd

Day 02

Mix.install([
  {:kino, "~> 0.7.0"}
])

Section

area = Kino.Input.textarea("Puzzle Input")
puzzle_input = Kino.Input.read(area)
example_input = """
A Y
B X
C Z
"""
defmodule Game do
  def parse_shape(str) do
    #  A for Rock, B for Paper, and C for Scissors
    #  X for Rock, Y for Paper, and Z for Scissors.
    cond do
      str in ["A", "X"] -> :rock
      str in ["B", "Y"] -> :paper
      str in ["C", "Z"] -> :scissors
      :else -> raise "unknown move"
    end
  end

  def parse_outcome(str) do
    # X means you need to lose
    # Y means you need to end the round in a draw
    # and Z means you need to win
    case str do
      "X" -> :defeat
      "Y" -> :draw
      "Z" -> :victory
    end
  end

  def my_shape_for_outcome(shape, outcome) do
    case {shape, outcome} do
      {_, :draw} -> shape
      {:rock, :victory} -> :paper
      {:rock, :defeat} -> :scissors
      {:scissors, :victory} -> :rock
      {:scissors, :defeat} -> :paper
      {:paper, :victory} -> :scissors
      {:paper, :defeat} -> :rock
    end
  end

  def outcome(your_shape, opponent_shape) do
    # Rock defeats Scissors, Scissors defeats Paper, and Paper defeats Rock.
    case {your_shape, opponent_shape} do
      {:rock, :scissors} -> :victory
      {:scissors, :paper} -> :victory
      {:paper, :rock} -> :victory
      {same, same} -> :draw
      _ -> :defeat
    end
  end

  def outcome_score(outcome) do
    # outcome of the round 
    # (0 if you lost, 3 if the round was a draw, and 6 if you won)
    case outcome do
      :defeat -> 0
      :draw -> 3
      :victory -> 6
    end
  end

  def shape_score(shape) do
    # score for the shape you selected 
    # (1 for Rock, 2 for Paper, and 3 for Scissors)
    case shape do
      :rock -> 1
      :paper -> 2
      :scissors -> 3
    end
  end

  def round_score(your_shape, opponent_shape) do
    outcome = outcome(your_shape, opponent_shape)
    shape_score(your_shape) + outcome_score(outcome)
  end
end
ExUnit.start(autorun: false)

defmodule GameTests do
  use ExUnit.Case, async: true
  import Game

  test "rock game" do
    assert outcome(:rock, :scissors) == :victory
    assert outcome(:rock, :rock) == :draw
    assert outcome(:rock, :paper) == :defeat
  end

  test "scissor game" do
    assert outcome(:scissors, :paper) == :victory
    assert outcome(:scissors, :scissors) == :draw
    assert outcome(:scissors, :rock) == :defeat
  end

  test "paper game" do
    assert outcome(:paper, :rock) == :victory
    assert outcome(:paper, :paper) == :draw
    assert outcome(:paper, :scissors) == :defeat
  end

  test "round score" do
    assert round_score(:paper, :rock) == 8
    assert round_score(:rock, :paper) == 1
    assert round_score(:scissors, :scissors) == 6
  end

  test "derive my shape for outcome" do
    assert my_shape_for_outcome(:rock, :draw) == :rock
    assert my_shape_for_outcome(:paper, :defeat) == :rock
    assert my_shape_for_outcome(:scissors, :victory) == :rock
  end
end

ExUnit.run()
rounds =
  puzzle_input
  |> String.split("\n", trim: true)
  |> Stream.map(&String.split(&1, " "))
# Part One

rounds
|> Stream.map(fn [opponent, me] ->
  Game.round_score(
    Game.parse_shape(me),
    Game.parse_shape(opponent)
  )
end)
|> Enum.sum()
# Part Two

rounds
|> Stream.map(fn [opponent, outcome] ->
  opponent_shape = Game.parse_shape(opponent)

  my_shape =
    Game.my_shape_for_outcome(
      opponent_shape,
      Game.parse_outcome(outcome)
    )

  Game.round_score(
    my_shape,
    opponent_shape
  )
end)
|> Enum.sum()