1 /*
  2  * Copyright (c) 2012-2014 Nonki Takahashi. All rights reserved.
  3  *
  4  * History:
  5  *  0.6 2014-01-29 Changed Move objcet interface.
  6  *  0.5 2013-06-15 Supported JsDoc Toolkit and score display.
  7  *  0.4 2013-04-07 Supported stone number display.
  8  *  0.3 2013-03-29 Supported liberty display.
  9  *  0.2 2012-05-17 Supported layer and posision object.
 10  *  0.1 2012-05-15 Created.  Dropped out from goban02.js.  Supported star display.
 11  */
 12 
 13 var PIXEL_COL = 22;
 14 var PIXEL_ROW = 24;
 15 var PIXEL_CHAR = 24;
 16 
 17 /**
 18  * @fileOverview Board - Go Board Object
 19  * @version 0.6
 20  * @author Nonki Takahashi
 21  */
 22 
 23 /**
 24  * Creates Go Board Object
 25  * @class Represents Go Board
 26  * @this {Board}
 27  * @param {Number} ro number of lines
 28  * @param {Context} context context of the canvas
 29  * @property {Number} ro number of lines
 30  * @since 0.1
 31  */
 32 Board = function(ro, context) {
 33     this.ro = ro;
 34     this.boardSize = {
 35         // size of go board [pixel]
 36         width : PIXEL_COL * (ro + 1),
 37         height : PIXEL_ROW * (ro + 1)
 38     };
 39     this.boardOffset = {
 40         // go board offset from table corner [pixel]
 41         x : Math.floor((context.canvas.width - this.boardSize.width) / 2),
 42         y : Math.floor((context.canvas.height - this.boardSize.height) / 2)
 43     };
 44     this.gridSize = {
 45         // whole grid size [pixel]
 46         width : PIXEL_COL * (ro - 1) + 2,
 47         height : PIXEL_ROW * (ro - 1) + 2
 48     };
 49     this.gridOffset = {
 50         // whole grid offset from go board [pixel]
 51         x : Math.floor((this.boardSize.width - this.gridSize.width) / 2),
 52         y : Math.floor((this.boardSize.height - this.gridSize.height) / 2)
 53     };
 54     // game position object
 55     this.p = new Pos(ro);
 56     // prisoners (captured stones)
 57     this.prisoner = new Array(2);
 58 };
 59 
 60 Board.prototype = {
 61 
 62     /**
 63      * Place next stone
 64      * @example
 65      * brd.move(col, row, context3, context2);
 66      * brd.move(PASS, context3, context2);
 67      * brd.move(new Move(col, row), context3, context2);
 68      * @param {Number} col stone column
 69      * @param {Number} row stone row
 70      * @param {Context} context3 context for drawing stone
 71      * @param {Context} context2 context for drawing shadow
 72      * @return {Boolean} true if the move succeeded
 73      * @since 0.6
 74      */
 75     move : function() {
 76         var mv;
 77         var context2;
 78         var context3;
 79         if (arguments.length == 4) {
 80             mv = new Move(arguments[0], arguments[1]);
 81             context3 = arguments[2];
 82             context2 = arguments[3];
 83         } else if (arguments.length == 3) {
 84             if (arguments[0] instanceof Move)
 85                 mv = arguments[0];
 86             else
 87                 mv = new Move(arguments[0]);
 88             context3 = arguments[1];
 89             context2 = arguments[2];
 90         }
 91         // disable current turn pass button
 92         passButton[this.p.turn].disabled = true;
 93         var turn = this.p.turn;
 94         // inhivited point becomes false
 95         var moved = this.p.move(mv);
 96         if (!mv.isPass) {
 97             // setWatch();  // TODO
 98             if (moved) {
 99                 this.prisoner[P_BLACK] = this.p.prisoner[P_BLACK];
100                 this.prisoner[P_WHITE] = this.p.prisoner[P_WHITE];
101                 // rec.move(col, row);  // TODO
102                 this.drawStone(mv.col, mv.row, turn, context3, context2);
103                 auClick.play();
104                 if (this.p.d.abs()) {
105                     this.removeStones(this.p.d, this.p.turn, context3, context2);
106                     showPrisoner(turn, this.prisoner[turn - 1]);
107                 }
108                 showScore(BLACK, this.p.liberty[P_BLACK] + this.p.w.inv().abs());
109                 showScore(WHITE, this.p.liberty[P_WHITE] + this.p.b.inv().abs());
110             } else
111                 auError.play();
112         }
113         // enable next turn pass button
114         passButton[this.p.turn].disabled = false;
115         return moved;
116     },
117 
118     /**
119      * Convert from canvas offset to move
120      * @param {Number} x X co-ordinate on canvas
121      * @param {Number} y Y co-ordinate on canvas
122      * @return {Move} positon to move
123      * @since 0.2
124      */
125     offsetToMove : function(x, y) {
126         var col = Math.floor((x - this.boardOffset.x - this.gridOffset.x / 2) / PIXEL_COL) + 1;
127         var row = Math.floor((y - this.boardOffset.y - this.gridOffset.y / 2) / PIXEL_ROW) + 1;
128         var mv = new Move(col, row);
129         if (1 <= col && col <= this.ro && 1 <= row && row <= this.ro)
130             mv.onBoard = true;
131         else
132             mv.onBoard = false;
133         return mv;
134     },
135 
136     /**
137      * Restore go board as initial state
138      * @since 0.2
139      */
140     clear : function() {
141         // game position object
142         this.p.clear();
143         // prisoners (captured stones)
144         this.prisoner = [0, 0];
145     },
146 
147     /**
148      * Draw go board
149      * @param {Number} s length of shadow(height / 2)
150      * @param {Context} context context for drawing
151      * @since 0.1
152      */
153     drawBoard : function(s, context) {
154         var x = this.boardOffset.x;
155         var y = this.boardOffset.y;
156         var ro = this.ro;
157         var dx = PIXEL_COL;
158         var dy = PIXEL_ROW;
159         var width = this.boardSize.width;
160         var height = this.boardSize.height;
161         var gwidth = this.gridSize.width;
162         var gheight = this.gridSize.height;
163         var gx = x + this.gridOffset.x;
164         var gy = y + this.gridOffset.y;
165         // shadow of go board
166         context.beginPath();
167         context.moveTo(x, y);
168         context.lineTo(x + width, y);
169         context.lineTo(x + width + s, y + s);
170         context.lineTo(x + width + s, y + height + s);
171         context.lineTo(x + s, y + height + s);
172         context.lineTo(x, y + height);
173         context.closePath();
174         context.fillStyle = "black";
175         context.globalAlpha = 0.5;
176         context.fill();
177         // go board
178         context.beginPath();
179         context.rect(x, y, width, height);
180         context.fillStyle = "burlywood";
181         context.globalAlpha = 1.0;
182         context.fill();
183         // wood grain
184         this.drawWoodGrain(x, y, width, height, context);
185         // grid
186         context.fillStyle = "black";
187         var x1, y1, lwidth;
188         // (horizontal grid)
189         x1 = gx;
190         for (var row = 1; row <= ro; row++) {
191             if (row == 1)
192                 y1 = gy + (row - 1) * dy;
193             else
194                 y1 = gy + 1 + (row - 1) * dy;
195             if (row == 1 || row == ro)
196                 lwidth = 2;
197             else
198                 lwidth = 1;
199             context.beginPath();
200             context.rect(x1, y1, gwidth, lwidth);
201             context.fill();
202         }
203         // (vertical grid)
204         y1 = gy;
205         for (var col = 1; col <= ro; col++) {
206             if (col == 1)
207                 x1 = gx + (col - 1) * dx;
208             else
209                 x1 = gx + 1 + (col - 1) * dx;
210             if (col == 1 || col == ro)
211                 lwidth = 2;
212             else
213                 lwidth = 1;
214             context.beginPath();
215             context.rect(x1, y1, lwidth, gheight);
216             context.fill();
217         }
218         if (ro % 2 == 1) {
219             // tengen
220             this.drawStar(Math.floor(ro / 2) + 1, Math.floor(ro / 2) + 1, context);
221         }
222         if (ro == 9) {
223             // stars on 3rd line corner
224             this.drawStar(3, 3, context);
225             this.drawStar(ro - 2, 3, context);
226             this.drawStar(3, ro - 2, context);
227             this.drawStar(ro - 2, ro - 2, context);
228         }
229         if (ro == 13 || ro == 19) {
230             // stars on 4th line corner
231             this.drawStar(4, 4, context);
232             this.drawStar(ro - 3, 4, context);
233             this.drawStar(4, ro - 3, context);
234             this.drawStar(ro - 3, ro - 3, context);
235         }
236         if (ro == 19) {
237             // stars of 4th line edge
238             this.drawStar(Math.floor(ro / 2) + 1, 4, context);
239             this.drawStar(4, Math.floor(ro / 2) + 1, context);
240             this.drawStar(ro - 3, Math.floor(ro / 2) + 1, context);
241             this.drawStar(Math.floor(ro / 2) + 1, ro - 3, context);
242         }
243     },
244 
245     /**
246      * Draw a stone
247      * @param {Number} col column position of the stone
248      * @param {Number} row row posetion of the stone
249      * @param {Number} stone stone color (BLACK or WHITE)
250      * @param {Context} context3 context for drawing stone
251      * @param {Context} context2 context for drawing shadow
252      * @since 0.2
253      */
254     drawStone : function(col, row, stone, context3, context2) {
255         var x = this.boardOffset.x + this.gridOffset.x + 1 + (col - 1) * PIXEL_COL;
256         var y = this.boardOffset.y + this.gridOffset.y + 1 + (row - 1) * PIXEL_ROW;
257         if (stone == BLACK)
258             drawObject(x - (PX_STONE / 2), y - (PX_STONE / 2), Z2_STONE, imgb, context3, context2);
259         else if (stone == WHITE)
260             drawObject(x - (PX_STONE / 2), y - (PX_STONE / 2), Z2_STONE, imgw, context3, context2);
261     },
262 
263     /**
264      * Remove stones
265      * @param {BVector} d BVecter for captured stones
266      * @param {Number} stone captured stone color (BLACK or WHITE)
267      * @param {Context} context3 context for stone drawn
268      * @param {Context} context2 context for shadow drawn
269      * @since 0.2
270      */
271     removeStones : function(d, stone, context3, context2) {
272         for (var i = 1; i <= d.order; i++)
273             if (d.getValue(i) == 1) {
274                 var mv = this.p.toMove(i);
275                 var x = this.boardOffset.x + this.gridOffset.x + 1 + (mv.col - 1) * PIXEL_COL;
276                 var y = this.boardOffset.y + this.gridOffset.y + 1 + (mv.row - 1) * PIXEL_ROW;
277                 context3.clearRect(x - (PX_STONE / 2), y - (PX_STONE / 2), PIXEL_COL, PIXEL_ROW);
278                 context2.clearRect(x - (PX_STONE / 2) + Z2_STONE, y - (PX_STONE / 2) + Z2_STONE, PIXEL_COL, PIXEL_ROW);
279                 // draw prisoner on lid of bowl
280                 var r = Math.random() * PX_LID / 3;
281                 var a = Math.random() * 2 * Math.PI;
282                 if (stone == BLACK) {
283                     var xs = offsetWLid.x + Math.floor((PX_LID / 2) + r * Math.cos(a) - (PX_STONE / 2));
284                     var ys = offsetWLid.y + Math.floor((PX_LID / 2) + r * Math.sin(a) - (PX_STONE / 2));
285                     drawObject(xs, ys, Z2_STONE, imgb, context2, context2);
286                 } else {
287                     var xs = offsetBLid.x + Math.floor((PX_LID / 2) + r * Math.cos(a) - (PX_STONE / 2));
288                     var ys = offsetBLid.y + Math.floor((PX_LID / 2) + r * Math.sin(a) - (PX_STONE / 2));
289                     drawObject(xs, ys, Z2_STONE, imgw, context2, context2);
290                 }
291             }
292     },
293     /**
294      * Draw a star
295      * @param {Number} col column position for the star
296      * @param {Number} row row position for the star
297      * @param {Context} context context for drawing star
298      * @since 0.1
299      */
300     drawStar : function(col, row, context) {
301         var x = this.boardOffset.x + this.gridOffset.x + 1 + (col - 1) * PIXEL_COL - 2;
302         var y = this.boardOffset.y + this.gridOffset.y + 1 + (row - 1) * PIXEL_ROW - 2;
303         context.drawImage(imgs, x, y);
304     },
305 
306     /**
307      * Draw wood grain
308      * @param {Number} x left co-ordinate
309      * @param {Number} y top co-ordinate
310      * @param {Number} width width to draw
311      * @param {Number} height height to draw
312      * @param {Context} context context to draw
313      * @since 0.1
314      */
315     drawWoodGrain : function(x, y, width, height, context) {
316         // wood grain color
317         context.strokeStyle = "rgb(192, 153, 86)";
318         var xb0 = x;
319         var xb1 = x + width;
320         var yb0 = y;
321         var yb1 = y + height;
322         var xo, yo, xw, yw;
323         // center of wood grain
324         var xc = xb0 + (xb1 - xb0) * 5 / 7;
325         var yc = yb0 + (yb1 - yb0) * 5 / 4;
326         for (var rw = 5; rw <= 460; rw += 5) {
327             xo = xc + rw;
328             yo = yc;
329             for (var theta = 0; theta <= 2 * Math.PI; theta += 2 * Math.PI / 19) {
330                 xw = xc + Math.floor(rw * Math.cos(theta));
331                 yw = yc + 13 * Math.floor(rw * Math.sin(theta));
332                 var xw0 = xo, xw1 = xw, yw0 = yo, yw1 = yw;
333                 if (yw1 < yb0 && yb0 < yw0) {
334                     // clipping on top edge
335                     xw1 = xw0 + (xw1 - xw0) * (yb0 - yw0) / (yw1 - yw0);
336                     yw1 = yb0;
337                 } else if (yw0 < yb0 && yb0 < yw1) {
338                     // clipping on top edge
339                     xw0 = xw1 + (xw0 - xw1) * (yb0 - yw1) / (yw0 - yw1);
340                     yw0 = yb0;
341                 }
342                 if (yw1 < yb1 && yb1 < yw0) {
343                     // clipping on bottom edge
344                     xw0 = xw1 + (xw0 - xw1) * (yb1 - yw1) / (yw0 - yw1);
345                     yw0 = yb1;
346                 } else if (yw0 < yb1 && yb1 < yw1) {
347                     // clipping on bottom edge
348                     xw1 = xw0 + (xw1 - xw0) * (yb1 - yw0) / (yw1 - yw0);
349                     yw1 = yb1;
350                 }
351                 if (xw1 < xb0 && xb0 < xw0) {
352                     // clipping on left edge
353                     xw1 = xb0;
354                     yw1 = yw0 + (yw1 - yw0) * (xb0 - xw0) / (xw1 - xw0);
355                 } else if (xw0 < xb0 && xb0 < xw1) {
356                     // clipping on left edge
357                     xw0 = xb0;
358                     yw0 = yw1 + (yw0 - yw1) * (xb0 - xw1) / (xw0 - xw1);
359                 }
360                 if (xw1 < xb1 && xb1 < xw0) {
361                     // clipping on right edge
362                     xw0 = xb1;
363                     yw0 = yw1 + (yw0 - yw1) * (xb1 - xw1) / (xw0 - xw1);
364                 } else if (xw0 < xb1 && xb1 < xw1) {
365                     // clipping on right edge
366                     xw1 = xb1;
367                     yw1 = yw0 + (yw1 - yw0) * (xb1 - xw0) / (xw1 - xw0);
368                 }
369                 if (xb0 <= xw0 && xw0 <= xb1 && xb0 <= xw1 && xw1 <= xb1 && yb0 <= yw0 && yw0 <= yb1 && yb0 <= yw1 && yw1 <= yb1) {
370                     context.beginPath();
371                     context.moveTo(xw0, yw0);
372                     context.lineTo(xw1, yw1);
373                     context.stroke();
374                 }
375                 xo = xw;
376                 yo = yw;
377             }
378         }
379     }
380 };
381