ライフゲーム再来

## はじめに

以前、VBAでライフゲームを実装したことを書きました。今回はRailsでライフゲームを実装したことを書きたいと思います。どんなものをつくったのか？どうやってつくったのか？どこを工夫したのか？について書きたいと思います。最初にRailsで実装したと書きましたが、実装の八割はCoffeeScriptです。実装は200行程度です。

## どんなものをつくったのか

![life00.png](https://qiita-image-store.s3.amazonaws.com/0/29884/7144c0aa-74a3-4bfb-bcab-84a225546959.png)


黒い大きな正方形があります。使い方は簡単です。この正方形の上にマウスのポインタを移動して、クリックすると、クリックしたところが白くなります。このようにして初期のパターンを設定します。この正方形の外にマウスのポインタを移動すると、パターンが更新されます。少し面倒ですが、ポインタを右往左往させることによってパターンを更新する仕組みになっています。途中でパターンを手動で更新することもできます。このようにしてパターンを楽しむためのものです。

![life04.png](https://qiita-image-store.s3.amazonaws.com/0/29884/d6ac9e43-ede2-6fd5-28ec-6e70c6543f2f.png)


## どうやってつくったのか

まず、次のコマンドを実行します。

```
 $ rbenv exec rails new lifegame
 $ cd lifegame
 $ rbenv exec rails generate controller Micro sample
```

次に、実装を追加します。まずはビューから実装します。

``` sample.html.erb
<h1>Micro#sample</h1>
<p>Find me in app/views/micro/sample.html.erb</p>

<% boardSize=30 %>

<table id="board" style="border-collapse: collapse">
<% boardSize.times do %>
	<tr>
<% boardSize.times do %>
		<td style="background: #000; width: 5px; height: 5px"></td>
<% end %>
	</tr>
<% end %>
</table>
```

次にクリックしたときの動きなどを実装します。

``` micro.js.coffee
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

BOARD_SIZE = 30

class Board
  $board: null
  $boarr: null
  _animation: null
  _tmp: null
  _matrix: null

  _getBackground: (tmp) ->
    for row in [1..BOARD_SIZE]
      for col in [1..BOARD_SIZE]
        $td = @$boarr.filter("[data-row = #{row}]").children("[data-column = #{col}]")
        style = $td.attr("style")
        if /^background: #000/.test style
          tmp[row][col] = 0
        if /^background: #FFF/.test style
          tmp[row][col] = 1

  _calc: (tmp, matrix, row, col) ->
    sum = \
      tmp[row - 1][col - 1] +
      tmp[row - 1][col] +
      tmp[row - 1][col + 1] +
      tmp[row][col - 1] +
      tmp[row][col + 1] +
      tmp[row + 1][col - 1] +
      tmp[row + 1][col] +
      tmp[row + 1][col + 1]

    if sum is 2
      matrix[row][col] = tmp[row][col]
    else if sum is 3
      matrix[row][col] = 1
    else
      matrix[row][col] = 0

  _calcMoore: (tmp, matrix) ->
    for row in [1..BOARD_SIZE]
      for col in [1..BOARD_SIZE]
        @_calc(tmp, matrix, row, col)

  _setBackground: (matrix) ->
    for row in [1..BOARD_SIZE]
      for col in [1..BOARD_SIZE]
        $td = @$boarr.filter("[data-row = #{row}]").children("[data-column = #{col}]")
        style = $td.attr("style")
        if matrix[row][col] is 0
          $td.attr("style", style.replace(/^background: #[0F]{3}/, "background: #000"))
        if matrix[row][col] is 1
          $td.attr("style", style.replace(/^background: #[0F]{3}/, "background: #FFF"))

  _doMain: (self) ->
    self._getBackground(self._tmp)
    self._calcMoore(self._tmp, self._matrix)
    self._setBackground(self._matrix)

  _stopAnimation: ->
    @_animation = false

  _startAnimation: ->
    @_animation = true
    @_doMain(@)

  _eventify: ->
   $("#board").hover =>
      @_stopAnimation()
    , =>
      @_startAnimation()
    @

  _toggleBackground: ->
    @$board.click ->
      style = $(@).attr("style")

      if /^background: #000/.test(style)
        $(@).attr("style", style.replace(/^background: #000/, "background: #FFF"))
        return @

      if /^background: #FFF/.test(style)
        $(@).attr("style", style.replace(/^background: #FFF/, "background: #000"))
        return @

  _prepare: ->
    @_tmp = new Array(BOARD_SIZE + 2)
    
    for row in [0..BOARD_SIZE + 1]
      @_tmp[row] = new Array(BOARD_SIZE + 2)
    
    for row in [0..BOARD_SIZE + 1]
      for col in [0..BOARD_SIZE + 1]
        @_tmp[row][col] = 0

    @_matrix = new Array(BOARD_SIZE + 2)
    
    for row in [0..BOARD_SIZE + 1]
      @_matrix[row] = new Array(BOARD_SIZE + 2)
    
    for row in [0..BOARD_SIZE + 1]
      for col in [0..BOARD_SIZE + 1]
        @_matrix[row][col] = 0

    @$board = $("#board td")
    @$boarr = $("#board tr")

    j = 0
    @$boarr.each ->
      j = j + 1
      $(@).attr("data-row": j)
      i = 0
      $(@).children("td").each ->
        i = i + 1
        $(@).attr("data-column": i)

  constructor: ->
    @_prepare()
    @_toggleBackground()
    @_eventify()
    @

# main
$ ->
  board = new Board()

# /* vim: set sw=2 sts=2 et : */
```

## どこを工夫したのか

素晴らしい工夫だとは言えませんが、td要素を取得するところを工夫しました。何も考えずに実装すると、パターン更新の処理時間が長くなってしまいます。行頭がマイナスの方が修正前、プラスの方が修正後の実装です。

```
- $td = @$board.filter("[data-row = #{row}][data-column = #{col}]")
+ $td = @$boarr.filter("[data-row = #{row}]").children("[data-column = #{col}]")
```

## まとめ

途中でパターンを手動で更新することができます。下記キャプチャのように初期パターンを設定すると、複雑なパターンに発展します。それがどうしたということでもありませんが、ちょっとした収穫でした。

![life99.png](https://qiita-image-store.s3.amazonaws.com/0/29884/4131d99b-cce6-2be5-2fa8-93b9af1e8145.png)