概要
CoffeeScriptに慣れるための習作。
ライフゲームの世界をLifeWorldクラスで構築し、パターンのセットや描画を行わせている。表示に関してはcanvasを利用している。
スマートフォン用はこちら
デモンストレーション
    
    
HTML内容
bodyブロックの構成は以下の通り。
- 初期パターン選択
 
- サイズ選択
 
- 時間間隔選択
 
- 開始/停止ボタン
 
- 表示領域
 
初期パターン選択
規定パターンの中から、ドロップダウンリストで初期パターンを選択。この要素の選択状態が変化した場合の処理は、スクリプト側でjQueryを用いてコールバックを定義している。
		
		
			
			
			
			
				
					
				| 
					
				 | 
						<p> <label>初期パターン:</label> <select id="pattern">   <option value="glider">Glider</option>   <option value="spaceshipL">Lightweight Spaceship</option>   <option value="spaceshipM">Middleweight Spaceship</option>   <option value="spaceshipH">Heavyweight Spaceship</option>   <option value="octagon">Octagon</option>   <option value="galaxy">Galaxy</option>   <option value="pulser">Pulser</option>   <option value="pentadecathron">Pentadecathron</option>   <option value="dieHard">Die Hard</option>   <option value="random">Random</option> </select> </p>  | 
					
				
			 
		 
サイズと表示間隔の選択
それぞれラジオボタン群で選択させる。これらの要素の選択状態が変化した場合の処理も、スクリプト側でjQueryを用いてコールバックを定義している。
		
		
			
			
			
			
				
					
				
					1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  
				 | 
						<p> <label>サイズ:</label> <label><input name="size" type="radio" value="10" />10</label> <label><input checked="checked" name="size" type="radio" value="15" />15</label> <label><input name="size" type="radio" value="20" />20</label> <label><input name="size" type="radio" value="30" />30</label> <label><input name="size" type="radio" value="40" />40</label> </p>   <p> <label>表示間隔:</label> <label><input name="interval" type="radio" value="100" />0.1sec</label> <label><input name="interval" type="radio" value="200" />0.2sec</label> <label><input checked="checked" name="interval" type="radio" value="500" />0.5sec</label> <label><input name="interval" type="radio" value="1000" />1.0sec</label> <label><input name="interval" type="radio" value="1500" />1.5sec</label> </p>  | 
					
				
			 
		 
開始/停止ボタン
ボタンが押されるた時の処理はスクリプト側でjQueryを用いてコールバックを定義しており、その時の状態によって停止と起動を繰り返す。
		
		
			
			
			
			
				
					
				| 
					
				 | 
						<input id="btn_start_stop" type="button" value="Start/Stop" />  | 
					
				
			 
		 
描画領域
HTMLのcanvasを設置。
		
		
			
			
			
			
				
					
				| 
					
				 | 
						<canvas id="canvas" width="399" height="399">  | 
					
				
			 
		 
CoffeeScriptコード
初期パターン
初期パターンを配列にセットしている。たとえば「グライダー」の場合は以下のように定義している。
		
		
			
			
			
			
				
					
				| 
					
				 | 
						@patternGrider = [   [1, 1, 1]   [1, 0, 0]   [0, 1, 0] ]  | 
					
				
			 
		 
本体
本体コードは、LifeWorldクラスとグローバル関数の定義からなる。
ブラウザのボタン操作なや状態変化などに対応するコールバックは、jQueryを利用して実装。
		
		
			
			
			
			
				
					
				
					1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34  
				 | 
						# LifeWorldクラス # Lifegameのグリッドを保持し、初期設定や世代交代の操作をし、アニメーションを描画 class LifeWorld   # コンストラクタ   constructor: (canvas)->   # 指定サイズで、0で埋められたグリッドを生成   create: (row, col)->   # 表示間隔設定   setDisplayInterval: (@interval)->   # 初期状態を設定   setPattern: (patternString)->   # gridの状態を表示   draw: ->   # 次の世代の状態を計算   nextGeneration: ->   # グリッドと位置を指定して生死を返すメソッド   getNewStatusAt: (grid, row, col)->   # タイマ処理に渡すメソッド   animate: ->   # アニメーションの開始/停止   startAndStop: ->   # アニメーションの停止   stop: ->   # 以下はグローバル関数 # ブラウザからのイベントによって起動され、LifeWorldクラスをコントロールする   # 初期化関数 getNewLifeWorld = ->     # 以下は初期動作 # ブラウザから読み込まれたときに一回だけ作動する。 jQuery ($) ->  | 
					
				
			 
		 
初期動作
ブラウザからこのスクリプトが呼び出されたときに一回だけ動作。
LifeWorldの初期化や、コールバックの設定を行う。
		
		
			
			
			
			
				
					
				
					1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25  
				 | 
						jQuery ($) ->   lifeWorld = getNewLifeWorld()   lifeWorld.draw()     $("#pattern").change ->     lifeWorld.stop()     lifeWorld = getNewLifeWorld()     lifeWorld.draw()     return     $("input[name='size']:radio").change ->     lifeWorld.stop()     lifeWorld = getNewLifeWorld()     lifeWorld.draw()     return     $("input[name='interval']:radio").change ->     lifeWorld.stop()     lifeWorld = getNewLifeWorld()     lifeWorld.draw()     return     $("#btn_start_stop").click ->     lifeWorld.startAndStop()     return  | 
					
				
			 
		 
グローバル関数
getNewLifeWorld()
LifeWorldの新しい状態を設定する際に呼ばれる関数。
ブラウザの部品から直接は呼ばれないため、windows.の要素とはしていないが、グローバルスコープの関数。
		
		
			
			
			
			
				
					
				| 
					
				 | 
						getNewLifeWorld = ->   # 初期パターン、サイズ、表示間隔の取得・設定   initialPattern = $("#pattern").val()   gridSize = parseInt($("input[name='size']:checked").val(), 10)   interval= parseInt($("input[name='interval']:checked").val(), 10)   canvas = $("#canvas")[0]     lifeWorld = new LifeWorld(canvas)   lifeWorld.create(gridSize, gridSize)   lifeWorld.setDisplayInterval(interval)   lifeWorld.setPattern(initialPattern)     return lifeWorld  | 
					
				
			 
		 
LifeWorldクラス
constructor(canvas)
LifeWorldクラスのコンストラクタ。
canvasを引数にしてインスタンスを生成するが、この段階ではグリッドも生成されていない。他のパラメータについては個別に設定することを意図している。
		
		
			
			
			
			
				
					
				| 
					
				 | 
						  constructor: (canvas)->     @row = 0     @col = 0     @grid = []     @canvas = canvas     @isRunning = false     @timer = null     @interval = 1000  | 
					
				
			 
		 
create(row, col)
行数と列数を指定してインスタンスのグリッドを構築する。グリッドの全要素が0で埋められている。
		
		
			
			
			
			
				
					
				| 
					
				 | 
						  create: (row, col)->     @row = row     @col = col     @grid = ((0 for c in [0..@col+1]) for r in [0..@row+1])     return  | 
					
				
			 
		 
setDisplayInterval(interval)
動画フレームの時間間隔を設定。
CoffeeScript特有の、引数を直接パラメータに代入する形式。
		
		
			
			
			
			
				
					
				| 
					
				 | 
						  # 表示間隔設定   setDisplayInterval: (@interval)->  | 
					
				
			 
		 
setPattern(patternString)
選択されているラジオボタンの文字により、あらかじめ定義された初期パターンを登録。
初期パターンがグリッドの中央付近に来るように調整している。
		
		
			
			
			
			
				
					
				
					1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27  
				 | 
						  setPattern: (patternString)->     switch patternString       when "glider" then pattern = patternGrider       when "spaceshipL" then pattern = patternSpaceshipL       when "spaceshipM" then pattern = patternSpaceshipM       when "spaceshipH" then pattern = patternSpaceshipH       when "octagon" then pattern = patternOctagon       when "galaxy" then pattern = patternGalaxy       when "pulser" then pattern = patternPulser       when "pentadecathron" then pattern = patternPentadecathron       when "dieHard" then pattern = patternDieHard       when "random"         for r in [1..@row]           for c in [1..@col]             if Math.random() > 0.5 then @grid[r][c] = 1         return       @grid[r][c] = 0 for r in [0...@grid.length] for c in [0...@grid[0].length]       pivotR = Math.floor((@grid.length - pattern.length) / 2)     pivotC = Math.floor((@grid[0].length - pattern[0].length) / 2)       for r in [0...pattern.length]       for c in [0...pattern[0].length]         @grid[pivotR + r][pivotC + c] = pattern[r][c]       return  | 
					
				
			 
		 
draw()
グリッドの状態に応じてcanvasのコンテキストに描画。格子線も描いている。
		
		
			
			
			
			
				
					
				
					1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33  
				 | 
						  draw: ->     w = @canvas.width     h = @canvas.height       ctx = @canvas.getContext("2d")     ctx.fillStyle = "#000000"     ctx.fillRect(0, 0, w, h)       cellSize = (h + 1) / @row       ctx.strokeStyle = "#808080"     ctx.beginPath()     for r in [0...@row]       ctx.moveTo(r*cellSize, 0)       ctx.lineTo(r*cellSize, w)     for c in [0...@col]       ctx.moveTo(0, c*cellSize)       ctx.lineTo(h, c*cellSize)     ctx.stroke()       ctx.fillStyle = "#ffffff"     ctx.strokeStyle = "#404040"     y = 0     for r in [1..@row]       x = 0       for c in [1..@col]         if @grid[r][c] == 1           ctx.fillRect(x, y, cellSize, cellSize)           ctx.strokeRect(x, y, cellSize, cellSize)         x += cellSize       y += cellSize       return  | 
					
				
			 
		 
nextGeneration()
次のgetNewStatusAt()メソッドを使いながら、次の世代のグリッドの状態を形成。
新しい世代用の一時的なグリッドを生成している。
		
		
			
			
			
			
				
					
				| 
					
				 | 
						  nextGeneration: ->     # grid->cloneへの複製     clone = ((x for x in rowArray) for rowArray in @grid)     # 各セルの次の世代の生死を設定     for r in [1..@row]       for c in [1..@col]         clone[r][c] = @getNewStatusAt(@grid, r, c)     # clone->gridへの複製     @grid = ((x for x in rowArray) for rowArray in clone)     return  | 
					
				
			 
		 
getNewStatusAt(grid, row, col)
現在の状態から、次の世代のグリッドの状態を算出。
自分のマスの周囲8つのマスの存在状態から、次の世代の誕生/存続/死滅を決定。
| 周囲の生存数 | 
現状=存在 | 
現状=不在 | 
| 0 | 
死滅 | 
不在 | 
| 1~2 | 
存続 | 
不在 | 
| 3 | 
存続 | 
誕生 | 
| 4以上 | 
死滅 | 
不在 | 
		
		
			
			
			
			
				
					
				| 
					
				 | 
						  getNewStatusAt: (grid, row, col)->     count = 0     count += grid[r][c] for c in [col-1..col+1] for r in [row-1..row+1]     count -= grid[row][col]       switch count       when 0 then status = 0       when 1 then status = 0       when 2 then status = grid[row][col]       when 3 then status = 1       else status = 0       return status  | 
					
				
			 
		 
animate()
setInterval()メソッドに登録するための、ワンフレームの画像を描くメソッド。
		
		
			
			
			
			
				
					
				| 
					
				 | 
						  animate: ->     @nextGeneration()     @draw()     return  | 
					
				
			 
		 
startAndStop() / stop()
startAndStop()は、現在動画を実行中かどうかのフラグをチェックし、停止していればsetInterval()を用いて起動、起動中ならclearInterval()停止を行う。
stop()は、状態によらず停止処理。
		
		
			
			
			
			
				
					
				| 
					
				 | 
						  # アニメーションの開始/停止   startAndStop: ->     if @isRunning       clearInterval(@timer)       @isRunning = false     else       @timer = setInterval(@animate.bind(this), @interval)       @isRunning = true     return     # アニメーションの停止   stop: ->     if @isRunning       clearInterval(@timer)     return  |