Advent of Code 2022: Day 2

Published on

Implementation of play

Here it is for reference:

data Shape = Rock | Paper | Scissors
  deriving (Show)

data Outcome = Win | Lose | Tie
  deriving (Show)

play :: Shape -> Shape -> Outcome
play =
  \cases
    Rock Rock -> Tie
    Rock Paper -> Lose
    Rock Scissors -> Win
    Paper Rock -> Win
    Paper Paper -> Tie
    Paper Scissors -> Lose
    Scissors Rock -> Lose
    Scissors Paper -> Win
    Scissors Scissors -> Tie

This definition is wonderfully obvious, but contains some redundancy. Could it be expressed more concisely? A couple of approaches spring to mind, so let’s try both!

Mechanical approach

A couple of redundancies are obvious:

What does the code look like if we try to encode those rules directly?

play :: Shape -> Shape -> Outcome
play =
  \case
    Rock Scissors -> Win
    Scissors Paper -> Win
    Paper Rock -> Win
    a b
      | a == b -> Tie
      | otherwise -> case play b a of
          Win -> Lose
          Lose -> Win
          Tie -> Tie

This is not great. It is the same number of lines of code, but now contains a potentially dangerous recursive call, and is far less obvious. Notably, the rock-scissors-paper cycle is still not directly modelled. Let’s strive for satisfaction instead, and see where we get to.

Satisfaction-oriented design

I want the definition of play to read like my internal model of the game:

Scissors-beats-paper-beats-rock-beats-scissors, and both players choosing the same shape is a tie.

The cyclical description evokes memories of modular arithmetic… can we use that, somehow? Let us project Shape into the integers modulo 3:

Shape integer (mod 3)
Rock 0
Paper 1
Scissors 2

Now, for the projection of any two shapes s and t:

Aha! With this written out, it becomes obvious that we can also project Outcome into the integers modulo 3:

Outcome integer (mod 3)
Win 1
Tie 0
Lose 2

Now for shape inputs can be directly computed by outcome = challenge - response (mod 3).

What does this look like in Haskell?

data Shape = Rock | Paper | Scissors
  deriving (Enum, Show)

data Outcome = Tie | Win | Lose
  deriving (Enum, Show)

play :: Shape -> Shape -> Outcome
play challenge response =
  toEnum $
    (fromEnum challenge - fromEnum response) `mod` 3

Much more pleasant!