初心者がRailsを勉強するブログ

Railsを0からお勉強するブログです。

CoffeeScriptで碁盤を作ってみた

Railsが難しいので、CoffeeScriptで息抜きします。

さらっと作ったので、低クオリティですが碁盤を作ります。
イメージはこんな感じ。

f:id:carmelokarimero:20140209184453p:plain

ソースは、ここにあります。

囲碁のルールを全然知らないので、canvasに碁盤を表示して、石を置くだけにします。
碁盤は19路盤、13路盤、9路盤の3種類があるみたいなので、対応しましょう。

まずは、HTMLを書きます。
設定を変更するselectと、canvasを置くだけの簡素なものです。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>碁盤</title>

  <!-- jQuery -->
  <script src="lib/jquery.js"></script>

  <!-- 俺のコード -->
  <script src="igo.js"></script>
  <link rel="stylesheet" href="igo.css">
</head>
<body>
  <!-- 設定 -->
  <div id="config-div">
    <p>
      碁盤の種類:
      <select class="config" id="board-kind">
        <option value="19">19路盤</option>
        <option value="13">13路盤</option>
        <option value="9">9路盤</option>
      </select>
    </p>

    <p>
      石の切り替えモード:
      <select class="config" id="stone-mode">
        <option value="mutual">交互</option>
        <option value="manual">手動</option>
      </select>
    </p>

    <p>
      次に置く石:
      <select class="config" id="next-stone">
        <option value="black">黒石</option>
        <option value="white">白石</option>
      </select>
    </p>
  </div>

  <!-- キャンバス -->
  <canvas id="board" width="600" height="600"></canvas>
  <canvas id="stone" width="600" height="600"></canvas>
  <canvas id="cursor" width="600" height="600"></canvas>
</body>
</html>


次に、scssを書きます。今回は息抜きなので、配置するだけにします。

// 設定
#config-div {
  display: flex;
  margin-left: 4em;
  margin-top: 1em;

  * {
    margin-right: 0.6em;
  }
}

// キャンバス
canvas {
  position: absolute;
  top: 4em;
  left: 5em;
  border: 1px solid black;
}

ほとんど生のCSSですねwこりゃひでぇw


最後に本丸CoffeeScriptです。
canvasは使い勝手が良すぎますね。

まず、ブラウザからも変更可能な設定項目を変数にしておきます。
ページのロードが終わってから動くように、document.readyも忘れないようにします。
ついでに碁盤の交点を入れる配列も書いておきます。

$ ->
  # 変数
  # 19, 13, 9路盤の3択
  numberOfLines = 19

  # 石の切り替えモード
  mutualMode = true

  # 次に置く石
  nextStone = "black"

  # 交点のオブジェクトを入れる配列
  points = []

numberOfLinesは碁盤の種類、mutualModeはtrueであれば黒石と白石を交互に打つように、nextStoneは次に打つ石の色です。

次に、canvasのDOMを取得して、変数に入れておきます。
ついでにcanvas関連のもろもろの変数も書いておきます。

  # キャンバスとコンテキストを取得
  boardCv = $("#board")[0]
  boardCtx = boardCv.getContext '2d'

  stoneCv = $("#stone")[0]
  stoneCtx = stoneCv.getContext '2d'

  cursorCv = $("#cursor")[0]
  cursorCtx = cursorCv.getContext '2d'

  # キャンバスの大きさと位置を取得
  canvasSize = boardCv.width
  canvasPos = boardCv.getBoundingClientRect()

getBoundingClientRect()は、ページ内のDOMの座標をくれます。
あとで使う、canvasの大きさと座標もついでに変数に入れておきます。

ついでに、キャンバスをクリアするメソッドを作成しておきます。
引数にコンテキストを取って、どのキャンバスも初期化できるようにしておきます。

  # キャンバスをクリア
  clearCanvas = (ctx) ->
    ctx.clearRect 0, 0, canvasSize, canvasSize

次に、碁盤のクラスを書きます。碁盤が3種類あるので、クラスにしました。

まず、コンストラクタを書きます。
設定をブラウザから変更できるようにするので、碁盤の設定を一括で変更するchangeLinesメソッドを実装します。
コンストラクタでも使ってしまいましょう。

# 碁盤のクラス
  class Board
    constructor: (@lines) ->
      @.changeLines @lines
      @image = new Image()
      @image.src = "image/wood.jpg"
      @lineColor = "black"
      @dotLines = [3, 9, 15]

linesは線の数、lineColorは碁盤の線の色、dotLinesは19路盤で点を打つ場所です。
リアルな見た目にするため、木の画像を使用します。好みの木の碁盤を作ってください。

次に、メソッドを書きます。
まず、設定を一括で変更して、碁盤を切り替えるchangeLinesメソッドを書きます。

    # 碁盤の種類を変更する
    changeLines: (num) ->
      @lines = num * 1
      @points = @lines - 1
      @margin = canvasSize / @lines * 0.5
      @linePadding = (canvasSize - @margin * 2) / @points
      @stoneRadius = @linePadding / 2 * 0.7
      @lineEnd = canvasSize - @margin

      # 交点オブジェクトを修正
      points = []

      for col in [0..@points]
        for row in [0..@points]
          points.push new Point @, col, row

各プロパティはこんな感じです。

lines: 線の数、9, 13, 19の3種類
points: 一行の交点の数
margin: 格子と端の余白
linePadding: 線の間隔
stoneRadius: 碁石の半径
lineEnd: 線の端の座標

碁盤を切り替えると、交点の数が変わるので、後で定義する交点オブジェクトを修正してます。

次に、クリックしたときや、ホバーした時に、座標から交点を取得するためのメソッドを書きます。

    # 座標から交点を取得
    getPoint: (ary, x, y) ->
      for point in ary
        pointX = canvasPos.left + point.x
        pointY = canvasPos.top + point.y

        nearX = pointX - @stoneRadius <= x <= pointX + @stoneRadius
        nearY = pointY - @stoneRadius <= y <= pointY + @stoneRadius

        return point if nearX and nearY

引数のaryは交点オブジェクトを入れた配列を渡します。
nearXとnearYは、座標と交点の距離が碁石の半径以内であればtrueになります。

次は碁盤をcanvasに描画するメソッドです。

    # 碁盤を描画
    drawBoard: ->
      boardCtx.strokeStyle = @lineColor
      boardCtx.fillStyle = @lineColor

      # 木の板を用意
      boardCtx.drawImage @image, 0, 0, canvasSize, canvasSize

      # 線を引く
      for line in [0..@points]
        thisLine = @margin + line * @linePadding

        # 横線
        boardCtx.beginPath()
        boardCtx.moveTo(@margin, thisLine)
        boardCtx.lineTo(@lineEnd, thisLine)
        boardCtx.stroke()

        # 縦線
        boardCtx.beginPath()
        boardCtx.moveTo(thisLine, @margin)
        boardCtx.lineTo(thisLine, @lineEnd)
        boardCtx.stroke()

        # 点を打つ
        if @lines is 19 and line in @dotLines
          boardCtx.beginPath()
          boardCtx.arc(thisLine, @margin + dot * @linePadding, 3, 0, Math.PI * 2, false) for dot in @dotLines
          boardCtx.fill()

canvasいっぱいに木の画像を置いて、その上に格子を描きます。
また、19路盤のときだけ謎の点があったので、描画します。

これでboardオブジェクトに必要なメソッドが揃いました。
次は交点オブジェクトを書きます。
例によって、コンストラクタから行きます。

  # 交点のクラス
  class Point
    constructor: (board, @col, @row) ->
      @stone = "empty"
      @x = board.margin + @col * board.linePadding
      @y = board.margin + @row * board.linePadding

x、yはcanvas内の座標です。
stoneは黒石ならblack、白石ならwhite、何もなければemptyです。

次に、メソッドを書きます。
まずは石を置くメソッドです。

    # 石を置く
    setStone: (stone) ->
      @stone = stone

      if mutualMode
        nextStone = ["black", "white"][(stone is "black") * 1]
        $("#next-stone")[0].value = nextStone

石を交互に打つモードのときには切り替わるようにしています。
三項演算子で書いたら動かなかったので、こんな書き方にしてみました。trueが1、falseが0の言語は遊べますね。

囲碁は、石を消す必要があるらしいので、石を消すメソッドを書きます。
stoneプロパティをemptyにするだけです。

    # 石を消す
    emptyStone: ->
      @stone = "empty"


石を描画するメソッドを書きます。
交点オブジェクトをまとめた配列にループをかけて使う予定なので、stoneプロパティがemptyのときは弾くようにします。

    # 石を描画
    drawStone: (board) ->
      return null if @stone is "empty"

      stoneCtx.fillStyle = @stone
      stoneCtx.beginPath()
      stoneCtx.arc @x, @y, board.stoneRadius, 0, Math.PI * 2, false
      stoneCtx.fill()

本当にcanvasは簡単で使いやすいです。

この碁盤では、交点をクリックした時に石を置くつもりです。
マウスオーバした交点に半透明の石が表示されると、思ったところに石が打てそうですよね。実装しましょう。

    # マウスを載せた時の挙動
    onMouse: (stone) ->
      cursorCtx.strokeStyle = stone
      cursorCtx.globalAlpha = 0.3
      cursorCtx.beginPath()
      cursorCtx.arc @x, @y, board.stoneRadius, 0, Math.PI * 2, false
      cursorCtx.stroke()

これで交点のオブジェクトも出来ました。

必要なクラスも揃ったので、インスタンスを作って、描画します。

  # 画像のロードが終わったらボードを描画
  board = new Board(numberOfLines)
  $(board.image).on 'load', ->
    board.drawBoard()

画像を使うときは、画像のロードが終わるまで待たないと描画できません。これで結構詰まりましたw

最後にイベントハンドラを書きます。
まずは、マウスオーバー時に半透明の碁石を表示しましょう。

  # イベントハンドラをセット
  # カーソル位置に対応する交点を表示
  $(cursorCv).on "mousemove", (e) ->
    clearCanvas cursorCtx

    x = e.pageX
    y = e.pageY

    pointOnCursor = board.getPoint points, x, y
    pointOnCursor.onMouse(nextStone) if pointOnCursor


次に、交点クリック時に石を置いたり消したりしましょう。

  # クリックしたら、石をトグルして、描画する
  $(cursorCv).on "click", (e) ->
    x = e.pageX
    y = e.pageY

    clickedPoint = board.getPoint points, x, y

    # 石をトグル
    if clickedPoint
      switch clickedPoint.stone
        when "empty" then clickedPoint.setStone nextStone
        else clickedPoint.emptyStone()

      # 石を描画
      clearCanvas stoneCtx
      point.drawStone(board) for point in points


最後に、設定を変更したら、反映できるようにします。

  # 設定を変更したら、反映する
  $(".config").on "change", ->
    switch @.id
      # 碁盤の種類変更
      when "board-kind"
        clearCanvas ctx for ctx in [boardCtx, stoneCtx]
        board.changeLines(@.value)
        board.drawBoard()

      # 石を置いたあとに、次の石を切り替えるか
      when "stone-mode"
        mutualMode = @.value is "mutual"

      # 次に置く石の色
      when "next-stone"
        nextStone = @.value

以上で、碁盤の実装は完了です。
やっぱりCoffeeScriptはいいです。もうjsには戻れそうにありません。