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