ライフゲーム
■2012-10-05 追記: いろいろ変えました。→ ライフゲーム 2 (高速化) - armbrust の日記
lispbuilder-sdl を Quicklisp で入れてみる - armbrust の日記 の続きです。
lispbuilder-sdlでライフゲーム書いてみた - masatoi’s blog
を参考にさせていただき、ちょこちょこ変えてみたりして遊んでいます。
で、「新しい銃 x 3の宇宙船工場」とか作ってみたりして。
(このへんの情報は 宇宙船 (ライフゲーム) - Wikipedia とか グライダー銃 - Wikipedia あたりを見ました)
あ、永久に動き続けるパターンにはなってません。
宇宙船が反対側から出てきて衝突しちゃったら壊れちゃいます。
ちょうどいい場所に宇宙船イーターを出せるようにしてありますが、
まあ、このまま崩壊の過程を見るのも結構楽しいです。
■2012-10-01 追記: 写真を変更しました。ソースも変わってます。
以下、ソースです。
Lisp の基本が分かっていないので、だいぶ汚くしてしまったと思いますけど・・・。
■2012-10-01 追記: ALEXANDRIA と CL-PPCRE も使っています。
■2012-10-04 追記: update-next-gen に不要な処理が混じっていたので削除しました。
■2012-10-10 追記: cl-ppcre:split が不要なのに気づいたので削除しました。
life.cl:
;; original: http://d.hatena.ne.jp/masatoi/20111029/1319893156 (require :lispbuilder-sdl) (require :lispbuilder-sdl-gfx) ;; ドットサイズとフィールドサイズ (defparameter dt 3) ; 余白に 1 ドット使ってしまうので 2 以上で指定 (defparameter wx 240) (defparameter wy 160) ;; いろいろ用意しておく (defparameter world nil) (defparameter map-init nil) ; 初期表示用 (defparameter gen 0) ; 世代表示用 (defparameter skip-gen 1) ; 2 以上にすると早送り、0 にすると止る ;; 部分的な初期配置を行う関数 (はみ出し置きには非対応) (defun put-shape (world list-pos-shape) (let* ((pos (nth 0 list-pos-shape)) (shape (nth 1 list-pos-shape)) (px (nth 0 pos)) (py (nth 1 pos)) (swx (length (nth 0 shape))) (swy (length shape))) (loop for i from 0 to (1- swx) do (loop for j from 0 to (1- swy) do (setf (aref world (+ px i) (+ py j)) (if (string= (char (nth j shape) i) #\Space) 0 1)))))) ;; フィールドの初期化 (defun init () (setf world (make-array (list wx wy) :element-type 'fixnum)) (load "map-init.cl") ; 初期配置のリストは別ファイルで書いておく (map nil #'(lambda (x) (put-shape world x)) map-init) (setf gen 0)) ;; 生きている個体が隣接する8個のセルにどれだけいるかをカウントする (defun count-neighboring-individual (world i j) (let* ((max-i (1- wx)) (max-j (1- wy)) (next-i (if (= i max-i) 0 (1+ i))) (prev-i (if (= i 0) max-i (1- i))) (next-j (if (= j max-j) 0 (1+ j))) (prev-j (if (= j 0) max-j (1- j)))) (+ (aref world prev-i prev-j) (aref world prev-i j) (aref world prev-i next-j) (aref world i prev-j) (aref world i next-j) (aref world next-i prev-j) (aref world next-i j) (aref world next-i next-j)))) ;; 1世代後のフィールドを返す (defun update-next-gen (world) (let ((next-world (alexandria:copy-array world))) (loop for i from 0 to (1- wx) do (loop for j from 0 to (1- wy) do (let ((sell-value (aref world i j)) (sell-neighboring (count-neighboring-individual world i j))) (cond ((and (zerop sell-value) ; 誕生 (= sell-neighboring 3)) (setf (aref next-world i j) 1)) ((and (> sell-value 0) ; 過疎or過密 (or (<= sell-neighboring 1) (>= sell-neighboring 4))) (setf (aref next-world i j) 0)))))) next-world)) (defun life () (sdl:with-init () (sdl:window (* dt wx) (* dt wy)) ; ウインドウのサイズ (setf (sdl:frame-rate) 60) ; フレームレートを60fpsに (init) (sdl:with-events () (:quit-event () t) ; ウインドウが閉じられたときの処理 (:idle () (dotimes (n skip-gen) (setf world (update-next-gen world)) (incf gen)) (loop for i from 0 to (1- wx) do (loop for j from 0 to (1- wy) do (sdl-gfx:draw-box (sdl:rectangle :x (* i dt) :y (* j dt) :w (1- dt) :h (1- dt)) :color (if (zerop (aref world i j)) (sdl:color :r #x55 :g #x55 :b #x55) sdl:*white*)))) (sdl:set-caption (format nil "generations: ~a" gen) nil) ; 世代表示 (sdl:update-display) )))) (life)
map-init.cl:
;; 反転用の関数など (defun reverse-shape-v (shape) (reverse shape)) (defun reverse-shape-h (shape) (mapcar #'reverse shape)) (defun reverse-shape-vh (shape) (mapcar #'reverse (reverse shape))) ;; 新しい銃 左上方向に発射 (defparameter gun-lu '( " " " ooo oo" " o o oo" " o o " " o o " " " " oo o o " " oo o o " " o o oo" " ooo oo" " " " " " " " " " " " o o " " ooo ooo " "oo o o oo " " " " " " o o " " o o " " " " " " " " " " " " " " " " " " " " " " " " oo oo " " oo oo " )) ;; 新しい銃 右下方向に発射 (発射のタイミングがちょっと違う) (defparameter gun-rd '( " oo oo " " oo oo " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " oo o o oo" " o o o o" " ooo ooo " " " " " " " " " " o " "oo oo " "oo oo " " oo oo oo " " oo " " " " " " oo oo " "oo oo " "oo oo " " o " )) ;; 新しい銃 右上方向に発射 (gun-rd を上下反転したもの) (defparameter gun-ru (reverse-shape-v gun-rd)) ;; 宇宙船を食うやつ (defparameter eater '( "oo " "o o " " o " " oo" )) ;; ブリンカー (defparameter blinker '( " o " " o " " o " )) ;; 初期配置のリスト (座標と名前) (defparameter map-init (list (list '( 20 10) gun-rd) (list '( 23 101) gun-ru) (list '( 96 101) gun-lu) )) #| ;; repl から ↓ を評価して直接置いてみたりとか (put-shape world (list '(220 75) eater)) (put-shape world (list '(220 10) blinker)) ;; repl から ↓ を評価して更新速度を変えたり初期状態にしたりとか (setf skip-gen 14) (setf skip-gen 7) (setf skip-gen 2) (setf skip-gen 1) (setf skip-gen 0) (init) ;; あ、Windows の SBCL だと、実行中に repl が入力を受け付けないので・・・orz |#