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