AOC 2022-02 (Rock Paper Scissors)

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

Shared code

defmodule RPS do
  @moves [:rock, :paper, :scissors]
  @size Enum.count(@moves)

  def result(move, move) when move in @moves, do: :tie
  def result(move1, move2) do
    index1 = Enum.find_index(@moves, &(&1 == move1))
    index2 = Enum.find_index(@moves, &(&1 == move2))

    cond do
      index1 == nil || index2 == nil -> raise "Invalid move"
      Integer.mod(index1 - 1, @size) == index2 -> :win
      Integer.mod(index1 + 1, @size) == index2 -> :lose
      true -> raise "Invalid game"
    end
  end

  def select_move_to(:tie, against), do: against

  def select_move_to(intended_status, against) do
    against_index = Enum.find_index(@moves, &(&1 == against))

    case intended_status do
      :win -> Enum.at(@moves, Integer.mod(against_index + 1, @size))
      :lose -> Enum.at(@moves, Integer.mod(against_index - 1, @size))
      _ -> raise "Invalid status"
    end
  end
end

Puzzle Input

input = Kino.Input.textarea("Input")
rounds =
  input
  |> Kino.Input.read()
  |> String.split("\n")
  |> Enum.map(&String.split/1)

Part 1

Score the strategy guide

defmodule Round1 do
  def new([p1, p2]),
    do: %{them: translate_move(p1), me: translate_move(p2), status: nil, score: nil}

  def play(%{me: a, them: b} = game), do: %{game | status: RPS.result(a, b)}

  def score(%{me: move, status: status} = game) do
    %{game | score: points_for(move) + points_for(status)}
  end

  defp translate_move(char) do
    case char do
      "A" -> :rock
      "B" -> :paper
      "C" -> :scissors
      "X" -> :rock
      "Y" -> :paper
      "Z" -> :scissors
      _ -> raise "Invalid move"
    end
  end

  defp points_for(move_or_status) do
    case move_or_status do
      # moves
      :rock -> 1
      :paper -> 2
      :scissors -> 3
      # statuses
      :win -> 6
      :tie -> 3
      :lose -> 0
      _ -> raise "Invalid move or status"
    end
  end
end

Enum.map(rounds, fn round ->
  round
  |> Round1.new()
  |> Round1.play()
  |> Round1.score()
  |> Map.get(:score)
end)
|> Enum.sum()

Part 2

Fix the logic in the strategy guide and score it again.

defmodule Round2 do
  def new([p1, p2]),
    do: %{them: translate_move(p1), me: nil, status: translate_intended_status(p2), score: -1}

  def play(%{them: a, status: b} = game), do: %{game | me: RPS.select_move_to(b, a)}

  def score(%{me: move, status: status} = game) do
    %{game | score: points_for(move) + points_for(status)}
  end

  defp translate_move(char) do
    case char do
      "A" -> :rock
      "B" -> :paper
      "C" -> :scissors
      _ -> raise "Invalid move"
    end
  end

  defp translate_intended_status(char) do
    case char do
      "X" -> :lose
      "Y" -> :tie
      "Z" -> :win
      _ -> raise "Invalid intended status"
    end
  end

  defp points_for(move_or_status) do
    case move_or_status do
      # moves
      :rock -> 1
      :paper -> 2
      :scissors -> 3
      # statuses
      :win -> 6
      :tie -> 3
      :lose -> 0
      _ -> raise "Invalid move or status"
    end
  end
end

Enum.map(rounds, fn round ->
  round
  |> Round2.new()
  |> Round2.play()
  |> Round2.score()
  |> Map.get(:score)
end)
|> Enum.sum()