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