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