Skip to content

Instantly share code, notes, and snippets.

@lispm
Last active December 31, 2022 12:31
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lispm/4ed43b1e226ccbf19065b35136f632ce to your computer and use it in GitHub Desktop.
Save lispm/4ed43b1e226ccbf19065b35136f632ce to your computer and use it in GitHub Desktop.
;;; -*- Syntax: ANSI-Common-Lisp; Package: CL-USER -*-
;;; Author: Rainer Joswig, joswig@lisp.de, 2022
;;; This code is written in portable Common Lisp.
; https://adventofcode.com/2022/day/10
;; This solution makes use of multiple dispatch and standard method combinations of CLOS.
(defparameter *input-10*
(if (member :lispm *features*)
(pathname "rjmbp:/Users/joswig/Lisp/aoc2022/input-10.text")
(pathname "/Users/joswig/Lisp/aoc2022/input-10.text"))
"The provided input file with the description of the stacks
and the list of moves.")
;;; ================================================================
;;; Reading a program
; a program looks like:
; addx 13
; addx 4
; noop
; addx -1
; addx 5
; there are only two possible instructions: addx and noop.
; in this program an instruction is a list with akeyword symbol and its arg.
(defun read-program (file-or-stream)
"Returns a vector of instructions. Each instruction is a list.
The instruction name is a keyword symbol."
(if (or (stringp file-or-stream) (pathnamep file-or-stream))
(with-open-file (stream file-or-stream)
(read-program stream))
(let ((*read-eval* nil)
(*package* (find-package "KEYWORD")))
(coerce
(loop for instruction = (read file-or-stream nil nil)
while instruction
collect (ecase instruction
((:noop) (list instruction))
((:addx) (list instruction (read file-or-stream nil nil)))))
'vector))))
;;; ================================================================
;;; The CPU10
(defun instruction-cycles (instruction)
"Returns the number of CPU cycles for each instruction."
(ecase instruction
((:addx) 2)
((:noop) 1)))
; The class for our CPU, which holds the various CPU related values, including the X register.
(defclass cpu10 ()
((trace :initform nil :accessor cpu-trace-p :initarg :trace )
(program :initform #() :accessor cpu-program :initarg :program :type vector)
(instruction-pointer :initform 0 :accessor cpu-instruction-pointer :type integer)
(instruction-cycles :initform 0 :accessor cpu-instruction-cycles :type integer)
(register-x :initform 1 :accessor cpu-register-x :type integer)
(cycle :initform 0 :accessor cpu-cycle :type integer)
(cycle-state :initform :halt :accessor cpu-cycle-state :type symbol)
(result-sum :initform 0 :accessor cpu-result-sum :type integer))
(:documentation "The general CPU class for this pauzzle"))
(defclass cpu10a (cpu10)
((result-sum :initform 0 :accessor cpu-result-sum :type integer))
(:documentation "The specific CPU class for part 1 of this puzzle"))
;; About above Slots:
;
; instruction-pointer is an integer pointing into the program vector
; instruction-cycles is the number of remaining cycles to finish the instruction
; cycle is the current cycle count
; cycle state is one of :halt, :start, :during, :end
(defun show-cpu (cpu)
"Prints a rough overview of the CPU state."
(with-slots (cycle cycle-state instruction-pointer instruction-cycles register-x)
cpu
(print (list :cycle cycle
:cycle-state cycle-state
:instruction-pointer instruction-pointer
:instruction-cycles instruction-cycles
:register-x register-x))))
(defmethod cpu-instruction ((cpu cpu10))
"Returns the current CPU instruction. A keyword symbol."
(let ((instruction+args (aref (cpu-program cpu)
(cpu-instruction-pointer cpu))))
(values (first instruction+args) (rest instruction+args))))
(defmethod cpu-instruction-args ((cpu cpu10))
"Returns the args for the current CPU instruction."
(nth-value 1 (cpu-instruction cpu)))
;;; ================================================================
;;; Executing the instructions
;; start
(defmethod do-cycle ((cpu cpu10) (current-state (eql :start)) instruction)
"The start of the cycle for all instructions."
(incf (cpu-cycle cpu))
(setf (cpu-cycle-state cpu) :during)
(when (zerop (cpu-instruction-cycles cpu))
(setf (cpu-instruction-cycles cpu) (instruction-cycles instruction)))
cpu)
;; during
(defmethod do-cycle ((cpu cpu10) (current-state (eql :during)) (instruction (eql :noop)))
"During the NOOP instruction."
(setf (cpu-instruction-cycles cpu) 0)
(setf (cpu-cycle-state cpu) :end)
cpu)
(defmethod do-cycle ((cpu cpu10) (current-state (eql :during)) (instruction (eql :addx)))
"During the ADDX instruction"
(decf (cpu-instruction-cycles cpu))
(setf (cpu-cycle-state cpu) :end)
cpu)
;; DURING method for part 1 of the puzzle, computing the sum of signal strengths
(defmethod do-cycle :around ((cpu cpu10a) (current-state (eql :during)) instruction)
"During each instruction we calculate the signal strength, if necessary."
(declare (ignore instruction))
(when (zerop (mod (- (cpu-cycle cpu) 20) 40))
(incf (cpu-result-sum cpu) (* (cpu-cycle cpu) (cpu-register-x cpu))))
(call-next-method)
cpu)
;; end
(defmethod do-cycle ((cpu cpu10) (current-state (eql :end)) (instruction (eql :noop)))
"The end of the NOOP instruction cycle."
(incf (cpu-instruction-pointer cpu))
cpu)
(defmethod do-cycle ((cpu cpu10) (current-state (eql :end)) (instruction (eql :addx)))
"The end of the ADDX instruction cycle 1 or 2."
(when (eql (cpu-instruction-cycles cpu) 0)
(incf (cpu-register-x cpu) (first (cpu-instruction-args cpu)))
(incf (cpu-instruction-pointer cpu)))
cpu)
(defmethod do-cycle :after ((cpu cpu10) (current-state (eql :end)) instruction)
"The end of each instruction cycle. Halts the CPU at the end of the program.
Otherwise starts a new cycle."
(declare (ignore instruction))
(cond ((>= (cpu-instruction-pointer cpu) (1- (length (cpu-program cpu))))
(setf (cpu-cycle-state cpu) :halt))
(t
(setf (cpu-cycle-state cpu) :start)))
cpu)
;;; ================================================================
;;; Running the CPU
(defun run-cpu (cpu)
"Calls the instruction dispatcher for each cycle."
(loop while (not (eql (cpu-cycle-state cpu) :halt)) do
(when (cpu-trace-p cpu)
(show-cpu cpu))
(do-cycle cpu
(cpu-cycle-state cpu)
(cpu-instruction cpu)))
cpu)
;;; ================================================================
;;; Part 2
; The CPU class for part 2. We reuse/inherit part 1.
; Additionally this CPU has a frame buffer of size 40x6.
(defclass cpu10b (cpu10)
((frame-buffer
:initform (make-array (list 40 6) :initial-element #\space)
:accessor cpu-frame-buffer)))
(defmethod do-cycle :after ((cpu cpu10b) (current-state (eql :during)) instruction)
"During each cycle we draw into the frame buffer."
(declare (ignore instruction))
(multiple-value-bind (y x)
(truncate (1- (cpu-cycle cpu)) 40)
(setf (aref (cpu-frame-buffer cpu) x y)
(if (<= (1- (cpu-register-x cpu)) x (1+ (cpu-register-x cpu))) #\# #\.))))
(defun draw-frame-buffer (cpu)
(terpri)
(let ((fb (cpu-frame-buffer cpu)))
(loop for y below 6
do (loop for x below 40
do (write-char (aref fb x y)))
(terpri))))
;;; ================================================================
;;; Solving AOC 2022, Day 10 part 1 and 2
(defun solve-aoc2022-10 (trace-p file cpu-class)
(let* ((program (read-program file))
(cpu (make-instance cpu-class
:program program
:trace trace-p)))
(setf (cpu-cycle-state cpu) :start)
(run-cpu cpu)
cpu))
(defun aoc2022-10 (&optional trace-p (file *input-10*))
(print (cpu-result-sum (solve-aoc2022-10 trace-p file 'cpu10a)))
(draw-frame-buffer (solve-aoc2022-10 trace-p file 'cpu10b)))
; Example Call:
;
; (aoc2022-10)
;;; ================================================================
;;; End of File
@lispm
Copy link
Author

lispm commented Dec 30, 2022

Bildschirm­foto 2022-12-30 um 21 36 20

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment