1 /*
  2 * Copyright (c) 2012-2014 Nonki Takahashi. All rights reserved.
  3 *
  4 * History:
  5 *  0.9 2014-01-29 Changed to return true when calcVectors is successful.  Changed Move object interface.
  6 *  0.8 2013-06-24 Changed the name of boolean variable playout to isPlayoutRule.  Kept possible move as property.
  7 *  0.7 2013-06-15 Supported JsDoc Toolkit.
  8 *  0.6 2013-04-26 Inhibited moving to an self eye which is not enclosed by atari stones for playout.
  9 *  0.5 2013-04-08 Inhibited moving to an self eye.
 10 *  0.4 2013-03-29 Simplified expressions.  Displayed liberty.
 11 *  0.3 2013-03-18 Supported ko.
 12 *  0.2 2012-05-17 Changed for disabling SixVectors.  Added method clear.
 13 *  0.1 2012-05-15 Created.  Ported from Java version.
 14 *
 15 * Reference:
 16 *  [1] Satou, M., Anada, K. and Tsutsumi, M.: A Mathematical Model for the Game of Go
 17 *      and its Application, The 16th Game Programming Workshop 2011, Vol.2011 No.6,
 18 *      pp.100-103, 2011.
 19 */
 20 
 21 /**
 22  * @fileOverview Go game position object using B-W graph model.
 23  * For arguments in this object, the sequence column then row is used as well as
 24  * Go game does so.
 25  * Be careful that the matrix row i and column j are opposite sequence i, j.
 26  * @author Nonki Takahashi
 27  * @version 0.9
 28  */
 29 // column co-ordinate
 30 var COL = 0;
 31 // row co-ordinate
 32 var ROW = 1;
 33 // space
 34 var SPACE = 0;
 35 // black stone
 36 var BLACK = 1;
 37 // white stone
 38 var WHITE = 2;
 39 // black stone (for prisoner, liberty, score, player name)
 40 var P_BLACK = 0;
 41 // white stone (for prisoner, liberty, score, player name)
 42 var P_WHITE = 1;
 43 // constant to obtain enemy
 44 var OPPOSITE = 3;
 45 // true if changing possible move expression for playout
 46 var isPlayoutRule = true;
 47 
 48 /**
 49  * Object to represent n ro Go board position.
 50  * @class Represents Go board position
 51  * @this {Pos}
 52  * @param {Number} n number of lines
 53  * @property {Number} ro number of lines
 54  * @property {Number} order vector order
 55  * @property {BMatrix} F0 adjacency relationship
 56  * @property {Number[]} prisoner prisoner
 57  * @property {Number[]} liberty number of liberty
 58  * @property {SixVectors} v6 6 vector(option)
 59  * @property {BMatrix} F unit adjacency relationship
 60  * @property {BVector} b black stone or space
 61  * @property {BVector} w white stone or space
 62  * @property {BVector} l space
 63  * @property {BVector} h atari
 64  * @property {BVector} k ko
 65  * @property {BVector} ib black stone liberty
 66  * @property {BVector} iw white stone liberty
 67  * @property {BVector} c movable points
 68  * @property {BVector} watch watched vector
 69  * @property {Number} turn turn (BLACK or WHITE)
 70  * @property {Number} moves number of moves
 71  * @since 0.1
 72  */
 73 Pos = function(n) {
 74     // number of lines
 75     this.ro = n;
 76     // order of vector
 77     this.order = n * n;
 78     // adjacency relationship
 79     this.F0 = new BMatrix(this.order, this.order);
 80     this.initF0();
 81     // prisoner
 82     this.prisoner = new Array(2);
 83     // number of liberty
 84     this.liberty = new Array(2);
 85     try {
 86         // 6 vector
 87         this.v6 = new SixVectors(this);
 88     } catch(e) {
 89         this.v6 = null;
 90     }
 91     this.clear();
 92 };
 93 
 94 Pos.prototype = {
 95     /**
 96      * Initialize adjacency relationship F0
 97      * @since 0.1
 98      */
 99     initF0 : function() {
100         var adj = new Array(4);
101         adj = [
102         // adjacent relative co-ordinate
103         // @formatter:off		  
104 			         [0, -1],
105 			[-1, 0], [0, 0], [1, 0],
106 			         [0, 1]
107         // @formatter:on
108         ];
109         for (var row = 1; row <= this.ro; row++)
110             for (var col = 1; col <= this.ro; col++)
111                 for (var i = 0; i < adj.length; i++) {
112                     var ar = row + adj[i][ROW];
113                     var ac = col + adj[i][COL];
114                     if (ar > 0 && ar <= this.ro && ac > 0 && ac <= this.ro)
115                         this.F0.setValue(this.toIndex(col, row), this.toIndex(ac, ar), 1);
116                 }
117     },
118 
119     /**
120      * Initialize game position
121      * @since 0.3
122      */
123     clear : function() {
124         // unit adjacency relationship
125         this.F = this.F0.clone();
126         // black stone or space
127         this.b = new BVector(this.order, 1);
128         // white stone or space
129         this.w = this.b.clone();
130         // space
131         this.l = this.b.and(this.w);
132         // atari
133         this.h = new BVector(this.order);
134         // ko
135         this.k = new BVector(this.order);
136         // black stone liberty
137         this.ib = new BVector(this.order);
138         // white stone liberty
139         this.iw = new BVector(this.order);
140         // movable points
141         this.c = new BVector(this.order, 1);
142         if (this.v6 != null)
143             this.v6.calcSixVectors(this);
144         // watched BVector
145         this.watch = null;
146         // next turn
147         this.turn = BLACK;
148         // number of moves
149         this.moves = 0;
150         // prisoners (captured stones)
151         this.prisoner = [0, 0];
152         // number of liberty
153         this.liberty = [0, 0];
154     },
155 
156     /**
157      * Set watching BVector
158      * @param {BVector} watch watching BVector. If null then stop watching.
159      * @since 0.1
160      */
161     setWatch : function(watch) {
162         this.watch = watch;
163     },
164 
165     /**
166      * Get watching BVector
167      * @return {BVector} watching BVector or null if not exist
168      * @since 0.1
169      */
170     getWatch : function() {
171         return this.watch;
172     },
173 
174     /**
175      * Convert move position to index for vector element
176      * @param {number} col column of move
177      * @param {Number} row row of move
178      * @return {Number} index (1 origin)
179      * @since 0.1
180      */
181     toIndex : function(col, row) {
182         return (row - 1) * this.ro + col;
183     },
184 
185     /**
186      * Convert index for vector element to move position on board
187      * @param {Number} index (1 origin)
188      * @return {Move} position on board as Move object
189      * @since 0.1
190      */
191     toMove : function(index) {
192         var row = Math.floor((index - 1) / this.ro) + 1;
193         var col = (index - 1) % this.ro + 1;
194         var mv = new Move(col, row);
195         return mv;
196     },
197 
198     /**
199      * Return opposite of turn
200      * @param {Number} turn (BLACK or WHITE)
201      * @return {Number} opposite of turn
202      * @since 0.1
203      */
204     opposite : function(turn) {
205         return OPPOSITE - turn;
206     },
207 
208     /**
209      * Update game position by move.  ro+1 for either col or row means pass.
210      * @example
211      * p.move(new Move(col, row));
212      * @param {Move} mv position to move (to place stone)
213      * @return {Boolean} true for leagal move, false for illeagal move
214      * @since 0.9
215      */
216     move : function(mv) {
217         if (!mv.isPass) {
218             if (!this.isMovable(mv.col, mv.row))
219                 return false;
220             this.calcVectors(mv);
221             if (this.v6 != null)
222                 this.v6.calcSixVectors(this);
223         } else
224             this.calcVectors(mv);
225         this.turn = this.opposite(this.turn);
226         this.moves++;
227         return true;
228     },
229 
230     /**
231      * Check if the point is movable
232      * @param {Number} col column co-ordinate for the point to check
233      * @param {Number} row row co-ordinate for the point to check
234      * @return {Boolean} true if movable
235      * @since 0.3
236      */
237     isMovable : function(col, row) {
238         // the point to check
239         var i = this.toIndex(col, row);
240         var ei = new BVector(this.order);
241         ei.setValue(i, 1);
242         if (this.c.and(ei).equals(ei))
243             return true;
244         return false;
245     },
246 
247     /**
248      * Calculate vectors and matrices for B-W Graph Model.  Targets are:<br>
249      * a - added stone vector by move i<br>
250      * d - deleted unit vector by move i<br>
251      * A - added unit adjacent points matrix by move i<br>
252      * D - reset unit adjacent points matrix by move i<br>
253      * b - black stone or space vector after move<br>
254      * w - white stone or space vector after move<br>
255      * l - space vector after move<br>
256      * F - unit adjacent points matrix after move<br>
257      * k - ko vectoer after move<br>
258      * h - atari stones vector after move<br>
259      * c - movable points after move<br>
260      * ib - black stone liberties after move<br>
261      * iw - white stone liberties after move
262      * @param {Move} mv move i
263      * @return {Boolean} false if error
264      * @since 0.9
265      */
266     calcVectors : function(mv) {
267         // all 0 (zero)
268         var z = new BVector(this.order);
269         if (!mv.isPass) {
270             // move point
271             var i = this.toIndex(mv.col, mv.row);
272             var ei = new BVector(this.order);
273             ei.setValue(i, 1);
274             // points that move point i is unit-adjointed by
275             var Fei = this.F.mul(ei);
276             // points that move point i adjoints to
277             var F0ei = this.F0.mul(ei);
278             if (this.turn == BLACK) {
279                 // black stones that move point is unit-adjonted by and move point
280                 this.a = (Fei.diff(this.w)).or(ei);
281                 // white atari stones that move point is unit-adjointed by
282                 this.d = (Fei.and(this.h)).diff(this.b);
283                 if (F0ei.diff(ei).and(this.b).abs() == 0 && this.d.abs() == 1)
284                     this.k = this.d;
285                 else
286                     this.k = z;
287                 this.b = this.b.or(this.d);
288                 this.w = this.w.diff(ei);
289                 this.prisoner[P_BLACK] += this.d.abs();
290             } else if (this.turn == WHITE) {
291                 // white stones that move point is unit-adjointed by and move point
292                 this.a = (Fei.diff(this.b)).or(ei);
293                 // black atari stones that move point is unit-adjointed by
294                 this.d = (Fei.and(this.h)).diff(this.w);
295                 if (F0ei.diff(ei).and(this.w).abs() == 0 && this.d.abs() == 1)
296                     this.k = this.d;
297                 else
298                     this.k = z;
299                 this.b = this.b.diff(ei);
300                 this.w = this.w.or(this.d);
301                 this.prisoner[P_WHITE] += this.d.abs();
302             }
303             // transposed F (before move)
304             var tF = this.F.tran();
305             this.A = this.a.cross(tF.mul(this.a));
306             // all 1 (one)
307             var o = new BVector(this.order, 1);
308             this.D = this.F0.or((this.d.inv()).cross(o));
309             this.F = (this.F.or(this.A)).and(this.D);
310             this.l = this.b.and(this.w);
311             // black stone liberties
312             this.ib = (tF.mul((this.w).inv())).and(this.l);
313             // number of black stone libeties
314             this.liberty[P_BLACK] = this.ib.abs();
315             // white stone libaties
316             this.iw = (tF.mul((this.b).inv())).and(this.l);
317             // number of white stone libeties
318             this.liberty[P_WHITE] = this.iw.abs();
319         } else {
320             this.k = z;
321         }
322         // transposed F (after move)
323         tF = this.F.tran();
324         for (var i = 1; i <= this.order; i++) {
325             var ei = new BVector(this.order);
326             ei.setValue(i, 1);
327             var tFei = tF.mul(ei);
328             if ((tFei.and(this.l)).abs() == 1)
329                 this.h.setValue(i, 1);
330             else
331                 this.h.setValue(i, 0);
332         }
333         if (isPlayoutRule) {
334             // restricted black move points for playout
335             var rb = new BVector(this.order);
336             // restricted white move points for playout
337             var rw = new BVector(this.order);
338             // all 0 (zero)
339             var z = new BVector(this.order);
340             for (var i = 1; i <= this.order; i++) {
341                 var ei = new BVector(this.order);
342                 ei.setValue(i, 1);
343                 var F0ei_ei = this.F0.mul(ei).diff(ei);
344                 if (F0ei_ei.diff(this.w.inv().diff(this.h)).equals(z))
345                     rb.setValue(i, 1);
346                 if (F0ei_ei.diff(this.b.inv().diff(this.h)).equals(z))
347                     rw.setValue(i, 1);
348             }
349             if (this.turn == BLACK)
350                 this.c = this.F.mul(this.w.xor(this.h)).and(this.l).diff(this.k).diff(rw);
351             else if (this.turn == WHITE)
352                 this.c = this.F.mul(this.b.xor(this.h)).and(this.l).diff(this.k).diff(rb);
353             else
354                 return false;
355         } else {
356             if (this.turn == BLACK)
357                 this.c = this.F.mul(this.w.xor(this.h)).and(this.l).diff(this.k);
358             else if (this.turn == WHITE)
359                 this.c = this.F.mul(this.b.xor(this.h)).and(this.l).diff(this.k);
360             else
361                 return false;
362         }
363         return true;
364     },
365 
366     /**
367      * Returns stone color or space from board intersection co-ordinate
368      * @param {Number} col column co-ordinate
369      * @param {Number} row row co-ordinate
370      * @return {Number} SPACE, BLACK or WHITE (or -1 if error)
371      * @since 0.1
372      */
373     getStone : function(col, row) {
374         if (row <= 0 || row > this.ro || col <= 0 || col > this.ro)
375             return -1;
376         var b1 = this.b.getValue(this.toIndex(col, row));
377         var w1 = this.w.getValue(this.toIndex(col, row));
378         var stone = ((b1 & w1) == 1) ? SPACE : ((b1 == 1) ? BLACK : WHITE);
379         return stone;
380     },
381 
382     /**
383      * Convert board position state to string
384      * @return {String} board position
385      * @since 0.3
386      */
387     toString : function() {
388         var turn = ["-", "B", "W"];
389         var str = new String();
390         str = "#" + this.moves + "\n";
391         str += "F'=\n" + this.F.toString();
392         str += "b'=" + this.b.toString() + "\n";
393         str += "w'=" + this.w.toString() + "\n";
394         str += "l=" + this.l.toString() + "\n";
395         str += "h=" + this.h.toString() + "\n";
396         str += "Σ|db|=" + this.prisoner[P_BLACK] + "\n";
397         str += "Σ|dw|=" + this.prisoner[P_WHITE] + "\n";
398         str += "k=" + this.k.toString() + "\n";
399         if (this.moves > 0) {
400             str += "a=" + this.a.toString() + "\n";
401             str += "d=" + this.d.toString() + "\n";
402             str += "A=\n" + this.A.toString();
403             str += "D=\n" + this.D.toString();
404         }
405         str += "turn:" + turn[this.turn] + "\n";
406         return str;
407     },
408 
409     /**
410      * Convert board position state to TeX string
411      * @return {String} TeX string which represents board position
412      * @since 0.3
413      */
414     toTeX : function() {
415         var turn = ["-", "B", "W"];
416         var str = new String();
417         str = "#" + this.moves + "\n";
418         str += "$$F'=" + this.F.toTeX() + "$$\n";
419         str += "$$\\boldsymbol{b}'=" + this.b.toTeX() + "$$\n";
420         str += "$$\\boldsymbol{w}'=" + this.w.toTeX() + "$$\n";
421         str += "$$\\boldsymbol{l}=" + this.l.toTeX() + "$$\n";
422         str += "$$\\boldsymbol{h}=" + this.h.toTeX() + "$$\n";
423         str += "$$\\sum \\left| \\boldsymbol{d}_b \\right| =" + this.prisoner[P_BLACK] + "$$\n";
424         str += "$$\\sum \\left| \\boldsymbol{d}_w \\right| =" + this.prisoner[P_WHITE] + "$$\n";
425         str += "$$\\boldsymbol{k}=" + this.k.toTeX() + "$$\n";
426         if (this.moves > 0) {
427             str += "$$\\boldsymbol{a}=" + this.a.toTeX() + "$$\n";
428             str += "$$\\boldsymbol{d}=" + this.d.toTeX() + "$$\n";
429             str += "$$A=" + this.A.toTeX() + "$$\n";
430             str += "$$D=" + this.D.toTeX() + "$$\n";
431         }
432         str += "turn:" + turn[this.turn] + "\n";
433         return str;
434     },
435 
436     /**
437      * Convert board position state to picture string
438      * @return {String} picture string of board position
439      * @since 0.1
440      */
441     toPicStr : function() {
442         var figure = ["┼", "●", "○"];
443         var grid = ["┏", "┯", "┓", "┠", "┼", "┨", "┗", "┷", "┛"];
444         var str = new String();
445         str = "";
446         for (var row = 1; row <= this.ro; row++) {
447             for (var col = 1; col <= this.ro; col++) {
448                 var stone = this.getStone(col, row);
449                 if (stone == SPACE) {
450                     var rx = Math.ceil((row - 1) / (this.ro - 1) * 2);
451                     var cx = Math.ceil((col - 1) / (this.ro - 1) * 2);
452                     str += grid[rx * this.ro + cx];
453                 } else
454                     str += figure[stone];
455             }
456             str += "\n";
457         }
458         return str;
459     }
460 };
461