(in-package :ca.mhcat.advent2022)

;; --- Day 2: Rock Paper Scissors ---

;; The Elves begin to set up camp on the beach. To decide
;; whose tent gets to be closest to the snack storage, a
;; giant Rock Paper Scissors tournament is already in
;; progress.

;; Rock Paper Scissors is a game between two players. Each
;; game contains many rounds; in each round, the players
;; each simultaneously choose one of Rock, Paper, or
;; Scissors using a hand shape. Then, a winner for that
;; round is selected: Rock defeats Scissors, Scissors
;; defeats Paper, and Paper defeats Rock. If both players
;; choose the same shape, the round instead ends in a draw.

;; Appreciative of your help yesterday, one Elf gives you an
;; encrypted strategy guide (your puzzle input) that they
;; say will be sure to help you win. "The first column is
;; what your opponent is going to play: A for Rock, B for
;; Paper, and C for Scissors. The second column--" Suddenly,
;; the Elf is called away to help with someone's tent.

;; The second column, you reason, must be what you should
;; play in response: X for Rock, Y for Paper, and Z for
;; Scissors. Winning every time would be suspicious, so the
;; responses must have been carefully chosen.

;; The winner of the whole tournament is the player with the
;; highest score. Your total score is the sum of your scores
;; for each round. The score for a single round is the score
;; for the shape you selected (1 for Rock, 2 for Paper, and
;; 3 for Scissors) plus the score for the outcome of the
;; round (0 if you lost, 3 if the round was a draw, and 6 if
;; you won).

;; Since you can't be sure if the Elf is trying to help you
;; or trick you, you should calculate the score you would
;; get if you were to follow the strategy guide.

;; For example, suppose you were given the following
;; strategy guide:

;; A Y
;; B X
;; C Z

;; This strategy guide predicts and recommends the following:

;;     In the first round, your opponent will choose Rock
;;     (A), and you should choose Paper (Y). This ends in a
;;     win for you with a score of 8 (2 because you chose
;;     Paper + 6 because you won).

;;     In the second round, your opponent will choose Paper
;;     (B), and you should choose Rock (X). This ends in a
;;     loss for you with a score of 1 (1 + 0).

;;     The third round is a draw with both players choosing
;;     Scissors, giving you a score of 3 + 3 = 6.

;; In this example, if you were to follow the strategy
;; guide, you would get a total score of 15 (8 + 1 + 6).

;; What would your total score be if everything goes exactly
;; according to your strategy guide?

(defparameter day2/test-data
  '((#\A #\Y)
    (#\B #\X)
    (#\C #\Z)))

(defparameter day2/bad-code-table
  '((#\A . :rock)
    (#\B . :paper)
    (#\C . :scissors)
    (#\X . :rock)
    (#\Y . :paper)
    (#\Z . :scissors)))

(defparameter day2/hierarchy
  '((:rock     :scissors)
    (:paper    :rock)
    (:scissors :paper)))

(defparameter day2/item-scores
  '((:rock     . 1)
    (:paper    . 2)
    (:scissors . 3)))

(defparameter day2/outcome-scores
  '((:win  . 6)
    (:draw . 3)
    (:lose . 0)))

(defun day2/parse-strategy (lst code-table)
  (flet ((decode (pair)
           (mapcar (lambda (ch)
                     (cdr (assoc ch code-table)))
                   pair)))
    (loop for pair in lst collect (decode pair))))

(defun day2/play-round (pair)
  (cond
    ((find pair day2/hierarchy :test #'equal) '(:win :lose))
    ((find (reverse pair) day2/hierarchy :test #'equal) '(:lose :win))
    (t '(:draw :draw))))

(defun day2/scores (strategy outcome)
  (flet ((scores (table pair)
           (mapcar (lambda (arg)
                     (cdr (assoc arg table)))
                   pair)))
    (list (scores day2/item-scores strategy)
          (scores day2/outcome-scores outcome))))

(defun day2/sum-scores (scores)
  (list
   (apply #'+ (mapcar #'first scores))
   (apply #'+ (mapcar #'second scores))))

(defun day2/compute-part1 (lst)
  (loop for pair in (day2/parse-strategy lst day2/bad-code-table)
        append (day2/scores pair (day2/play-round pair)) into scores
        finally (return (day2/sum-scores scores))))

(defun day2/load-dataset (fname)
  (loop for line in (load-lines fname)
        collect (remove #\space (coerce line 'list))))

(defun day2/part1 ()
  (day2/compute-part1
   (day2/load-dataset "day2.txt")))

;; --- Part Two ---

;; The Elf finishes helping with the tent and sneaks back
;; over to you. "Anyway, the second column says how the
;; round needs to end: X means you need to lose, Y means you
;; need to end the round in a draw, and Z means you need to
;; win. Good luck!"

;; The total score is still calculated in the same way, but
;; now you need to figure out what shape to choose so the
;; round ends as indicated. The example above now goes like
;; this:

;;     In the first round, your opponent will choose Rock
;;     (A), and you need the round to end in a draw (Y), so
;;     you also choose Rock. This gives you a score of 1 + 3
;;     = 4.

;;     In the second round, your opponent will choose Paper
;;     (B), and you choose Rock so you lose (X) with a score
;;     of 1 + 0 = 1.

;;     In the third round, you will defeat your opponent's
;;     Scissors with Rock for a score of 1 + 6 = 7.

;; Now that you're correctly decrypting the ultra top secret
;; strategy guide, you would get a total score of 12.

;; Following the Elf's instructions for the second column,
;; what would your total score be if everything goes exactly
;; according to your strategy guide?

(defparameter day2/real-code-table
  '((#\A . :rock)
    (#\B . :paper)
    (#\C . :scissors)
    (#\X . :lose)
    (#\Y . :draw)
    (#\Z . :win)))

(defun day2/resolve-strategy (round)
  (destructuring-bind (them outcome) round
    (list
     them
     (case outcome
       (:lose (second (find them day2/hierarchy :key #'car)))
       (:win (second (find them (mapcar #'reverse day2/hierarchy) :key #'car)))
       (otherwise them)))))

(defun day2/compute-part2 (lst)
  (loop for pair in (mapcar #'day2/resolve-strategy
                            (day2/parse-strategy lst day2/real-code-table))
        append (day2/scores pair (day2/play-round pair)) into scores
        finally (return (day2/sum-scores scores))))

(defun day2/part2 ()
  (day2/compute-part2
   (day2/load-dataset "day2.txt")))