Goban 0.3

マウスをクリックすると碁石を自由に置けます。待ったはできません。[パス Pass] ボタンでパスすることができます。石を囲ってもまだ取れません。

実行結果

Goban 0.3 には HTML5 Canvas が必要です。

ソース

"expand source" を押すと展開されます。元に戻すには、このページを再ロードしてください。

goban03.html

<div style="height: 510px; background-color: #26130D; margin 0">
<canvas style="margin: 0" width="640" height="480" id="table">Goban 0.3 には HTML5 Canvas が必要です。</canvas>
<img style="vertical-align: bottom; padding-left: 10px" src="img/white22.png">
<input id="white" type="button" value="パス Pass" onclick="pass()">
<img style="vertical-align: bottom; padding-left: 10px" src="img/black22.png">
<input id="black" type="button" value="パス Pass" onclick="pass()">
<input style="margin-left: 40px" type="button" value="はじめから Restart" onclick="restart()">
</div>

goban03.js

goban02.js からの差分は以下のとおりです。
(1) 碁盤や碁笥の位置を計算により求め、きれいに配置しました。
(2) Firefox で石が置けなかったため、マウスイベントの処理を改良しました。
(3) 6路盤を9路盤に変更し、星の描画処理を追加しました。
(4) 碁盤の処理を Board オブジェクトとして board01.js に分離しました。
(5) [パス]、[はじめから]ボタンの処理を追加しました。
/*
 * Copyright (C) 2012 たかはしのんき. All rights reserved.
 *
 * History:
 *  0.3 2012/05/15 マウスイベントで Firefox に対応。星の表示に対応。ボタンに対応。
 *  0.2 2012/05/08 マウスイベントハンドラーを追加。効果音に対応。
 *  0.1 2012/05/07 新規作成。Java 版より移植。
 */

/**
 * Goban - 碁盤イメージの描画
 * @version 0.3
 * @author たかはしのんき
 * 
 */

// 共通に使う定数と変数
var BLACK = 1;
var WHITE = 2;
var PX_BOWL = 134;	// 碁笥の直径 [ピクセル]
var Z2_BOWL = 20;	// 碁笥の高さ / 2 [ピクセル]
var PX_LID = 110;	// 碁笥の蓋の直径 [ピクセル]
var Z2_LID = 8;		// 碁笥の蓋の高さ / 2 [ピクセル]
var PX_STONE = 22;	// 碁石の直径 [ピクセル]
var Z2_STONE = 2;	// 碁石の高さ / 2 [ピクセル]
var PX_STAR = 5;	// 星の直径 [ピクセル]
var PX_CHAR = 24;	// 文字の高さ [ピクセル]
var Z2_BOARD = 6;	// 碁盤の高さ / 2 [ピクセル]

// キャッシュにイメージを読み込んでおく
var imgbb = new Image(PX_BOWL, PX_BOWL);	// 黒の碁笥 (black bowl)
imgbb.src = "img/blackbowl134.png";
var imgwb = new Image(PX_BOWL, PX_BOWL);	// 白の碁笥 (white bowl)
imgwb.src = "img/whitebowl134.png";
var imgl = new Image(PX_LID, PX_LID);		// 碁笥の蓋 (lid)
imgl.src = "img/lid110.png";
var imgb = new Image(PX_STONE, PX_STONE);	// 黒石 (white stone)
imgb.src = "img/black22.png";
var imgw = new Image(PX_STONE, PX_STONE);	// 白石 (black stone)
imgw.src = "img/white22.png";
var imgs = new Image(PX_STAR, PX_STAR);		// 星 (star)
imgs.src = "img/star5.png";

// キャッシュに効果音を読み込んでおく
var ext = audioExt();
var auClick = new Audio("se/button6."  + ext);
var auSwitch = new Audio("se/se_sad05." + ext);
var auError = new Audio("se/se_sad08." + ext);

var turn;							// 手番
var context;						// キャンバスのコンテキスト
var passButton = new Array(2);		// [パス]ボタン
var brd;							// 碁盤オブジェクト

/**
 * ロード時に碁盤イメージを描画します。
 */
window.onload = function() {
	// 黒石の <img id="black"> 取得
	passButton[BLACK] = document.getElementById('black');
	// 白石の <img id="white"> 取得
	passButton[WHITE] = document.getElementById('white');

	// キャンバスとコンテキスト取得
	var canvas = document.querySelector('#table');
	context = canvas.getContext('2d');
	// 座標 (270, 50) にタイトルを表示
	context.fillStyle = "white";
	context.font = "bold 24px Arial";
	var title = "Goban 0.3";
	var x = context.canvas.width / 2 - 
			PX_CHAR / 1.8 * title.length / 2;
	var y = PX_CHAR * 2;
	context.fillText(title, x, y);
	// 机の中央に碁盤を表示
	brd = new Board(9);
	// 碁盤の左に白の碁笥の蓋のイメージを表示
	x = Math.floor((brd.boardOffset.x - PX_LID) / 2);
	y = brd.boardOffset.y +
		Math.floor((brd.boardSize.height - PX_LID) / 2);
	drawObject(x, y, Z2_LID, imgl, context);
	// 白の碁笥の蓋に黒石のイメージを表示
	var r = Math.random() * PX_LID / 3;
	var a = Math.random() * 2 * Math.PI;
	var xs = x + Math.floor((PX_LID / 2) + r * Math.cos(a) - (PX_STONE / 2));
	var ys = y + Math.floor((PX_LID / 2) + r * Math.sin(a) - (PX_STONE / 2));
	drawObject(xs, ys, Z2_STONE, imgb, context);
	// 左上に白の碁笥のイメージを表示
	var xb = x - Math.floor((PX_BOWL - PX_LID) / 2);
	var yb = y - PX_BOWL - Z2_BOWL;
	drawObject(xb, yb, Z2_BOWL, imgwb, context);
	// 碁盤の右に黒の碁笥の蓋のイメージを表示
	x = x + brd.boardOffset.x + brd.boardSize.width;
	drawObject(x, y, Z2_LID, imgl, context);
	// 黒の碁笥の蓋に白石のイメージを表示
	r = Math.random() * PX_LID / 3;
	a = Math.random() * 2 * Math.PI;
	xs = x + Math.floor((PX_LID / 2) + r * Math.cos(a) - (PX_STONE / 2));
	ys = y + Math.floor((PX_LID / 2) + r * Math.sin(a) - (PX_STONE / 2));
	drawObject(xs, ys, Z2_STONE, imgw, context);
	// 右下に黒の碁笥のイメージを表示
	xb = x - Math.floor((PX_BOWL - PX_LID) / 2);
	yb = y + PX_LID + Z2_BOWL;
	drawObject(xb, yb, Z2_BOWL, imgbb, context);
	// 碁盤を表示
	brd.drawBoard(Z2_BOARD);
	// 黒番から
	turn = BLACK;
	passButton[WHITE].disabled = true;
	// クリックイベント登録
	canvas.addEventListener("click", onClick, false);
};

/**
 * マウスイベントハンドラー
 * とりあえず碁石を置きます。
 * @since 0.2
 */
function onClick() {
	var mx, my;
	if (arguments.length == 1) {		// Firefox 対応
		event = arguments[0];
		mx = event.pageX - this.offsetLeft;
		my = event.pageY - this.offsetTop; 
	} else {
		mx = event.offsetX;
		my = event.offsetY;
	}
	if (turn == BLACK) 
		drawObject(mx - (PX_STONE / 2), my - (PX_STONE / 2), Z2_STONE, imgb, context);
	else
		drawObject(mx - (PX_STONE / 2), my - (PX_STONE / 2), Z2_STONE, imgw, context);
	auClick.play();
	passButton[turn].disabled = true;
	turn = 3 - turn;
	passButton[turn].disabled = false;
}

/**
 * [パス]ボタンの処理
 * @since 0.3
 */
function pass() {
	// ボタンの音を鳴らす。
	auSwitch.play();
	passButton[turn].disabled = true;
	turn = 3 - turn;
	passButton[turn].disabled = false;
}

/**
 * [はじめから]ボタンの処理
 * @since 0.3
 */
function restart() {
	// ボタンの音を鳴らす。
	auSwitch.play();
	// 碁盤を表示
	brd.drawBoard(Z2_BOARD);
	// 黒番から
	turn = BLACK;
	passButton[WHITE].disabled = true;
	passButton[BLACK].disabled = false;
}

/**
 * ブラウザで有効なAudioの拡張子を獲得
 * @since 0.2
 */
function audioExt() {
    var ext = "";
    var audio = new Audio();
 	if (audio.canPlayType("audio/ogg") == 'maybe')
 		ext="ogg";
    else if (audio.canPlayType("audio/mp3") == 'maybe')
    	ext="mp3";
    else if (audio.canPlayType("audio/wav") == 'maybe')
    	ext="wav";
    return ext;
}

/**
 * 円形オブジェクトを描画します。
 * @param x 左端座標
 * @param y 上端座標
 * @param s 影の長さ(高さ/2)
 * @param img オブジェクトのイメージ
 * @param context 描画先のコンテキストを指定します。
 * @since 0.1
 */
function drawObject(x, y, s, img, context) {
	var r = img.width / 2;
	context.beginPath();
	context.arc(x + r + s, y + r + s, r, 0, 2 * Math.PI);
	context.fillStyle = "black";
	context.globalAlpha = 0.5;
	context.fill();
	context.globalAlpha = 1.0;
	context.drawImage(img, x, y);
}

board01.js

goban02.js より碁盤の描画処理をオブジェクト Board として分離しました。
/*
 * Copyright (C) 2012 たかはしのんき. All rights reserved.
 *
 * History:
 *  0.1 2012/05/15 新規作成。 goban02.js より独立。
 */

var PIXEL_COL = 22;
var PIXEL_ROW = 24;
var PIXEL_CHAR = 24;

/**
 * Board - 碁盤オブジェクト
 * @version 0.1
 * @author たかはしのんき
 * 
 */
Board = function(ro) {
	this.ro = ro;
	this.boardSize = {					// 碁盤のサイズ[ピクセル]
		width : PIXEL_COL * (ro + 1),
		height : PIXEL_ROW * (ro + 1)
	};
	this.boardOffset = {				// 碁盤のテーブルからのオフセット[ピクセル]
		x : Math.floor((context.canvas.width - this.boardSize.width) / 2),
		y : Math.floor((context.canvas.height 
				+ PIXEL_CHAR * 2 - this.boardSize.height) / 2)
	};
	this.gridSize = {					// 格子全体のサイズ[ピクセル]
		width : PIXEL_COL * (ro - 1) + 2,
		height : PIXEL_ROW * (ro - 1) + 2
	};
	this.gridOffset = {					// 格子全体の碁盤からのオフセット[ピクセル]
		x : Math.floor((this.boardSize.width - this.gridSize.width) / 2),
		y : Math.floor((this.boardSize.height - this.gridSize.height) / 2)
	};
};

Board.prototype = {
	
	/**
	 * 碁盤を描画します。
	 * @param s 影の長さ(高さ/2)
	 * @since 0.1
	 */
	drawBoard : function(s) {
		var x = this.boardOffset.x;
		var y = this.boardOffset.y;
		var ro = this.ro;
		var dx = PIXEL_COL;
		var dy = PIXEL_ROW;
		var width = this.boardSize.width;
		var height = this.boardSize.height;
		var gwidth = this.gridSize.width;
		var gheight = this.gridSize.height;
		var gx = x + this.gridOffset.x;
		var gy = y + this.gridOffset.y;
		// 碁盤の影
		context.beginPath();
		context.moveTo(x, y);
		context.lineTo(x + width, y);
		context.lineTo(x + width + s, y + s);
		context.lineTo(x + width + s, y + height + s);
		context.lineTo(x + s, y + height + s);
		context.lineTo(x, y + height);
		context.closePath();
		context.fillStyle = "black";
		context.globalAlpha = 0.5;
		context.fill();
		// 碁盤
		context.beginPath();
		context.rect(x, y, width, height);
		context.fillStyle = "burlywood";
		context.globalAlpha = 1.0;
		context.fill();
		// 木目
		this.drawWoodGrain(x, y, width, height, context);
		// 格子
		context.fillStyle = "black";
		var x1, y1, lwidth;
		// (横の格子線)
		x1 = gx;
		for (var row = 1; row <= ro; row++) {
			if (row == 1)
				y1 = gy + (row - 1) * dy;
			else
				y1 = gy + 1 + (row - 1) * dy;
			if (row == 1 || row == ro)
				lwidth = 2;
			else
				lwidth = 1;
			context.beginPath();
			context.rect(x1, y1, gwidth, lwidth);
			context.fill();
		}
		// 縦の格子線
		y1 = gy;
		for (var col = 1; col <= ro; col++) {
			if (col == 1)
				x1 = gx + (col - 1) * dx;
			else
				x1 = gx + 1 + (col - 1) * dx;
			if (col == 1 || col == ro)
				lwidth = 2;
			else
				lwidth = 1;
			context.beginPath();
			context.rect(x1, y1, lwidth, gheight);
			context.fill();
		}
			if (ro % 2 == 1) {			// 星
				// 天元
				this.drawStar(Math.floor(ro / 2) + 1, Math.floor(ro / 2) + 1);
				if (ro == 9) {				// 3線隅の星
					this.drawStar(3, 3);
					this.drawStar(ro - 2, 3);
					this.drawStar(3, ro - 2);
					this.drawStar(ro - 2, ro - 2);
				}
				if (ro == 13 || ro == 19) {	// 4線隅の星
					this.drawStar(4, 4);
					this.drawStar(ro - 3, 4);
					this.drawStar(4, ro - 3);
					this.drawStar(ro - 3, ro - 3);
				}
				if (ro == 19) {				// 4線辺の星
					this.drawStar(ro / 2 + 1, 4);
					this.drawStar(4, ro / 2 + 1);
					this.drawStar(ro - 3, ro / 2 + 1);
					this.drawStar(ro / 2 + 1, ro - 3);
				}
			}
		},

		/**
		 * 星を描画します。
		 * @param col 星の桁位置を指定します。
		 * @param row 星の行位置を指定します。
		 * @since 0.1
		 */
		drawStar : function(col, row) {
			var x = this.boardOffset.x + this.gridOffset.x + 1 +
					(col - 1) * PIXEL_COL - 2; 
			var y = this.boardOffset.y + this.gridOffset.y + 1 +
					(row - 1) * PIXEL_ROW - 2;
			context.drawImage(imgs, x, y);
		},

	/**
	 * 木目を描画します。
	 * @param x 左端座標
	 * @param y 上端座標
	 * @param width 描画する幅
	 * @param height 描画する高さ
	 * @param context 描画先のコンテキストを指定します。
	 * @since 0.1
	 */
	drawWoodGrain : function(x, y, width, height, context) {
		context.strokeStyle = "rgb(192, 153, 86)";	// 木目の色
		var xb0 = x;
		var xb1 = x + width;
		var yb0 = y;
		var yb1 = y + height;
		var xo, yo, xw, yw;
		var xc = xb0 + (xb1 - xb0) * 5 / 7;			// 木目の中心
		var yc = yb0 + (yb1 - yb0) * 5 / 4;
		for (var rw = 5; rw <= 460; rw += 5) {
			xo = xc + rw;
			yo = yc;
			for (var theta = 0; theta <= 2 * Math.PI;
					theta += 2 * Math.PI / 19) {
				xw = xc + Math.floor(rw * Math.cos(theta));
				yw = yc + 13 * Math.floor(rw * Math.sin(theta));
				var xw0 = xo, xw1 = xw, yw0 = yo, yw1 = yw;
				if (yw1<yb0 && yb0<yw0) {			//上辺でクリッピング
					xw1 = xw0+(xw1-xw0)*(yb0-yw0)/(yw1-yw0);
					yw1 = yb0;
				} else if (yw0<yb0 && yb0<yw1) {	//上辺でクリッピング
					xw0 = xw1+(xw0-xw1)*(yb0-yw1)/(yw0-yw1);
					yw0 = yb0;
				}
				if (yw1<yb1 && yb1<yw0) {			//下辺でクリッピング
					xw0 = xw1+(xw0-xw1)*(yb1-yw1)/(yw0-yw1);
					yw0 = yb1;
				} else if (yw0<yb1 && yb1<yw1) {	//下辺でクリッピング
					xw1 = xw0+(xw1-xw0)*(yb1-yw0)/(yw1-yw0);
					yw1 = yb1;
				}
				if (xw1<xb0 && xb0<xw0) {			//左辺でクリッピング
					xw1 = xb0;
					yw1 = yw0+(yw1-yw0)*(xb0-xw0)/(xw1-xw0);
				} else if (xw0<xb0 && xb0<xw1) {	//左辺でクリッピング
					xw0 = xb0;
					yw0 = yw1+(yw0-yw1)*(xb0-xw1)/(xw0-xw1);
				}
				if (xw1<xb1 && xb1<xw0) {			//右辺でクリッピング
					xw0 = xb1;
					yw0 = yw1+(yw0-yw1)*(xb1-xw1)/(xw0-xw1);
				} else if (xw0<xb1 && xb1<xw1) {	//右辺でクリッピング
					xw1 = xb1;
					yw1 = yw0+(yw1-yw0)*(xb1-xw0)/(xw1-xw0);
				}
				if (xb0<=xw0 && xw0<=xb1 && xb0<=xw1 && xw1<=xb1 &&
						yb0<=yw0 && yw0<=yb1 && yb0<=yw1 && yw1<=yb1) {
					context.beginPath();
					context.moveTo(xw0, yw0);
					context.lineTo(xw1, yw1);
					context.stroke();
				}
				xo = xw;
				yo = yw;
			}
		}
	}
};