src/2017/number-spiral.lisp @ 9312a3a851cc

2021/10
author Steve Losh <steve@stevelosh.com>
date Fri, 10 Dec 2021 18:14:40 -0800
parents 182bdd87fd9e
children (none)
(defpackage :advent/spiral
  (:use :cl :losh :iterate)
  (:export :number-coordinates))

(in-package :advent/spiral)

(defun layer-side-length (layer)
  "Return the length of one side of `layer`."
  (1+ (* 2 layer)))

(defun layer-size (layer)
  "Return the total size of a number spiral with a final layer of `layer`."
  (square (layer-side-length layer)))

(defun layer-for-number (number)
  "Return the index of the layer containing `number`."
  (ceiling (/ (1- (sqrt number)) 2)))

(defun layer-start (layer)
  "Return the smallest number in `layer`."
  (if (zerop layer)
    1
    (1+ (layer-size (1- layer)))))

(defun layer-leg-length (layer)
  "Return the length of one \"leg\" of `layer`."
  (1- (layer-side-length layer)))


(defun leg (layer number)
  "Return the leg index and offset of `number` in `layer`."
  (if (= 1 number)
    (values 0 0)
    (let ((idx (- number (layer-start layer)))
          (legsize (layer-leg-length layer)))
      (values (floor idx legsize)
              (1+ (mod idx legsize))))))

(defun corner-coordinates (layer leg)
  "Return the coordinates of the corner starting `leg` in `layer`.

  Leg | Corner
   0  | Bottom Right
   1  | Top Right
   2  | Top Left
   3  | Bottom Left

  "

  ;; 2   1
  ;;
  ;; 3   0
  (ccase leg
    (0 (complex layer (- layer)))
    (1 (complex layer layer))
    (2 (complex (- layer) layer))
    (3 (complex (- layer) (- layer)))))

(defun leg-direction (leg)
  "Return the direction vector for the given `leg`.
  "
  ;;    <--
  ;;   11110
  ;; | 2   0 ^
  ;; | 2   0 |
  ;; v 2   0 |
  ;;   23333
  ;;    -->
  (ccase leg
    (0 (complex 0 1))
    (1 (complex -1 0))
    (2 (complex 0 -1))
    (3 (complex 1 0))))


(defun number-coordinates (number)
  (nest
    ;; Find the layer the number falls in.
    (let ((layer (layer-for-number number))))

    ;; Find which leg of that layer it's in, and how far along the leg it is.
    (multiple-value-bind (leg offset) (leg layer number))

    ;; Find the coordinates of the leg's corner, and its direction vector.
    (let ((corner (corner-coordinates layer leg))
          (direction (leg-direction leg))))

    ;; Start at the corner and add the offset in the leg's direction to find the
    ;; number's coordinates.
    (+ corner (* direction offset))))