1 /*
  2  * Copyright (C) 2012-2013 たかはしのんき. All rights reserved.
  3  *
  4  * History:
  5  *  0.5 2013-06-15 JsDoc Toolkit対応。得点の表示。
  6  *  0.4 2013-04-07 石の数の表示。
  7  *  0.3 2013-03-29 活路の表示。
  8  *  0.2 2012-05-17 レイヤーに対応。局面オブジェクト対応。
  9  *  0.1 2012-05-15 新規作成。 goban02.js より独立。星の表示に対応。
 10  */
 11 
 12 var PIXEL_COL = 22;
 13 var PIXEL_ROW = 24;
 14 var PIXEL_CHAR = 24;
 15 
 16 /**
 17  * @fileOverview Board - 碁盤オブジェクト
 18  * @version 0.5
 19  * @author たかはしのんき
 20  * 
 21  */
 22 
 23 /**
 24  * 碁盤オブジェクトを作成します。
 25  * @constructor
 26  * @this {Board}
 27  * @param ro 路数
 28  * @param context 描画先のコンテキスト
 29  * @since 0.1
 30  */
 31 Board = function(ro, context) {
 32 	this.ro = ro;
 33 	this.boardSize = {					// 碁盤のサイズ[ピクセル]
 34 		width : PIXEL_COL * (ro + 1),
 35 		height : PIXEL_ROW * (ro + 1)
 36 	};
 37 	this.boardOffset = {				// 碁盤のテーブルからのオフセット[ピクセル]
 38 		x : Math.floor((context.canvas.width - this.boardSize.width) / 2),
 39 		y : Math.floor((context.canvas.height 
 40 				+ PIXEL_CHAR * 2 - this.boardSize.height) / 2)
 41 	};
 42 	this.gridSize = {					// 格子全体のサイズ[ピクセル]
 43 		width : PIXEL_COL * (ro - 1) + 2,
 44 		height : PIXEL_ROW * (ro - 1) + 2
 45 	};
 46 	this.gridOffset = {					// 格子全体の碁盤からのオフセット[ピクセル]
 47 		x : Math.floor((this.boardSize.width - this.gridSize.width) / 2),
 48 		y : Math.floor((this.boardSize.height - this.gridSize.height) / 2)
 49 	};
 50 	this.p = new Pos(ro);				// 局面オブジェクト
 51 	this.prisoner = new Array(2);		// アゲハマ
 52 };
 53 
 54 Board.prototype = {
 55 	
 56 	/**
 57 	 * 着手します。
 58 	 * @param col 石の桁位置を指定します。
 59 	 * @param row 石の行位置を指定します。
 60 	 * @param context3 石を描画するコンテキスト
 61 	 * @param context2 影を描画するコンテキスト
 62 	 * @since 0.2
 63 	 */
 64 	move : function(col, row, context3, context2) {
 65 		passButton[this.p.turn].disabled = true;
 66 		var turn = this.p.turn;
 67 		var moved = this.p.move(col, row);	// 着手禁止点は false
 68 		var mv = new Move(col, row);
 69 		if (!mv.isPass(this.ro)) {
 70 //			setWatch();
 71 			if (moved) {
 72 				this.prisoner[P_BLACK] = this.p.prisoner[P_BLACK];
 73 				this.prisoner[P_WHITE] = this.p.prisoner[P_WHITE];
 74 //				rec.move(col, row);
 75 				this.drawStone(col, row, turn, context3, context2);
 76 				auClick.play();
 77 				if (this.p.d.abs()) {
 78 					this.removeStones(this.p.d, this.p.turn, context3, context2);
 79 					showPrisoner(turn, this.prisoner[turn - 1]);
 80 				}
 81 				showScore(BLACK, this.p.liberty[P_BLACK] + this.p.w.inv().abs());
 82 				showScore(WHITE, this.p.liberty[P_WHITE] + this.p.b.inv().abs());
 83 			} else
 84 				auError.play();
 85 		}
 86 		passButton[this.p.turn].disabled = false;
 87 		return moved;
 88 	},
 89 	
 90 	/**
 91 	 * 机上のオフセット座標から碁盤の[パス]ボタンの処理
 92 	 * @since 0.2
 93 	 */
 94 	offsetToMove : function(x, y) {
 95 		var col = Math.floor((x - this.boardOffset.x - this.gridOffset.x / 2) / PIXEL_COL) + 1;
 96 		var row = Math.floor((y - this.boardOffset.y - this.gridOffset.y / 2) / PIXEL_ROW) + 1;
 97 		var mv = new Move(col, row);
 98 		if (1 <= col && col <= this.ro && 1 <= row && row <= this.ro)
 99 			mv.onBoard = true;
100 		else
101 			mv.onBoard = false;
102 		return mv;
103 	},
104 
105 	/**
106 	 * 碁盤を初期状態に戻します。
107 	 * @since 0.2
108 	 */
109 	clear : function() {
110 		this.p.clear();						// 局面オブジェクト
111 		this.prisoner = [0, 0];				// アゲハマ
112 	},
113 
114 	/**
115 	 * 碁盤を描画します。
116 	 * @param s 影の長さ(高さ/2)
117 	 * @param context 描画先のコンテキスト
118 	 * @since 0.1
119 	 */
120 	drawBoard : function(s, context) {
121 		var x = this.boardOffset.x;
122 		var y = this.boardOffset.y;
123 		var ro = this.ro;
124 		var dx = PIXEL_COL;
125 		var dy = PIXEL_ROW;
126 		var width = this.boardSize.width;
127 		var height = this.boardSize.height;
128 		var gwidth = this.gridSize.width;
129 		var gheight = this.gridSize.height;
130 		var gx = x + this.gridOffset.x;
131 		var gy = y + this.gridOffset.y;
132 		// 碁盤の影
133 		context.beginPath();
134 		context.moveTo(x, y);
135 		context.lineTo(x + width, y);
136 		context.lineTo(x + width + s, y + s);
137 		context.lineTo(x + width + s, y + height + s);
138 		context.lineTo(x + s, y + height + s);
139 		context.lineTo(x, y + height);
140 		context.closePath();
141 		context.fillStyle = "black";
142 		context.globalAlpha = 0.5;
143 		context.fill();
144 		// 碁盤
145 		context.beginPath();
146 		context.rect(x, y, width, height);
147 		context.fillStyle = "burlywood";
148 		context.globalAlpha = 1.0;
149 		context.fill();
150 		// 木目
151 		this.drawWoodGrain(x, y, width, height, context);
152 		// 格子
153 		context.fillStyle = "black";
154 		var x1, y1, lwidth;
155 		// (横の格子線)
156 		x1 = gx;
157 		for (var row = 1; row <= ro; row++) {
158 			if (row == 1)
159 				y1 = gy + (row - 1) * dy;
160 			else
161 				y1 = gy + 1 + (row - 1) * dy;
162 			if (row == 1 || row == ro)
163 				lwidth = 2;
164 			else
165 				lwidth = 1;
166 			context.beginPath();
167 			context.rect(x1, y1, gwidth, lwidth);
168 			context.fill();
169 		}
170 		// 縦の格子線
171 		y1 = gy;
172 		for (var col = 1; col <= ro; col++) {
173 			if (col == 1)
174 				x1 = gx + (col - 1) * dx;
175 			else
176 				x1 = gx + 1 + (col - 1) * dx;
177 			if (col == 1 || col == ro)
178 				lwidth = 2;
179 			else
180 				lwidth = 1;
181 			context.beginPath();
182 			context.rect(x1, y1, lwidth, gheight);
183 			context.fill();
184 		}
185 		if (ro % 2 == 1) {			// 天元
186 			this.drawStar(Math.floor(ro / 2) + 1, Math.floor(ro / 2) + 1, context);
187 		}
188 		if (ro == 9) {				// 3線隅の星
189 			this.drawStar(3, 3, context);
190 			this.drawStar(ro - 2, 3, context);
191 			this.drawStar(3, ro - 2, context);
192 			this.drawStar(ro - 2, ro - 2, context);
193 		}
194 		if (ro == 13 || ro == 19) {	// 4線隅の星
195 			this.drawStar(4, 4, context);
196 			this.drawStar(ro - 3, 4, context);
197 			this.drawStar(4, ro - 3, context);
198 			this.drawStar(ro - 3, ro - 3, context);
199 		}
200 		if (ro == 19) {				// 4線辺の星
201 			this.drawStar(ro / 2 + 1, 4, context);
202 			this.drawStar(4, ro / 2 + 1, context);
203 			this.drawStar(ro - 3, ro / 2 + 1, context);
204 			this.drawStar(ro / 2 + 1, ro - 3, context);
205 		}
206 	},
207 
208 	/**
209 	 * 石を描画します。
210 	 * @param col 石の桁位置を指定します。
211 	 * @param row 石の行位置を指定します。
212 	 * @param stone 石 (BLACK か WHITE) を指定します。
213 	 * @param context3 描画先のコンテキスト
214 	 * @param context2 影を描画するコンテキスト
215 	 * @since 0.2
216 	 */
217 	drawStone : function(col, row, stone, context3, context2) {
218 		var x = this.boardOffset.x + this.gridOffset.x + 1 +
219 				(col - 1) * PIXEL_COL; 
220 		var y = this.boardOffset.y + this.gridOffset.y + 1 +
221 				(row - 1) * PIXEL_ROW;
222 		if (stone == BLACK)
223 			drawObject(x - (PX_STONE / 2), y - (PX_STONE / 2), Z2_STONE, imgb, context3, context2);
224 		else if (stone == WHITE)
225 			drawObject(x - (PX_STONE / 2), y - (PX_STONE / 2), Z2_STONE, imgw, context3, context2);
226 	},
227 
228 	/**
229 	 * 石を消します。
230 	 * @param d 取られる石を表す BVecter を指定します。
231 	 * @param stone 取られる石 (BLACK か WHITE) を指定します。
232 	 * @param context3 描画先のコンテキスト
233 	 * @param context2 影を描画するコンテキスト
234 	 * @since 0.2
235 	 */
236 	removeStones : function(d, stone, context3, context2) {
237 		for (var i = 1; i <= d.order; i++)
238 			if (d.getValue(i) == 1) {
239 				var mv = this.p.toMove(i);
240 				var x = this.boardOffset.x + this.gridOffset.x + 1 +
241 					(mv.col - 1) * PIXEL_COL; 
242 				var y = this.boardOffset.y + this.gridOffset.y + 1 +
243 					(mv.row - 1) * PIXEL_ROW;
244 				context3.clearRect(x - (PX_STONE / 2), y - (PX_STONE / 2), PIXEL_COL, PIXEL_ROW);
245 				context2.clearRect(x - (PX_STONE / 2) + Z2_STONE, y - (PX_STONE / 2) + Z2_STONE, PIXEL_COL, PIXEL_ROW);
246 				// 碁笥の蓋にアゲハマのイメージを表示
247 				var r = Math.random() * PX_LID / 3;
248 				var a = Math.random() * 2 * Math.PI;
249 				if (stone == BLACK) {
250 					var xs = offsetWLid.x + Math.floor((PX_LID / 2) + r * Math.cos(a) - (PX_STONE / 2));
251 					var ys = offsetWLid.y + Math.floor((PX_LID / 2) + r * Math.sin(a) - (PX_STONE / 2));
252 					drawObject(xs, ys, Z2_STONE, imgb, context2, context2);
253 				} else {
254 					var xs = offsetBLid.x + Math.floor((PX_LID / 2) + r * Math.cos(a) - (PX_STONE / 2));
255 					var ys = offsetBLid.y + Math.floor((PX_LID / 2) + r * Math.sin(a) - (PX_STONE / 2));
256 					drawObject(xs, ys, Z2_STONE, imgw, context2, context2);
257 				}
258 			}
259 	},
260 	/**
261 	 * 星を描画します。
262 	 * @param col 星の桁位置を指定します。
263 	 * @param row 星の行位置を指定します。
264 	 * @param context 描画先のコンテキスト
265 	 * @since 0.1
266 	 */
267 	drawStar : function(col, row, context) {
268 		var x = this.boardOffset.x + this.gridOffset.x + 1 +
269 				(col - 1) * PIXEL_COL - 2; 
270 		var y = this.boardOffset.y + this.gridOffset.y + 1 +
271 				(row - 1) * PIXEL_ROW - 2;
272 		context.drawImage(imgs, x, y);
273 	},
274 
275 	/**
276 	 * 木目を描画します。
277 	 * @param x 左端座標
278 	 * @param y 上端座標
279 	 * @param width 描画する幅
280 	 * @param height 描画する高さ
281 	 * @param context 描画先のコンテキストを指定します。
282 	 * @since 0.1
283 	 */
284 	drawWoodGrain : function(x, y, width, height, context) {
285 		context.strokeStyle = "rgb(192, 153, 86)";	// 木目の色
286 		var xb0 = x;
287 		var xb1 = x + width;
288 		var yb0 = y;
289 		var yb1 = y + height;
290 		var xo, yo, xw, yw;
291 		var xc = xb0 + (xb1 - xb0) * 5 / 7;			// 木目の中心
292 		var yc = yb0 + (yb1 - yb0) * 5 / 4;
293 		for (var rw = 5; rw <= 460; rw += 5) {
294 			xo = xc + rw;
295 			yo = yc;
296 			for (var theta = 0; theta <= 2 * Math.PI;
297 					theta += 2 * Math.PI / 19) {
298 				xw = xc + Math.floor(rw * Math.cos(theta));
299 				yw = yc + 13 * Math.floor(rw * Math.sin(theta));
300 				var xw0 = xo, xw1 = xw, yw0 = yo, yw1 = yw;
301 				if (yw1<yb0 && yb0<yw0) {			//上辺でクリッピング
302 					xw1 = xw0+(xw1-xw0)*(yb0-yw0)/(yw1-yw0);
303 					yw1 = yb0;
304 				} else if (yw0<yb0 && yb0<yw1) {	//上辺でクリッピング
305 					xw0 = xw1+(xw0-xw1)*(yb0-yw1)/(yw0-yw1);
306 					yw0 = yb0;
307 				}
308 				if (yw1<yb1 && yb1<yw0) {			//下辺でクリッピング
309 					xw0 = xw1+(xw0-xw1)*(yb1-yw1)/(yw0-yw1);
310 					yw0 = yb1;
311 				} else if (yw0<yb1 && yb1<yw1) {	//下辺でクリッピング
312 					xw1 = xw0+(xw1-xw0)*(yb1-yw0)/(yw1-yw0);
313 					yw1 = yb1;
314 				}
315 				if (xw1<xb0 && xb0<xw0) {			//左辺でクリッピング
316 					xw1 = xb0;
317 					yw1 = yw0+(yw1-yw0)*(xb0-xw0)/(xw1-xw0);
318 				} else if (xw0<xb0 && xb0<xw1) {	//左辺でクリッピング
319 					xw0 = xb0;
320 					yw0 = yw1+(yw0-yw1)*(xb0-xw1)/(xw0-xw1);
321 				}
322 				if (xw1<xb1 && xb1<xw0) {			//右辺でクリッピング
323 					xw0 = xb1;
324 					yw0 = yw1+(yw0-yw1)*(xb1-xw1)/(xw0-xw1);
325 				} else if (xw0<xb1 && xb1<xw1) {	//右辺でクリッピング
326 					xw1 = xb1;
327 					yw1 = yw0+(yw1-yw0)*(xb1-xw0)/(xw1-xw0);
328 				}
329 				if (xb0<=xw0 && xw0<=xb1 && xb0<=xw1 && xw1<=xb1 &&
330 						yb0<=yw0 && yw0<=yb1 && yb0<=yw1 && yw1<=yb1) {
331 					context.beginPath();
332 					context.moveTo(xw0, yw0);
333 					context.lineTo(xw1, yw1);
334 					context.stroke();
335 				}
336 				xo = xw;
337 				yo = yw;
338 			}
339 		}
340 	}
341 };
342