1 /*
  2 * Copyright (c) 2014 Nonki Takahashi. All rights reserved.
  3 *
  4 * History:
  5 *  0.1 2014-01-21 Created from Simulator 0.4.
  6 */
  7 
  8 /**
  9  * @fileOverview Viewer - SGF Viewer
 10  * @name viewer01.js
 11  * @version 0.1
 12  * @author Nonki Takahashi
 13  */
 14 
 15 // common constants
 16 var BLACK = 1;
 17 var WHITE = 2;
 18 // diameter of bowl [pixel]
 19 var PX_BOWL = 134;
 20 // height of bowl / 2 [pixel]
 21 var Z2_BOWL = 20;
 22 // diameter of lid [pixel]
 23 var PX_LID = 110;
 24 // height of lid / 2 [pixel]
 25 var Z2_LID = 8;
 26 // diameter of stone [pixel]
 27 var PX_STONE = 22;
 28 // height of stone / 2 [pixel]
 29 var Z2_STONE = 2;
 30 // diameter of star [pixel]
 31 var PX_STAR = 5;
 32 // height of character [pixel]
 33 var PX_CHAR = 24;
 34 // height of board / 2 [pixel]
 35 var Z2_BOARD = 6;
 36 
 37 // preload images into cache
 38 // black bowl
 39 var imgbb = new Image(PX_BOWL, PX_BOWL);
 40 imgbb.src = "img/blackbowl134.png";
 41 // white bowl
 42 var imgwb = new Image(PX_BOWL, PX_BOWL);
 43 imgwb.src = "img/whitebowl134.png";
 44 // lid
 45 var imgl = new Image(PX_LID, PX_LID);
 46 imgl.src = "img/lid110.png";
 47 // white stone
 48 var imgb = new Image(PX_STONE, PX_STONE);
 49 imgb.src = "img/black22.png";
 50 // black stone
 51 var imgw = new Image(PX_STONE, PX_STONE);
 52 imgw.src = "img/white22.png";
 53 // star
 54 var imgs = new Image(PX_STAR, PX_STAR);
 55 imgs.src = "img/star5.png";
 56 
 57 // preload sound effect into cache
 58 var ext = audioExt();
 59 var auClick = new Audio("se/button6." + ext);
 60 var auSwitch = new Audio("se/se_sad05." + ext);
 61 var auError = new Audio("se/se_sad08." + ext);
 62 
 63 // number of lines (ro)
 64 var ro = 9;
 65 // context for campus of stone layer
 66 var context3;
 67 // context for campus of shade layer
 68 var context2;
 69 // context for campus of board
 70 var context;
 71 // [pass] button
 72 var passButton = new Array(2);
 73 // board object
 74 var brd;
 75 // black lid offset
 76 var offsetBLid = new Object();
 77 // white lid offset
 78 var offsetWLid = new Object();
 79 // interval
 80 var ms = 500;
 81 var outInterval;
 82 var lastPass = false;
 83 var inGame = true;
 84 var rec;
 85 // program information
 86 var programName = "Viewer";
 87 var programVer = "0.1";
 88 
 89 /**
 90  * Draw board image at HTML page load
 91  */
 92 window.onload = function() {
 93     // get <input id="black"> for black stone
 94     passButton[BLACK] = document.getElementById("black");
 95     // get <input id="white"> for white stone
 96     passButton[WHITE] = document.getElementById("white");
 97 
 98     // get canvases and contexts
 99     var canvas = document.querySelector("#table");
100     context = canvas.getContext("2d");
101     var canvas3 = document.querySelector("#stones");
102     context3 = canvas3.getContext("2d");
103     var canvas2 = document.querySelector("#shadow");
104     context2 = canvas2.getContext("2d");
105     // draw a Go set and program title
106     drawNewGoSet();
107     // from black (so disable white pass button)
108     passButton[WHITE].disabled = true;
109     // register a click event
110     canvas3.addEventListener("click", onClick, false);
111     // select input element described as for attribute
112     var inInterval = document.querySelector("#interval");
113     // get output element
114     outInterval = document.querySelector("output");
115     // set onhange event handler
116     inInterval.onchange = function(e) {
117         // assign input element value to output element
118         ms = e.target.value;
119         outInterval.value = ms;
120     };
121 };
122 
123 /**
124  * Draw board, bowls and title 
125  */
126 function drawNewGoSet() {
127     // display title
128     context.fillStyle = "white";
129     context.font = "bold 24px Arial";
130     var title = programName + " " + programVer;
131     var x = context.canvas.width / 2 - PX_CHAR / 1.8 * title.length / 2;
132     var y = PX_CHAR * 2;
133     context.fillText(title, x, y);
134     // draw a Go board at the center of the desk
135     brd = new Board(ro, context);
136     // draw a white lid of bowl on the left of the board
137     offsetWLid.x = Math.floor((brd.boardOffset.x - PX_LID) / 2);
138     offsetWLid.y = brd.boardOffset.y + Math.floor((brd.boardSize.height - PX_LID) / 2);
139     drawObject(offsetWLid.x, offsetWLid.y, Z2_LID, imgl, context, context);
140     // draw a white bowl on the top left corner
141     var xb = offsetWLid.x - Math.floor((PX_BOWL - PX_LID) / 2);
142     var yb = offsetWLid.y - PX_BOWL - Z2_BOWL;
143     drawObject(xb, yb, Z2_BOWL, imgwb, context, context);
144     // draw a black lid of bowl on the right of the board
145     offsetBLid.x = offsetWLid.x + brd.boardOffset.x + brd.boardSize.width;
146     offsetBLid.y = offsetWLid.y;
147     drawObject(offsetBLid.x, offsetBLid.y, Z2_LID, imgl, context, context);
148     // draw a black bowl on the bottom right corner
149     xb = offsetBLid.x - Math.floor((PX_BOWL - PX_LID) / 2);
150     yb = offsetBLid.y + PX_LID + Z2_BOWL;
151     drawObject(xb, yb, Z2_BOWL, imgbb, context, context);
152     // draw board
153     brd.drawBoard(Z2_BOARD, context);
154 }
155 
156 /**
157  * Show number of prisoners
158  * @param {Number} stone color of stone (BLACK or WHITE)
159  * @param {Number} prisoner number of prisoners
160  * @since 0.1
161  */
162 function showPrisoner(stone, prisoner) {
163     if (stone == BLACK) {
164         var p = document.getElementById("pb");
165     } else {
166         var p = document.getElementById("pw");
167     }
168     p.firstChild.nodeValue = prisoner;
169 }
170 
171 /**
172  * Show score
173  * @param {Number} stone color of stone (BLACK or WHITE)
174  * @param {Number} score score
175  * @since 0.1
176  */
177 function showScore(stone, score) {
178     if (stone == BLACK) {
179         var p = document.getElementById("sb");
180     } else {
181         var p = document.getElementById("sw");
182     }
183     p.firstChild.nodeValue = score;
184 }
185 
186 /**
187  * Mouse event handler
188  * for place a stone
189  * @since 0.1
190  */
191 function onClick() {
192     var event = arguments[0];
193     var mx = event.pageX - this.parentNode.offsetLeft;
194     var my = event.pageY - this.parentNode.offsetTop;
195     var mv = brd.offsetToMove(mx, my);
196     if (mv.onBoard) {
197         brd.move(mv.col, mv.row, context3, context2);
198     } else {
199         if (brd.p.turn == BLACK)
200             drawObject(mx - (PX_STONE / 2), my - (PX_STONE / 2), Z2_STONE, imgb, context2, context2);
201         else
202             drawObject(mx - (PX_STONE / 2), my - (PX_STONE / 2), Z2_STONE, imgw, context2, context2);
203         auError.play();
204     }
205 }
206 
207 /**
208  * [Pass] button event handler
209  * @since 0.1
210  */
211 function onPass() {
212     // sound of button
213     auSwitch.play();
214     brd.move(PASS, context3, context2);
215 }
216 
217 /**
218  * Playout timer event handler
219  * @since 0.1
220  */
221 function playout() {
222     if (0 < brd.p.c.abs()) {
223         // if there are possible moves, select one of them randomly
224         // TODO check whether this random number works correctly
225         var i = Math.floor(Math.random() * brd.p.order) + 1;
226         for (; ; i++) {
227             if (brd.p.order < i)
228                 i = 1;
229             if (brd.p.c.getValue(i) == 1)
230                 break;
231         }
232         var mv = brd.p.toMove(i);
233         brd.move(mv, context3, context2);
234         // rec.move(mv);    // TODO involve if needed
235         lastPass = false;
236     } else {
237         // if there are no possible moves, pass
238         auSwitch.play();
239         brd.move(PASS, context3, context2);
240         // rec.move(MOVE_PASS); // TODO involve if needed
241         // both pass becomes game end
242         if (lastPass)
243             inGame = false;
244         lastPass = true;
245     }
246     if (inGame)
247         window.setTimeout(playout, ms);
248 }
249 
250 /**
251  * Replay timer event handler
252  * @since 0.1
253  */
254 function replay() {
255     if (!rec.eod()) {
256         var mv = rec.replay();
257         brd.move(mv, context3, context2);
258         window.setTimeout(replay, ms);
259     }
260 }
261 
262 /**
263  * [Play] button event handler
264  * @since 0.1
265  */
266 function onPlay() {
267     // sound of button
268     auSwitch.play();
269     // clear stone and shadow layer
270     context3.clearRect(0, 0, context3.canvas.width, context3.canvas.height);
271     context2.clearRect(0, 0, context2.canvas.width, context2.canvas.height);
272     // clear prisoners
273     showPrisoner(BLACK, 0);
274     showPrisoner(WHITE, 0);
275     // clear scores
276     showScore(BLACK, 0);
277     showScore(WHITE, 0);
278     // clear board
279     context.clearRect(0, 0, context.canvas.width, context.canvas.height);
280     // ro change test // TODO
281     // ro = Math.round(Math.random()) * 10 + 9;
282     drawNewGoSet();
283     // from black
284     passButton[BLACK].disabled = false;
285     passButton[WHITE].disabled = true;
286     // get game record from SGF
287     textbox = document.querySelector("#sgf");
288     rec = new Rec(textbox.innerHTML);
289     rec.rewind();
290     // play game record
291     lastPass = false;
292     inGame = true;
293     window.setTimeout(replay, ms);
294 }
295 
296 /**
297  * Get available Audio extension for current browser
298  * @returns {String} available Audio extension
299  * @since 0.1
300  */
301 function audioExt() {
302     var ext = "";
303     var audio = new Audio();
304     if (audio.canPlayType("audio/ogg") == "maybe")
305         ext = "ogg";
306     else if (audio.canPlayType("audio/mp3") == "maybe")
307         ext = "mp3";
308     else if (audio.canPlayType("audio/wav") == "maybe")
309         ext = "wav";
310     return ext;
311 }
312 
313 /**
314  * Draw circlar object
315  * @param {Number} x left co-ordiinate
316  * @param {Number} y top co-ordinate
317  * @param {Number} s length of shadow (height / 2)
318  * @param {Image} img object image
319  * @param {Context} context3 context for object
320  * @param {Context} context2 context for shadow
321  * @since 0.1
322  */
323 function drawObject(x, y, s, img, context3, context2) {
324     var r = img.width / 2;
325     context2.beginPath();
326     context2.arc(x + r + s, y + r + s, r, 0, 2 * Math.PI);
327     context2.fillStyle = "black";
328     context2.globalAlpha = 0.5;
329     context2.fill();
330     context3.globalAlpha = 1.0;
331     context3.drawImage(img, x, y);
332 }
333