CoffeeScriptで碁盤を作ってみた
Railsが難しいので、CoffeeScriptで息抜きします。
さらっと作ったので、低クオリティですが碁盤を作ります。
イメージはこんな感じ。
ソースは、ここにあります。
囲碁のルールを全然知らないので、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には戻れそうにありません。