+(in-package :atoms)
+
+(defun score-better-p (this that)
+ (or (eq this :win)
+ (eq that :lose)
+ (and (not (eq this :lose))
+ (not (eq that :win))
+ (> this that))))
+
+(defun invert-score (this)
+ (case this
+ (:win :lose)
+ (:lose :win)
+ (t (- this))))
+
+(defparameter *plies* 2)
+
+(defun make-board-connectivity-map (width height)
+ (let ((map (make-array (* width height)
+ :element-type 'list
+ :initial-element nil)))
+ (flet ((index (i j) (+ (* j width) i)))
+ (dotimes (j height)
+ (dotimes (i width)
+ (setf (aref map (index i j))
+ (nconc (and (> j 0) (list (index i (1- j))))
+ (and (> i 0) (list (index (1- i) j)))
+ (and (< i (1- width)) (list (index (1+ i) j)))
+ (and (< j (1- height)) (list (index i (1+ j))))))))
+ map)))
+
+(deftype octet () '(unsigned-byte 8))
+
+(defun make-critical-count-map (conn-map)
+ (make-array (length conn-map)
+ :element-type 'octet
+ :initial-contents (map 'list #'length conn-map)))
+
+(defun make-simple-board (size)
+ (make-array size
+ :element-type 'octet
+ :initial-element 0))
+
+(defun copy-vector (vector)
+ (make-array (length vector)
+ :element-type (array-element-type vector)
+ :initial-contents vector))
+
+(defun simple-score (who counts owners)
+ (reduce #'+ (mapcar (lambda (count owner)
+ (if (= owner who) count 0))
+ counts owners)))
+
+(defun make-checklist (size)
+ (make-array size :element-type 'fixnum :fill-pointer 0))
+
+(defclass robot-state ()
+ ((n-players :type octet :reader robot-n-players :initarg :n-players)
+ (me :type octet :reader robot-me :initarg :me)
+ (size :type fixnum :reader robot-size :initarg :size)
+ (conn-map :type (vector list *) :reader robot-conn-map :initarg :conn-map)
+ (crit-map :type (vector octet *)
+ :reader robot-crit-map
+ :initarg :crit-map)
+ (checklist-a :type (vector fixnum *)
+ :reader robot-checklist-a
+ :initarg :checklist-a)
+ (checklist-b :type (vector fixnum *)
+ :reader robot-checklist-b
+ :initarg :checklist-b)
+ (scores :type (vector fixnum *) :reader robot-scores :initarg :scores)
+ (seen :type bit-vector :reader robot-seen :initarg :seen)))
+
+(defclass robot-position ()
+ ((who :type octet :reader robot-who :initarg :who)
+ (counts :type (vector octet *) :reader robot-counts :initarg :counts)
+ (owners :type (vector octet *) :reader robot-owners :initarg :owners)))
+
+(defun make-robot-state (game player)
+ (let* ((grid (game-grid game))
+ (width (array-dimension grid 1))
+ (height (array-dimension grid 0))
+ (size (* width height))
+ (n-players (length (game-players game)))
+ (conn-map (make-board-connectivity-map width height)))
+ (make-instance 'robot-state
+ :me (1+ (position player (game-players game)))
+ :n-players n-players
+ :size size
+ :conn-map conn-map
+ :crit-map (make-critical-count-map conn-map)
+ :scores (make-array (1+ n-players) :element-type 'fixnum)
+ :checklist-a (make-checklist size)
+ :checklist-b (make-checklist size)
+ :seen (make-array size :element-type 'bit))))
+
+(defun make-robot-position (who counts owners)
+ (make-instance 'robot-position
+ :who who
+ :counts counts
+ :owners owners))
+
+(defun make-initial-robot-position (state game)
+ (let* ((size (robot-size state))
+ (grid (game-grid game))
+ (counts (make-simple-board size))
+ (owners (make-simple-board size)))
+
+ (dotimes (i size)
+ (let ((cell (row-major-aref grid i)))
+ (setf (aref counts i) (cell-count cell)
+ (aref owners i) (let ((owner (cell-owner cell)))
+ (if owner (1+ owner) 0)))))
+
+ (make-robot-position (robot-me state) counts owners)))
+
+(defclass robot-player (player)
+ ((robot-state :type robot-state :reader robot-player-state)))
+(setf (player-type-name 'robot-player) "Robot")
+
+(defun robot-update-scores (state counts owners)
+ (let ((scores (robot-scores state))
+ (n-players (robot-n-players state))
+ (size (robot-size state)))
+ (dotimes (i (1+ n-players)) (setf (aref scores i) 0))
+ (dotimes (i size) (incf (aref scores (aref owners i)) (aref counts i)))))
+
+(defun robot-try-move (state position move)
+ (let ((conn-map (robot-conn-map state))
+ (crit-map (robot-crit-map state))
+ (n-players (robot-n-players state))
+ (size (robot-size state))
+ (seen (robot-seen state))
+ (checklist (robot-checklist-a state))
+ (next-checklist (robot-checklist-b state))
+ (who (robot-who position))
+ (scores (robot-scores state))
+ (owners (copy-vector (robot-owners position)))
+ (counts (copy-vector (robot-counts position))))
+
+ (let ((occupier (aref owners move)))
+ (unless (or (zerop occupier) (= occupier who))
+ (return-from robot-try-move nil)))
+
+ (block update-board
+ (setf (aref owners move) who)
+ (unless (>= (incf (aref counts move))
+ (aref crit-map move))
+ (return-from update-board))
+
+ (setf (fill-pointer checklist) 0)
+ (vector-push move checklist)
+ (let ((opponents (count-if (lambda (own)
+ (and (plusp own) (/= own who)))
+ owners)))
+ (loop
+ (when (or (zerop (fill-pointer checklist))
+ (zerop opponents))
+ (return-from update-board))
+ (setf (fill-pointer next-checklist) 0)
+ (dotimes (i size) (setf (bit seen i) 0))
+ (dotimes (i (fill-pointer checklist))
+ (let* ((pos (aref checklist i))
+ (crit (aref crit-map pos)))
+ (multiple-value-bind (dist left) (floor (aref counts pos) crit)
+ (setf (aref counts pos) left)
+ (when (zerop left)
+ (setf (aref owners pos) 0))
+ (dolist (neigh (aref conn-map pos))
+ (let ((old-owner (aref owners neigh)))
+ (unless (= old-owner who)
+ (unless (zerop old-owner) (decf opponents))
+ (setf (aref owners neigh) who))
+ (when (and (>= (incf (aref counts neigh) dist)
+ (aref crit-map neigh))
+ (zerop (bit seen neigh)))
+ (vector-push neigh next-checklist)
+ (setf (bit seen neigh) 1)))))))
+ (rotatef checklist next-checklist))))
+
+ (robot-update-scores state counts owners)
+ (make-robot-position (do ((i (1+ (mod who n-players))
+ (1+ (mod i n-players))))
+ ((plusp (aref scores i)) i))
+ counts owners)))
+
+(defun simple-rating (state position)
+ (declare (ignore position))
+ (let* ((n-players (robot-n-players state))
+ (scores (robot-scores state))
+ (me (robot-me state))
+ (total (reduce #'+ scores))
+ (mine (aref scores me)))
+ (cond ((and (zerop mine) (>= total me)) :lose)
+ ((and (= mine total) (>= total n-players)) :win)
+ (t (- (* 2 mine) total)))))
+
+(defparameter *robot-n-plies* 2)
+
+(defun robot-choose-move (state game)
+ (let ((me (robot-me state))
+ (size (robot-size state)))
+ (labels ((walk (position depth alpha beta)
+ (let ((who (robot-who position)))
+
+ #+debug
+ (format t ";; walk; who = ~A; depth remaining = ~A~%"
+ who depth)
+
+ (let ((rating (simple-rating state position)))
+ (case rating
+ ((:win :lose)
+ #+debug
+ (format t ";; final position ~A~%" rating)
+ (return-from walk (values rating nil))))
+ (when (zerop depth)
+ #+debug
+ (format t ";; bottomed out; rating = ~A~%" rating)
+ (return-from walk (values rating nil))))
+
+ (if (= who me)
+ (let ((best-move nil))
+ (dotimes (move size)
+ #+debug
+ (format t ";; try my move ~A~%"
+ (multiple-value-list
+ (floor move
+ (array-dimension (game-grid game)
+ 1))))
+ (let ((next-pos (robot-try-move state
+ position
+ move)))
+ (when next-pos
+ (let ((score (walk next-pos
+ (1- depth)
+ alpha beta)))
+ (when (score-better-p score alpha)
+ #+debug
+ (format t ";; best move so far~%")
+ (setf best-move move
+ alpha score))
+ (when (score-better-p alpha beta)
+ (return))))))
+ (values alpha best-move))
+ (let ((best-move nil))
+ (dotimes (move size)
+ #+debug
+ (format t ";; try opponent #~A move ~A~%"
+ who
+ (multiple-value-list
+ (floor move
+ (array-dimension (game-grid game)
+ 1))))
+ (let ((next-pos (robot-try-move state
+ position
+ move)))
+ (when next-pos
+ (let ((score (walk next-pos
+ (1- depth)
+ alpha beta)))
+ (when (score-better-p beta score)
+ #+debug
+ (format t ";; best opponent move so far~%")
+ (setf best-move move
+ beta score))
+ (when (score-better-p alpha beta)
+ (return))))))
+ (values beta best-move))))))
+ (let ((position (make-initial-robot-position state game)))
+ (robot-update-scores state
+ (robot-counts position)
+ (robot-owners position))
+ (multiple-value-bind (rating move)
+ (walk position *robot-n-plies* :lose :win)
+ (declare (ignore rating))
+ move)))))
+
+(defun robot-move (game player)
+ (let* ((state (robot-player-state player))
+ (move (robot-choose-move state game))
+ (grid (game-grid game))
+ (width (array-dimension grid 1)))
+ (multiple-value-bind (j i) (floor move width)
+ (play-cell game player i j))))
+
+(defun stupid-robot-move (game player)
+ (let* ((state (robot-player-state player))
+ (grid (game-grid game))
+ (width (array-dimension grid 1))
+ (height (array-dimension grid 0))
+ (moves (make-array (array-total-size grid) :fill-pointer 0)))
+ (dotimes (j height)
+ (dotimes (i width)
+ (let ((occupier (cell-owner (aref grid j i))))
+ (when (or (null occupier)
+ (= occupier (1- (robot-me state))))
+ (vector-push (cons i j) moves)))))
+ (let ((move (aref moves (random (length moves)))))
+ (play-cell game player (car move) (cdr move)))))
+
+(defmethod player-turn-begin (game (player robot-player))
+ (unless (slot-boundp player 'robot-state)
+ (setf (slot-value player 'robot-state) (make-robot-state game player)))
+ (robot-move game player))
+
+(unless (find 'robot-player *player-types*)
+ (setf *player-types*
+ (append *player-types* '(robot-player))))
+
+#+debug
+(start-atom-game :width 7
+ :players '("Alice"
+ ("RoboBob" :type robot-player)))