| 1 |
(in-package :geometry) |
|---|
| 2 |
|
|---|
| 3 |
;; a point in this package is represented |
|---|
| 4 |
;; as a list (x y) |
|---|
| 5 |
|
|---|
| 6 |
;; a rectangle is represented |
|---|
| 7 |
;; as a list (left top width height) |
|---|
| 8 |
|
|---|
| 9 |
(defmacro with-point (point &body body) |
|---|
| 10 |
(let* ((*package* (symbol-package point)) |
|---|
| 11 |
(x (intern (format nil "~A-X" (symbol-name point)))) |
|---|
| 12 |
(y (intern (format nil "~A-Y" (symbol-name point))))) |
|---|
| 13 |
`(destructuring-bind (,x ,y) ,point |
|---|
| 14 |
,@body))) |
|---|
| 15 |
|
|---|
| 16 |
(defmacro with-points ((&rest points) &body body) |
|---|
| 17 |
(if (null points) |
|---|
| 18 |
`(progn ,@body) |
|---|
| 19 |
`(with-point ,(car points) |
|---|
| 20 |
(with-points (,@(cdr points)) |
|---|
| 21 |
,@body)))) |
|---|
| 22 |
|
|---|
| 23 |
(defmacro with-rectangle (rectangle-or-options &body body) |
|---|
| 24 |
(destructuring-bind (rectangle &key suffix) (ensure-list rectangle-or-options) |
|---|
| 25 |
(flet ((add-suffix (symbol) |
|---|
| 26 |
(if suffix |
|---|
| 27 |
(intern (format nil "~a-~a" (symbol-name symbol) (string-upcase suffix))) |
|---|
| 28 |
(intern (symbol-name symbol))))) |
|---|
| 29 |
`(destructuring-bind (,(add-suffix 'left) |
|---|
| 30 |
,(add-suffix 'top) |
|---|
| 31 |
,(add-suffix 'width) |
|---|
| 32 |
,(add-suffix 'height)) |
|---|
| 33 |
,rectangle |
|---|
| 34 |
,@body)))) |
|---|
| 35 |
|
|---|
| 36 |
(defun distance (point-a point-b) |
|---|
| 37 |
(with-points (point-a point-b) |
|---|
| 38 |
(sqrt (+ (expt (- point-a-x point-b-x) 2) |
|---|
| 39 |
(expt (- point-a-y point-b-y) 2))))) |
|---|
| 40 |
|
|---|
| 41 |
(defmacro dorect ((point (left top width height) &key test row-change) &body body) |
|---|
| 42 |
"Iterate with POINT over all points in rect row per row. The list |
|---|
| 43 |
containing x and y is intended for only extracting those |
|---|
| 44 |
and not to be stored away (it will be modified). |
|---|
| 45 |
|
|---|
| 46 |
BODY is only executed, if TEST of the current point is true. |
|---|
| 47 |
|
|---|
| 48 |
For convenience, a null arg function ROW-CHANGE can be given |
|---|
| 49 |
that will be called between the rows." |
|---|
| 50 |
(check-type point symbol) |
|---|
| 51 |
(rebinding (left top) |
|---|
| 52 |
`(iter |
|---|
| 53 |
(with ,point = (list nil nil)) |
|---|
| 54 |
(for y from ,top to (1- (+ ,top ,height))) |
|---|
| 55 |
,(when row-change |
|---|
| 56 |
`(unless (first-time-p) |
|---|
| 57 |
(funcall ,row-change))) |
|---|
| 58 |
(iter |
|---|
| 59 |
(for x from ,left to (1- (+ ,left ,width))) |
|---|
| 60 |
(setf (first ,point) x |
|---|
| 61 |
(second ,point) y) |
|---|
| 62 |
(when ,(if test |
|---|
| 63 |
`(funcall ,test ,point) |
|---|
| 64 |
t) |
|---|
| 65 |
,@body))))) |
|---|
| 66 |
|
|---|
| 67 |
(defun rectangle-center (rectangle &key roundp) |
|---|
| 68 |
(with-rectangle rectangle |
|---|
| 69 |
(let ((x (+ left (/ width 2))) |
|---|
| 70 |
(y (+ top (/ height 2)))) |
|---|
| 71 |
(if roundp |
|---|
| 72 |
(list (round x) (round y)) |
|---|
| 73 |
(list x y))))) |
|---|
| 74 |
|
|---|
| 75 |
(defun rectangle-intersects-p (a b) |
|---|
| 76 |
(with-rectangle (a :suffix a) |
|---|
| 77 |
(with-rectangle (b :suffix b) |
|---|
| 78 |
(let* ((right-a (+ left-a width-a)) |
|---|
| 79 |
(bottom-a (+ top-a height-a)) |
|---|
| 80 |
(right-b (+ left-b width-b)) |
|---|
| 81 |
(bottom-b (+ top-b height-b)) |
|---|
| 82 |
(left (max left-a left-b)) |
|---|
| 83 |
(top (max top-a top-b)) |
|---|
| 84 |
(right (min right-a right-b)) |
|---|
| 85 |
(bottom (min bottom-a bottom-b))) |
|---|
| 86 |
(and (> right left) (> bottom top)))))) |
|---|
| 87 |
|
|---|
| 88 |
;; maybe change this function to take a |
|---|
| 89 |
;; point as an argument? |
|---|
| 90 |
(defun point-in-polygon-p (x y polygon) |
|---|
| 91 |
(let (result |
|---|
| 92 |
(py y)) |
|---|
| 93 |
(loop with (pjx . pjy) = (aref polygon (1- (length polygon))) |
|---|
| 94 |
for (pix . piy) across polygon |
|---|
| 95 |
when (and (or (and (<= piy py) (< py pjy)) |
|---|
| 96 |
(and (<= pjy py) (< py piy))) |
|---|
| 97 |
(< x |
|---|
| 98 |
(+ (/ (* (- pjx pix) (- py piy)) |
|---|
| 99 |
(- pjy piy)) |
|---|
| 100 |
pix))) |
|---|
| 101 |
do (setf result (not result)) |
|---|
| 102 |
do (setf pjx pix |
|---|
| 103 |
pjy piy)) |
|---|
| 104 |
result)) |
|---|
| 105 |
|
|---|
| 106 |
(defun point-in-circle-p (point center radius) |
|---|
| 107 |
(<= (distance point center) radius)) |
|---|
| 108 |
|
|---|
| 109 |
(defun point-in-rect-p (point rectangle) |
|---|
| 110 |
(with-point point |
|---|
| 111 |
(with-rectangle rectangle |
|---|
| 112 |
(and (<= left point-x) |
|---|
| 113 |
(< point-x (+ left width)) |
|---|
| 114 |
(<= top point-y) |
|---|
| 115 |
(< point-y (+ top height)))))) |
|---|
| 116 |
|
|---|
| 117 |
;;; for fun... |
|---|
| 118 |
;; (defun point-in-circle-p-test () |
|---|
| 119 |
;; (let ((center (list 4 4))) |
|---|
| 120 |
;; (dorect (p (0 0 10 10) :row-change #'terpri) |
|---|
| 121 |
;; (if (point-in-circle-p p center 3) |
|---|
| 122 |
;; (princ "x") |
|---|
| 123 |
;; (princ "."))))) |
|---|
| 124 |
|
|---|
| 125 |
(defun bounding-box (objects &key (key #'identity)) |
|---|
| 126 |
(let (min-x min-y max-x max-y) |
|---|
| 127 |
(dolist (obj objects) |
|---|
| 128 |
(let ((point (funcall key obj))) |
|---|
| 129 |
(with-point point |
|---|
| 130 |
(setf min-x (min point-x (or min-x point-x))) |
|---|
| 131 |
(setf min-y (min point-y (or min-y point-y))) |
|---|
| 132 |
(setf max-x (max point-x (or max-x point-x))) |
|---|
| 133 |
(setf max-y (max point-y (or max-y point-y)))))) |
|---|
| 134 |
(list min-x min-y (1+ (- max-x min-x)) (1+ (- max-y min-y))))) |
|---|
| 135 |
|
|---|
| 136 |
(defmacro with-bounding-box-collect ((collect) &body body) |
|---|
| 137 |
`(let (min-x min-y max-x max-y) |
|---|
| 138 |
(flet ((,collect (point) |
|---|
| 139 |
(with-point point |
|---|
| 140 |
(setf min-x (min point-x (or min-x point-x))) |
|---|
| 141 |
(setf min-y (min point-y (or min-y point-y))) |
|---|
| 142 |
(setf max-x (max point-x (or max-x point-x))) |
|---|
| 143 |
(setf max-y (max point-y (or max-y point-y)))))) |
|---|
| 144 |
,@body) |
|---|
| 145 |
(when min-x |
|---|
| 146 |
(list min-x min-y (1+ (- max-x min-x)) (1+ (- max-y min-y)))))) |
|---|
| 147 |
|
|---|
| 148 |
;;; directions |
|---|
| 149 |
|
|---|
| 150 |
;; A direction can be represented either |
|---|
| 151 |
;; as one of the symbols: |
|---|
| 152 |
;; :down, :left, :right, :up |
|---|
| 153 |
;; |
|---|
| 154 |
;; or as a list of dx and dy |
|---|
| 155 |
;; which can be used to move from one |
|---|
| 156 |
;; point to another in that direction |
|---|
| 157 |
;; |
|---|
| 158 |
;; the mapping is as follows: |
|---|
| 159 |
;; |
|---|
| 160 |
;; dx dy symbol |
|---|
| 161 |
;; -- -- ----- |
|---|
| 162 |
;; 0 1 :down |
|---|
| 163 |
;; -1 0 :left |
|---|
| 164 |
;; 1 0 :right |
|---|
| 165 |
;; 0 -1 :up |
|---|
| 166 |
;; |
|---|
| 167 |
|
|---|
| 168 |
(defmethod turn-right ((direction symbol)) |
|---|
| 169 |
(case direction |
|---|
| 170 |
(:down :left) |
|---|
| 171 |
(:left :up) |
|---|
| 172 |
(:up :right) |
|---|
| 173 |
(:right :down))) |
|---|
| 174 |
|
|---|
| 175 |
(defmethod turn-right ((direction list)) |
|---|
| 176 |
(direction-as-list (turn-right (direction-as-symbol direction)))) |
|---|
| 177 |
|
|---|
| 178 |
(defmethod turn-left ((direction symbol)) |
|---|
| 179 |
(case direction |
|---|
| 180 |
(:down :right) |
|---|
| 181 |
(:right :up) |
|---|
| 182 |
(:up :left) |
|---|
| 183 |
(:left :down))) |
|---|
| 184 |
|
|---|
| 185 |
(defmethod turn-left ((direction list)) |
|---|
| 186 |
(direction-as-list (turn-left (direction-as-symbol direction)))) |
|---|
| 187 |
|
|---|
| 188 |
(defmethod direction-as-symbol ((direction symbol)) |
|---|
| 189 |
direction) |
|---|
| 190 |
|
|---|
| 191 |
(defmethod direction-as-symbol ((direction list)) |
|---|
| 192 |
(arnesi:switch (direction :test #'equal) |
|---|
| 193 |
(((0 1)) :down) |
|---|
| 194 |
(((-1 0)) :left) |
|---|
| 195 |
(((1 0)) :right) |
|---|
| 196 |
(((0 -1)) :up))) |
|---|
| 197 |
|
|---|
| 198 |
(defmethod direction-as-list ((direction list)) |
|---|
| 199 |
direction) |
|---|
| 200 |
|
|---|
| 201 |
(defmethod direction-as-list ((direction symbol)) |
|---|
| 202 |
(case direction |
|---|
| 203 |
(:down '(0 1)) |
|---|
| 204 |
(:left '(-1 0)) |
|---|
| 205 |
(:right '(1 0)) |
|---|
| 206 |
(:up '(0 -1)))) |
|---|
| 207 |
|
|---|
| 208 |
(defmethod move ((point list) direction) |
|---|
| 209 |
(destructuring-bind (x y) |
|---|
| 210 |
point |
|---|
| 211 |
(destructuring-bind (dx dy) |
|---|
| 212 |
(direction-as-list direction) |
|---|
| 213 |
(list (+ x dx) |
|---|
| 214 |
(+ y dy))))) |
|---|
| 215 |
|
|---|
| 216 |
;;; TODO add eql for directions ? |
|---|
| 217 |
|
|---|
| 218 |
(defun find-boundary-point (point in-region-p &optional (direction :up)) |
|---|
| 219 |
(let* ((direction (direction-as-list direction)) |
|---|
| 220 |
(next (move point direction))) |
|---|
| 221 |
(if (funcall in-region-p next) |
|---|
| 222 |
(find-boundary-point next in-region-p) |
|---|
| 223 |
point))) |
|---|
| 224 |
|
|---|
| 225 |
;;; region-to-polygon |
|---|
| 226 |
(defun region-to-polygon (point in-region-p) |
|---|
| 227 |
"Will return a closed path of points in mathematical order. |
|---|
| 228 |
IN-REGION-P is a predicate that takes a point as an argument. |
|---|
| 229 |
It defines the region whose bounding polygon is to be found." |
|---|
| 230 |
(let ((polygon) |
|---|
| 231 |
(count 0) |
|---|
| 232 |
(boundary-point (find-boundary-point point in-region-p :up)) |
|---|
| 233 |
(initial-direction :left)) |
|---|
| 234 |
(labels ((neighbour (point direction) |
|---|
| 235 |
"Validate the NEIGHBOUR of POINT in DIRECTION, |
|---|
| 236 |
if it is part of the region, returns (NEIGHBOUR DIRECTION), |
|---|
| 237 |
otherwise returns NIL." |
|---|
| 238 |
(when point |
|---|
| 239 |
(let ((neighbour (move point direction))) |
|---|
| 240 |
(when (funcall in-region-p neighbour) |
|---|
| 241 |
(list neighbour direction))))) |
|---|
| 242 |
(choose-next (point direction) |
|---|
| 243 |
"Returns a place to move to next as a list (NEXT-POINT NEXT-DIRECTION). |
|---|
| 244 |
NEXT-POINT can be the same POINT (but then with a different direction." |
|---|
| 245 |
(acond |
|---|
| 246 |
((neighbour point (turn-right direction)) it) |
|---|
| 247 |
((neighbour (first (neighbour point direction)) |
|---|
| 248 |
(turn-right direction)) |
|---|
| 249 |
it) |
|---|
| 250 |
((neighbour point direction) it) |
|---|
| 251 |
(t (list point (turn-left direction))))) |
|---|
| 252 |
(terminate (point direction) |
|---|
| 253 |
"Are we done?" |
|---|
| 254 |
(when (and (eql direction initial-direction) |
|---|
| 255 |
(equal point boundary-point)) |
|---|
| 256 |
(incf count) |
|---|
| 257 |
(= 2 count))) |
|---|
| 258 |
(push-point (point direction) |
|---|
| 259 |
"Add a point to POLYGON. The actual point |
|---|
| 260 |
depends on the DIRECTION." |
|---|
| 261 |
(push |
|---|
| 262 |
(case direction |
|---|
| 263 |
(:left point) |
|---|
| 264 |
(:down (move point :down)) |
|---|
| 265 |
(:right (move (move point :down) :right)) |
|---|
| 266 |
(:up (move point :right))) |
|---|
| 267 |
polygon)) |
|---|
| 268 |
(traverse (point direction) |
|---|
| 269 |
"Go to next POINT by DIRECTION." |
|---|
| 270 |
(push-point point direction) |
|---|
| 271 |
(unless (terminate point direction) |
|---|
| 272 |
(destructuring-bind (next-point next-direction) |
|---|
| 273 |
(choose-next point direction) |
|---|
| 274 |
(traverse next-point next-direction))))) |
|---|
| 275 |
(traverse boundary-point initial-direction) |
|---|
| 276 |
(nreverse polygon)))) |
|---|
| 277 |
|
|---|
| 278 |
|
|---|
| 279 |
;;; formatting |
|---|
| 280 |
;; proposed by Michael Weber on alexandria-devel |
|---|
| 281 |
(defun format-mixed-radix-number (stream number radix-list format-list |
|---|
| 282 |
&key lsb-first leading-zeros |
|---|
| 283 |
(trailing-zeros t)) |
|---|
| 284 |
"Prints NUMBER to STREAM in mixed-radix RADIX. |
|---|
| 285 |
representation-LIST is a list of radixes, least-significant first. |
|---|
| 286 |
FORMAT-LIST is a list of format directives, one for each digit. |
|---|
| 287 |
When LSB-FIRST is nil (default), print most-significant digit first, |
|---|
| 288 |
otherwise least-significant digit first. |
|---|
| 289 |
When LEADING-ZEROS and TRAILING-ZEROS are nil, leading and |
|---|
| 290 |
trailing zero digits are not printed, respectively. \(default: remove |
|---|
| 291 |
leading zeros, keep trailing zeros)" |
|---|
| 292 |
(let ((format-pairs |
|---|
| 293 |
(loop with digit and fraction |
|---|
| 294 |
initially (setf (values number fraction) |
|---|
| 295 |
(truncate number)) |
|---|
| 296 |
for f-list on format-list |
|---|
| 297 |
and r-list = radix-list then (rest r-list) |
|---|
| 298 |
collect (list (first f-list) |
|---|
| 299 |
(cond ((endp r-list) |
|---|
| 300 |
(shiftf number 0)) |
|---|
| 301 |
((rest f-list) |
|---|
| 302 |
(setf (values number digit) |
|---|
| 303 |
(truncate number (first r-list))) |
|---|
| 304 |
digit) |
|---|
| 305 |
(t number))) |
|---|
| 306 |
into list |
|---|
| 307 |
finally (progn |
|---|
| 308 |
(incf (cadar list) fraction) |
|---|
| 309 |
(return (nreverse list)))))) |
|---|
| 310 |
(unless trailing-zeros |
|---|
| 311 |
(setf format-pairs (member-if #'plusp format-pairs :key |
|---|
| 312 |
#'second))) |
|---|
| 313 |
(when lsb-first |
|---|
| 314 |
(setf format-pairs (nreverse format-pairs))) |
|---|
| 315 |
(unless leading-zeros |
|---|
| 316 |
(setf format-pairs (member-if #'plusp format-pairs :key |
|---|
| 317 |
#'second))) |
|---|
| 318 |
(format stream "~{~{~@?~}~}" format-pairs))) |
|---|
| 319 |
|
|---|
| 320 |
|
|---|
| 321 |
(defun format-decimal-degree (degree) |
|---|
| 322 |
(format-mixed-radix-number nil (* 60 60 degree) '(60 60 360) '("~,2FŽŽ" "~DŽ" "~D°"))) |
|---|
| 323 |
|
|---|
| 324 |
(defun format-lon-lat (stream lon lat) |
|---|
| 325 |
(format stream "~A~:[S~;N~], ~A~:[W~;E~]" |
|---|
| 326 |
(format-decimal-degree (abs lat)) |
|---|
| 327 |
(plusp lat) |
|---|
| 328 |
(format-decimal-degree (abs lon)) |
|---|
| 329 |
(plusp lon))) |
|---|
| 330 |
|
|---|
| 331 |
;;; publish - subscribe on rectangles |
|---|
| 332 |
|
|---|
| 333 |
;;; rect-publisher |
|---|
| 334 |
(defvar *rect-publisher*) |
|---|
| 335 |
|
|---|
| 336 |
(defun make-rect-publisher () |
|---|
| 337 |
"MAKE-RECT-PUBLISHER creates a new publisher object." |
|---|
| 338 |
(setf *rect-publisher* (%make-rect-publisher))) |
|---|
| 339 |
|
|---|
| 340 |
(defstruct (rect-publisher (:constructor %make-rect-publisher)) |
|---|
| 341 |
subscribers) |
|---|
| 342 |
|
|---|
| 343 |
(defstruct rect-subscriber |
|---|
| 344 |
object rectangle callback-fn) |
|---|
| 345 |
|
|---|
| 346 |
(defun register-rect-subscriber (publisher subscriber rectangle callback-fn) |
|---|
| 347 |
"Register SUBSCRIBER with associated RECTANGLE and CALLBACK-FN with |
|---|
| 348 |
PUBLISHER, so that on changes in RECTANGLE, CALLBACK-FN will be called |
|---|
| 349 |
with SUBSCRIBER and the published INFO as additional args." |
|---|
| 350 |
(remove-rect-subscriber publisher subscriber) |
|---|
| 351 |
(push (make-rect-subscriber :object subscriber :rectangle (copy-list rectangle) :callback-fn callback-fn) |
|---|
| 352 |
(rect-publisher-subscribers publisher)) |
|---|
| 353 |
subscriber) |
|---|
| 354 |
|
|---|
| 355 |
(defun remove-rect-subscriber (publisher subscriber) |
|---|
| 356 |
"Unsubscribes SUBSCRIBER from PUBLISHER." |
|---|
| 357 |
(setf (rect-publisher-subscribers publisher) |
|---|
| 358 |
(delete subscriber (rect-publisher-subscribers publisher) |
|---|
| 359 |
:key #'rect-subscriber-object))) |
|---|
| 360 |
|
|---|
| 361 |
(defun publish-rect-change (publisher rectangle &rest info) |
|---|
| 362 |
"Tells PUBLISHER about changes in RECTANGLE. All subscribers whose |
|---|
| 363 |
own rectangle intersects with RECTANGLE will be notified. The kind of |
|---|
| 364 |
change can be further specified by INFO." |
|---|
| 365 |
(dolist (subscriber (rect-publisher-subscribers publisher)) |
|---|
| 366 |
(when (rectangle-intersects-p rectangle (rect-subscriber-rectangle subscriber)) |
|---|
| 367 |
;; (print (rect-subscriber-callback-fn subscriber)) |
|---|
| 368 |
(apply (rect-subscriber-callback-fn subscriber) (rect-subscriber-object subscriber) info)))) |
|---|
| 369 |
|
|---|
| 370 |
|
|---|
| 371 |
(in-package :screamer-user) |
|---|
| 372 |
|
|---|
| 373 |
(export 'largest-rectangle) |
|---|
| 374 |
(defun largest-rectangle (bounding-rectangle in-region-p) |
|---|
| 375 |
"Returns the largest rectangle inside a region (a polygon), which is |
|---|
| 376 |
specified here by its BOUNDING-RECTANGLE and the predicate IN-REGION-P |
|---|
| 377 |
that will be called with two arguments X and Y to determine if a given |
|---|
| 378 |
point belongs to the region or not." |
|---|
| 379 |
(destructuring-bind (l tt w h) |
|---|
| 380 |
bounding-rectangle |
|---|
| 381 |
(let ((left (an-integer-betweenv l (1- (+ l w)) 'left)) |
|---|
| 382 |
(top (an-integer-betweenv tt (1- (+ tt h)) 'top)) |
|---|
| 383 |
(width (an-integer-betweenv 1 w 'width)) |
|---|
| 384 |
(height (an-integer-betweenv 1 h 'height)) |
|---|
| 385 |
(right (an-integer-betweenv (1+ l) (+ l w) 'right)) |
|---|
| 386 |
(bottom (an-integer-betweenv (1+ tt) (+ tt h) 'bottom)) |
|---|
| 387 |
(area (an-integer-betweenv 1 (* w h) 'area))) |
|---|
| 388 |
(assert! (=v width (-v right left))) |
|---|
| 389 |
(assert! (=v height (-v bottom top))) |
|---|
| 390 |
(assert! (=v area (*v width height))) |
|---|
| 391 |
(assert! (funcallv #'(lambda (left top right bottom) |
|---|
| 392 |
(block result |
|---|
| 393 |
(loop for x from left below right |
|---|
| 394 |
do (loop for y from top below bottom |
|---|
| 395 |
do (unless (funcall in-region-p x y) (return-from result nil)))) |
|---|
| 396 |
(return-from result t))) |
|---|
| 397 |
left top right bottom)) |
|---|
| 398 |
;; (best-value (solution (list left top width height) (reorder #'range-size (constantly nil) #'< #'linear-force)) area) |
|---|
| 399 |
(first (best-value (solution (list left top width height) (static-ordering #'linear-force)) area))))) |
|---|
| 400 |
|
|---|
| 401 |
(defun integer-random-force (variable) |
|---|
| 402 |
(let ((variable (value-of variable))) |
|---|
| 403 |
(when (screamer::variable? variable) |
|---|
| 404 |
(screamer::restrict-value! |
|---|
| 405 |
variable |
|---|
| 406 |
(cond ((not (eq (screamer::variable-enumerated-domain variable) t)) |
|---|
| 407 |
(a-member-of (alexandria:shuffle (screamer::variable-enumerated-domain variable)))) |
|---|
| 408 |
(t (error "INTEGER-RANDOM-FORCE is currently only implemented for ~ |
|---|
| 409 |
variables that have an enumerated domain.")))))) |
|---|
| 410 |
(value-of variable)) |
|---|
| 411 |
|
|---|
| 412 |
(export 'colorize) |
|---|
| 413 |
(defun colorize (colors objects neighbours-fn) |
|---|
| 414 |
(let* ((number-of-colors (length colors)) |
|---|
| 415 |
(object2color-var (make-hash-table)) |
|---|
| 416 |
(color-vars (mapcar #'(lambda (obj) |
|---|
| 417 |
(setf (gethash obj object2color-var) |
|---|
| 418 |
(an-integer-betweenv 1 number-of-colors))) |
|---|
| 419 |
objects)) |
|---|
| 420 |
(hash (make-hash-table :size (hash-table-size object2color-var)))) |
|---|
| 421 |
(dolist (obj objects) |
|---|
| 422 |
(setf (gethash obj hash) nil)) |
|---|
| 423 |
(loop for obj in objects |
|---|
| 424 |
for obj-color in color-vars |
|---|
| 425 |
do (dolist (neighbour (funcall neighbours-fn obj)) |
|---|
| 426 |
(unless (member obj (gethash neighbour hash)) |
|---|
| 427 |
(let ((neighbour-color (gethash neighbour object2color-var))) |
|---|
| 428 |
(assert! (notv (=v obj-color neighbour-color))) |
|---|
| 429 |
(push obj (gethash neighbour hash)))))) |
|---|
| 430 |
(one-value (mapcar #'(lambda (color-index) (nth (1- color-index) colors)) |
|---|
| 431 |
(solution color-vars (static-ordering #'integer-random-force))) |
|---|
| 432 |
(error "no solution to colorize problem")))) |
|---|
| 433 |
|
|---|
| 434 |
(in-package :geometry) |
|---|
| 435 |
|
|---|
| 436 |
(defun nodes-connected-p (nodes node-neighbours &optional (test #'eql)) |
|---|
| 437 |
(let ((hash (make-hash-table :test test))) |
|---|
| 438 |
(labels ((visited-p (node) |
|---|
| 439 |
(gethash node hash)) |
|---|
| 440 |
(mark (node) |
|---|
| 441 |
(setf (gethash node hash) t)) |
|---|
| 442 |
(traverse (stack) |
|---|
| 443 |
(let ((current (pop stack))) |
|---|
| 444 |
(when current |
|---|
| 445 |
(mark current) |
|---|
| 446 |
(dolist (neighbour (funcall node-neighbours current)) |
|---|
| 447 |
(unless (visited-p neighbour) |
|---|
| 448 |
(push neighbour stack))) |
|---|
| 449 |
(traverse stack))))) |
|---|
| 450 |
(traverse (list (first nodes))) |
|---|
| 451 |
(= (length nodes) |
|---|
| 452 |
(hash-table-count hash))))) |
|---|
| 453 |
|
|---|
| 454 |
(defun ascii-plot-points (objects &key key) |
|---|
| 455 |
(fresh-line) |
|---|
| 456 |
(let ((bbox (bounding-box objects :key key))) |
|---|
| 457 |
(with-rectangle bbox |
|---|
| 458 |
(loop for y from top below (+ top height) |
|---|
| 459 |
do (loop for x from left below (+ left width) |
|---|
| 460 |
if (member (list x y) objects :key key :test #'equal) |
|---|
| 461 |
do (princ "x") |
|---|
| 462 |
else do (princ ".")) |
|---|
| 463 |
do (terpri))))) |
|---|