1<!DOCTYPE html> 2<html> 3 <head> 4 <title>Scrabble Tetris</title> 5 <style> 6 body { 7 position: relative; 8 -webkit-touch-callout: none; 9 -webkit-user-select: none; 10 -khtml-user-select: none; 11 -moz-user-select: none; 12 -ms-user-select: none; 13 user-select: none; 14 } 15 #myCanvas { 16 position: relative; 17 left: 0; 18 top: 0; 19 } 20 </style> 21 </head> 22 <body> 23 <canvas id="myCanvas" width="1600" height="900"></canvas> 24
24<script language="javascript" type="text/javascript"> 25 var tetrominoes = [ 26 [[0,0]], 27 [[0,0],[0,1]], 28 [[0,0],[1,0],[0,1]], 29 [[0,0],[1,0],[-1,0]], 30 [[0,0],[0,1],[1,0],[1,1]], 31 [[0,0],[1,0],[2,0],[-1,0]], 32 [[0,0],[1,0],[-1,0],[0,1]], 33 [[0,0],[1,0],[-1,0],[1,1]], 34 [[0,0],[1,0],[-1,0],[1,-1]], 35 [[0,0],[1,0],[0,1],[-1,1]], 36 [[0,0],[-1,0],[0,1],[1,1]]]; 37 38 var audio_pack_len = 5; 39 var audio_filenames = ["break0","break1","break2","side","down0","down1","down2","down3","land","rotate","send","menu","hold","gameover"]; 40 let audio = new Map(); 41 for(var i = 0; i < audio_filenames.length; i++){ 42 var stri = audio_filenames[i]; 43 var filepath = stri+'.wav'; 44 var newAudio = new Audio(filepath); 45 var na_pack = [0,newAudio]; 46 na_pack[1].load(); 47 audio.set(stri, na_pack); 48 } 49 var ECHO_DELAY = 50; 50 var ECHO_TIME = 1000; 51 function getRandomLetter(){ 52 var choice = Math.random(); 53 for(var i = 0; i < ALPHA_N; i++){ 54 if(freqs[i] > choice){ 55 return i+1; 56 } 57 } 58 } 59 function playAudio(stri){ 60 var pack = audio.get(stri); 61 pack[pack[0]+1].play(); 62 pack[0] = (pack[0]+1)%audio_pack_len; 63 while(pack.length < pack[0]+2){ 64 pack.push(pack[1].cloneNode(true)); 65 pack[pack.length-1].load(); 66 } 67 } 68 class Button{ 69 constructor(index,x,y,w,h,text,op,op_type){ 70 this.index = index; 71 this.x = x; 72 this.y = y; 73 this.w = w; 74 this.h = h; 75 this.col = "#FFCC00"; 76 this.text = text; 77 this.op = -1; 78 if(op != undefined){ 79 this.op = op; 80 this.op_type = op_type; 81 } 82 } 83 drawButton(){ 84 ctx.fillStyle = this.col; 85 if(this.op >= 0){ 86 if(options[this.op] != this.op_type){ 87 ctx.fillStyle = grayedColor; 88 } 89 }else if(this.index < 8){ 90 var g = Math.floor(this.index/2)-2; 91 var dire = (this.index%2 == 1); 92 if((dire == 0 && heights[g] <= BOARD_MIN_H) || 93 (dire == 1 && heights[g] >= BOARD_MAX_H)){ 94 ctx.fillStyle = grayedColor; 95 } 96 }else if(this.index == 8){ 97 if(dicty == null){ 98 ctx.fillStyle = grayedColor; 99 } 100 } 101 ctx.fillRect(this.x,this.y,this.w,this.h); 102 var txt = this.text; 103 if(this.text === "Pause"){ 104 centerText(ctx,"(ESC)",this.x+this.w/2,this.y+77,"#000000",32); 105 if(paused){ 106 txt = "Resume"; 107 } 108 } 109 centerText(ctx,txt,this.x+this.w/2,this.y+45,"#000000",32); 110 } 111 testClick(mX,mY){ 112 return (mX >= this.x && mX < this.x+this.w && mY >= this.y && mY < this.y+this.h); 113 } 114 } 115 class Piece{ 116 constructor(getBomb){ 117 this.pieceType = r(tetrominoes.length); 118 if(getBomb){ 119 this.pieceType = 0; 120 } 121 var len = tetrominoes[this.pieceType].length; 122 this.info = new Array(len); 123 this.x = -1; 124 this.y = -1; 125 for(var t = 0; t < len; t++){ 126 var x = tetrominoes[this.pieceType][t][0]; 127 var y = tetrominoes[this.pieceType][t][1]; 128 var letter = getRandomLetter(); 129 if(getBomb){
130 letter = BOMB_N; 131 } 132 this.info[t] = [x,y,letter]; 133 } 134 this.preRotateInfo = JSON.parse(JSON.stringify(this.info)); 135 } 136 placeOnBoard(){ 137 this.x = 5; 138 var minY = 0; 139 for(var t = 0; t < this.info.length; t++){ 140 var thisY = this.info[t][1]; 141 if(thisY < minY){ 142 minY = thisY; 143 } 144 } 145 this.y = -minY; 146 } 147 } 148 class Game{ 149 constructor(index,grid_W,grid_H) { 150 this.index = index; 151 this.alive = true; 152 this.board = newArray(grid_W,grid_H,0); 153 this.queue = new Array(QUEUE_LENGTH); 154 this.drop = null; 155 this.queueDropTime = -1; 156 this.disappearDropTime = -1; 157 this.vibrationTime = -1; 158 this.score = 0; 159 this.recentWords = []; 160 this.downPress = false; 161 this.hold = null; 162 this.alreadyHeld = false; 163 this.clearArrays(); 164 this.winner = true; 165 this.lands = 0; 166 this.echo = ["",0,true]; 167 this.finalSpeed = 0; 168 this.startTime = Date.now(); 169 this.endTime = null; 170 for(var q = 0; q < QUEUE_LENGTH; q++){ 171 this.queue[q] = new Piece(); 172 } 173 this.pullFromQueue(); 174 } 175 shiftTimers(dur){ 176 this.queueDropTime += dur; 177 this.disappearDropTime += dur; 178 this.vibrationTime += dur; 179 this.startTime += dur; 180 if(this.endTime != null){ 181 this.endTime += dur; 182 } 183 } 184 pullFromQueue(getBomb){ 185 this.drop = this.queue.shift(); 186 this.drop.placeOnBoard(); 187 this.queue[QUEUE_LENGTH-1] = new Piece(getBomb); 188 this.queueDropTime = Date.now(); 189 } 190 dropPiece(){ 191 if(this.hittingGround()){ 192 var isBomb = (this.drop.info[0][2] == BOMB_N); 193 this.imprintPiece(); 194 this.disp = newArrayShape(this.board,0); 195 this.recentWords = []; 196 if(isBomb){ 197 this.explode(); 198 this.makePiecesFall(); 199 this.pullFromQueue(false); 200 }else{ 201 var wordsNow = this.scanBoard(true); 202 var shouldGetBomb = this.shouldGetBombFrom(wordsNow); 203 this.pullFromQueue(shouldGetBomb); 204 } 205 this.alreadyHeld = false; 206 this.checkForAlive(); 207 return false; 208 } 209 this.drop.y += 1; 210 var v = 0; 211 if(games.length == 2){ 212 v = this.index+(this.lands%2)*2; 213 }else{ 214 v = this.drop.y%4; 215 } 216 playAudio("down"+v); 217 return true; 218 } 219 checkForAlive(){ 220 for(var x = 0; x < this.board.length; x++){ 221 if(this.board[x][0] >= 1){ 222 this.finalSpeed = this.getSpeed(); 223 this.endTime = Date.now(); 224 this.alive = false; 225 playAudio("gameover"); 226 if(games.length == 2){ 227 var other = games[1-this.index]; 228 if(other.alive){ 229 this.winner = false; 230 } 231 } 232 } 233 } 234 } 235 clearArrays(){ 236 this.settled = true; 237 this.toRemove = newArrayShape(this.board,false); 238 this.justVanished = newArrayShape(this.board,0); 239 this.disp = newArrayShape(this.board,0); 240 } 241 scanBoard(asLand){ 242 this.clearArrays(); 243 var wordsNow = this.checkForWords(); 244 this.makePiecesFall(); 245 this.checkForOpponentWords(wordsNow); 246 if(wordsNow.length >= 1){ // active 247 playAudio("break"+Math.floor(Math.random()*3));
248 var longest = this.getLongestWord(wordsNow,0); 249 this.echo = [longest[0],Date.now(),false]; 250 this.recentWords = wordsNow; 251 }else if(asLand){ 252 playAudio("land"); 253 this.lands += 1; 254 } 255 return wordsNow; 256 } 257 shouldGetBombFrom(wordsNow){ 258 if(options[1] == 1){ 259 return false; 260 }else{ 261 for(var w = 0; w < wordsNow.length; w++){ 262 if(wordsNow[w][0].length >= 4){ 263 return true; 264 } 265 } 266 return false; 267 } 268 } 269 getLongestWord(wordsNow, minLength){ 270 var recordHolder = null; 271 for(var w = 0; w < wordsNow.length; w++){ 272 var rw = wordsNow[w]; 273 if(rw[0].length >= minLength && (recordHolder == null || rw[0].length >= recordHolder[0].length)){ 274 recordHolder = rw; 275 } 276 } 277 return recordHolder; 278 } 279 checkForOpponentWords(wordsNow){ 280 if(games.length >= 2){ 281 var sendLength = 5; 282 var longest = this.getLongestWord(wordsNow,sendLength); 283 if(longest != null){ 284 this.sendToOpponent(longest); 285 } 286 } 287 } 288 sendToOpponent(rw){ 289 var stri = rw[0]; 290 var oGame = games[1-this.index]; 291 var choiceX = rw[1]; 292 if(rw[2] == 1){ // it's vertical. 293 choiceX -= Math.floor(stri.length/2); 294 if(stri.length%2 == 0){ // it's an even-length word, so you can be on the left or right sides 295 if(Math.random() > 0.5){ 296 choiceX += 1; 297 } 298 } 299 choiceX = Math.min(Math.max(choiceX,0),oGame.board.length-stri.length); 300 } 301 var bottomY = oGame.board[0].length-1; 302 for(var x = choiceX; x < choiceX+stri.length; x++){ 303 for(var y = 0; y < bottomY; y++){ 304 oGame.board[x][y] = oGame.board[x][y+1]; 305 } 306 oGame.board[x][bottomY] = stri.charCodeAt(x-choiceX)-64+ALPHA_N; 307 } 308 oGame.vibrationTime = Date.now(); 309 oGame.settled = false; 310 oGame.disappearDropTime = Date.now(); 311 playAudio("send"); 312 } 313 explode(){ 314 this.clearArrays(); 315 var x = this.drop.x; 316 var y = this.drop.y; 317 var x_start = Math.max(x-1,0); 318 var x_end = Math.min(x+1,this.board.length-1); 319 var y_start = Math.max(y-1,0); 320 var y_end = Math.min(y+1,this.board[0].length-1); 321 for(var ex = x_start; ex <= x_end; ex++){ 322 for(var ey = y_start; ey <= y_end; ey++){ 323 this.toRemove[ex][ey] = true; 324 this.justVanished[ex][ey] = -this.board[ex][ey]; 325 if(this.board[ex][ey] >= 1){ 326 this.score += 1; 327 } 328 } 329 } 330 playAudio("break"+Math.floor(Math.random()*3)); 331 this.recentWords = [["!!!",x,0]]; 332 } 333 imprintPiece(){ 334 for(var t = 0; t < this.drop.info.length; t++){ 335 var at_me_x = this.drop.x+this.drop.info[t][0]; 336 var at_me_y = this.drop.y+this.drop.info[t][1]; 337 var tile = this.drop.info[t][2]; 338 if(this.isInBounds(at_me_x,at_me_y)){ 339 this.board[at_me_x][at_me_y] = tile; 340 } 341 } 342 } 343 isInBounds(x,y){ 344 return (x >= 0 && x < this.board.length && y >= 0 && y < this.board[0].length); 345 } 346 isFilled(x,y){ 347 if(!this.isInBounds(x,y)){ 348 return true; 349 }else if(this.board[x][y] >= 1){ 350 return true; 351 } 352 return false; 353 } 354 hittingGround(){ 355 for(var t = 0; t < this.drop.info.length; t++){ 356 var newX = this.drop.x+this.drop.info[t][0]; 357 var newY = this.drop.y+this.drop.info[t][1]+1; 358 if(this.isFilled(newX,newY)){ 359 return true; 360 } 361 } 362 return false; 363 } 364 moveSide(sign){ 365 for(var t = 0; t < this.drop.info.length; t++){ 366 var beside_x = this.drop.x+this.drop.info[t][0]+sign; 367 var beside_y = this.drop.y+this.drop.info[t][1]; 368 if(this.isFilled(beside_x,beside_y)){ 369 return true; 370 } 371 } 372 playAudio("side"); 373 this.drop.x += sign; 374 } 375 instaDrop(){ 376 while(this.dropPiece()){ 377 } 378 } 379 rotate(sign){ 380 var copy = JSON.parse(JSON.stringify(this.drop.info)); 381 var minX = 9999; 382 var minY = 9999; 383 for(var t = 0; t < copy.length; t++){ 384 var ph = copy[t][0]; 385 copy[t][0] = -sign*copy[t][1]; 386 copy[t][1] = sign*ph; 387 minX = Math.min(minX,copy[t][0]); 388 minY = Math.min(minY,copy[t][1]); 389 } 390 if(this.drop.pieceType == SQUARE_N || this.drop.pieceType == L_N){ 391 for(var t = 0; t < copy.length; t++){ 392 copy[t][0] -= minX; 393 copy[t][1] -= minY; 394 } 395 } 396 var x_shift = this.getFittingXshift(copy); 397 if(x_shift != null){ 398 this.drop.x += x_shift; 399 playAudio("rotate"); 400 this.drop.info = copy; 401 } 402 } 403 404 getFittingXshift(copy){ 405 var x_shift_options = [0,1,-1]; 406 if(this.drop.pieceType == LINE_N){ 407 x_shift_options = [0,1,-1,2,-2]; 408 } 409 var x_shift_choice = null; 410 for(var s = 0; s < x_shift_options.length; s++){ 411 var dx = x_shift_options[s];
412 var valid = true; 413 for(var t = 0; t < copy.length; t++){ 414 var newX = this.drop.x+copy[t][0]+dx; 415 var newY = this.drop.y+copy[t][1]; 416 if(this.isFilled(newX,newY)){ 417 valid = false; 418 } 419 } 420 if(valid){ 421 return dx; 422 } 423 } 424 return null; 425 } 426 427 checkForWord(x,y,LEN,dim,wordsNow){ 428 var word = ""; 429 var valid = true; 430 var margin = options[1] ? 1 : 0; 431 var containsFresh = false; 432 for(var le = 0; le < LEN; le++){ 433 var dx = (1-dim)*le; 434 var dy = (dim)*le; 435 if(!this.isInBounds(x+dx,y+dy)){ 436 return; 437 } 438 var tile = this.board[x+dx][y+dy]; 439 440 if(tile == 0){ 441 valid = false; 442 continue; 443 } 444 word += numToLet(tile); 445 if(tile <= ALPHA_N){ 446 containsFresh = true; 447 } 448 } 449 if(valid && dicty[word.length].has(word) && containsFresh){ 450 for(var perp = -margin; perp <= margin; perp++){ 451 for(var le = -margin; le < LEN+margin; le++){ 452 var dx = (1-dim)*le+(dim)*perp; 453 var dy = (dim)*le+(1-dim)*perp; 454 if(this.isInBounds(x+dx,y+dy)){ 455 if(this.board[x+dx][y+dy] >= 1){ 456 var sign = (perp == 0 && le >= 0 && le < LEN) ? 1 : -1; 457 if(sign == 1 || this.justVanished[x+dx][y+dy] == 0){ 458 this.justVanished[x+dx][y+dy] = sign*this.board[x+dx][y+dy]; 459 } 460 if(!this.toRemove[x+dx][y+dy]){ 461 this.score += 1; 462 } 463 } 464 this.toRemove[x+dx][y+dy] = true; 465 } 466 } 467 } 468 wordsNow.push([word,x,dim]); 469 } 470 } 471 checkForWords(){ 472 var wordsNow = []; 473 var SHORTEST = options[1] ? 4 : 3; 474 for(var len = SHORTEST; len <= MAX_WORD_LENGTH; len++){ 475 for(var x = 0; x <= this.board.length; x++){ 476 for(var y = 0; y < this.board[0].length; y++){ 477 for(var dim = 0; dim < 2; dim++){ 478 this.checkForWord(x,y,len,dim,wordsNow); 479 } 480 } 481 } 482 } 483 return wordsNow; 484 } 485 makePiecesFall(){ 486 for(var x = 0; x < this.board.length; x++){ 487 for(var y = 0; y < this.board[0].length; y++){ 488 if(this.toRemove[x][y]){ 489 this.settled = false; 490 for(var fy = y; fy >= 1; fy--){ 491 this.board[x][fy] = this.board[x][fy-1]; 492 this.disp[x][fy] = this.disp[x][fy-1]; 493 this.disp[x][fy] -= 1; 494 } 495 this.board[x][0] = 0; 496 this.disappearDropTime = Date.now(); 497 this.vibrationTime = Date.now(); 498 } 499 } 500 } 501 } 502 pressDown(){ 503 if(!this.downPress){ 504 this.downPress = true; 505 var now = Date.now(); 506 if(this.queueDropTime < now-this.getTimeToDrop()){ 507 this.queueDropTime = now-this.getTimeToDrop(); 508 } 509 } 510 } 511 iterate(){ 512 if(this.alive){ 513 var now = Date.now(); 514 while(now >= this.queueDropTime+this.getTimeToDrop()){ 515 this.queueDropTime += this.getTimeToDrop(); 516 this.dropPiece(); 517 } 518 if(!this.settled && now >= this.disappearDropTime+this.getTimeToSpirit()){ 519 this.settled = (this.scanBoard(false).length == 0); 520 } 521 if(!this.echo[2] && Date.now() >= this.echo[1]+ECHO_DELAY){ 522 speak(this.echo[0],1.0); 523 this.echo[2] = true; 524 } 525 } 526 } 527 getShake(){ 528 var timePassed = getNow()-this.vibrationTime; 529 var shake = 12*Math.pow(0.005,timePassed/1000); 530 if(shake < 0.1){ 531 return 0; 532 } 533 var choice = (frand(timePassed,0))*2-1; 534 return choice*shake; 535 } 536 getTimeToDrop(){ 537 var val = 1000/this.getSpeed(); 538 if(this.downPress){ 539 val /= 8; 540 } 541 return val; 542 } 543 listRecentWords(){ 544 var result = ""; 545 for(var w = 0; w < this.recentWords.length; w++){ 546 var rw = this.recentWords[w][0]; 547 if(w >= 1){ 548 result += ", "; 549 } 550 result += rw; 551 } 552 return result; 553 } 554 holdPiece(){ 555 if(!this.alreadyHeld){ 556 if(this.hold == null){ 557 this.hold = this.drop; 558 this.hold.info = this.hold.preRotateInfo; 559 this.pullFromQueue(); 560 }else{ 561 var ph = this.drop; 562 this.drop = this.hold; 563 this.hold = ph; 564 this.hold.info = this.hold.preRotateInfo; 565 this.drop.placeOnBoard(); 566 } 567 this.alreadyHeld = true; 568 playAudio("hold"); 569 this.queueDropTime = Date.now(); 570 } 571 } 572 getSpeed(){ 573 if(!this.alive){ 574 return this.finalSpeed; 575 } 576 if(!options[2]){ 577 return 1.0; 578 } 579 if(games.length == 1){ 580 return 1+this.score/100; 581 }else{ 582 var other = games[1-this.index]; 583 if(!other.alive){ 584 return 1+this.score/100; 585 } 586 return 1+other.score/100; 587 } 588 } 589 getTimeToSpirit(){ 590 return 2000/this.getSpeed(); 591 } 592 getExtremeDPY(sign){ 593 var record = -sign*9999; 594 for(var t = 0; t < this.drop.info.length; t++){ 595 var at_me_y = this.drop.y+this.drop.info[t][1]; 596 if(sign*at_me_y > sign*record){ 597 record = at_me_y; 598 } 599 } 600 return record; 601 } 602 } 603 function newArrayShape(example, value){ 604 var W = example.length; 605 var H = example[0].length; 606 var arr = new Array(W); 607 for(var x = 0; x < W; x++){ 608 arr[x] = new Array(H); 609 for(var y = 0; y < H; y++){ 610 arr[x][y] = value; 611 } 612 } 613 return arr; 614 } 615 function newArray(W,H,value){ 616 var arr = new Array(W); 617 for(var x = 0; x < W; x++){ 618 arr[x] = new Array(H); 619 for(var y = 0; y < H; y++){ 620 arr[x][y] = value; 621 } 622 } 623 return arr; 624 } 625 function hue_to_RGB(hue,green_suppress){ 626 var hp = (hue*6)%1.0; 627 if(hue < 1/6){ 628 return [255,hp*255*green_suppress,0]; 629 }else if(hue < 2/6){ 630 return [255-hp*255,255*green_suppress,0]; 631 }else if(hue < 3/6){ 632 return [0,255*green_suppress,hp*255]; 633 }else if(hue < 4/6){ 634 return [0,(255-hp*255)*green_suppress,255]; 635 }else if(hue < 5/6){ 636 return [hp*255,0,255]; 637 }else{
638 return [255,0,255-hp*255]; 639 } 640 } 641 loadFreqs(); 642 loadWords(); 643 var c=document.getElementById("myCanvas"); 644 c.onmousedown = onClick; 645 var ctx = c.getContext("2d"); 646 var cW = myCanvas.width; 647 var cH = myCanvas.height; 648 var BOARD_W = 10; 649 var BOARD_MIN_H = 2; 650 var BOARD_MAX_H = 20; 651 var ALPHA_N = 26; 652 var TILE_S = 40; 653 var QUEUE_LENGTH = 5; 654 var BOMB_N = ALPHA_N*2+1; 655 var MAX_WORD_LENGTH = 10; 656 var dicty = null; 657 658 var bgColor = "#000000"; 659 var borderColor = "#808080"; 660 var dropColor = "#FFFFFF"; 661 var spiritColor = "#00FFFF"; 662 var textColor = "#000000"; 663 var statTextColor = "#FFFFFF"; 664 var grayedColor = "#808080"; 665 var explosionColor = "#FFFF00"; 666 var holdColors = ["#00FF00","#008000"]; 667 var games = null; 668 var freqs = null; 669 var wordList = null; 670 var menu = true; 671 var SQUARE_N = 4; 672 var L_N = 2; 673 var LINE_N = 5; 674 var heights = [BOARD_MAX_H,BOARD_MAX_H]; 675 var paused = false; 676 var pauseTime = -1; 677 678 var options = [false,true,true]; 679 var buttons = [new Button(0,30,200,400,70,"1 player",0,false), 680 new Button(1,460,200,400,70,"2 players",0,true), 681 new Button(2,30,670,400,70,"Boring-3 Mode",1,false), 682 new Button(3,460,670,400,70,"Explosive-4 Mode",1,true), 683 new Button(4,30,300,70,70,"<"), 684 new Button(5,360,300,70,70,">"), 685 new Button(6,460,300,70,70,"<"), 686 new Button(7,790,300,70,70,">"), 687 new Button(8,30,770,830,70,"Go"), 688 new Button(9,940,200,300,70,"Zen",2,false), 689 new Button(10,1270,200,300,70,"Speeds Up",2,true)]; 690 691 var SYNTH_COUNT = 10; 692 var synth = window.speechSynthesis; 693 694 var controlsImages = [new Image, new Image]; 695 for(var c = 0; c < 2; c++){ 696 controlsImages[c].src = 'controls'+c+'.png'; 697 } 698 699 speak("Welcome to Scrabble Tetris! Play it immediately or else.",1.0); 700 701 var gameButtons = [new Button(9,cW/2-140,cH-112,130,102,"Pause"), 702 new Button(10,cW-140,cH-80,130,70,"Menu")]; 703 704 async function loadFreqs() { 705 const response = await fetch("freq.tsv"); 706 const freqText = await response.text(); 707 freqs = createFreqTable(freqText); 708 } 709 async function loadWords() { 710 const response = await fetch("dictionary.txt"); 711 const dictText = await response.text(); 712 dicty = createDicty(dictText); 713 } 714 function getEnglishVoices(){ 715 var result = []; 716 var list = synth.getVoices(); 717 for(var v = 0; v < list.length; v++){ 718 var name = list[v].name 719 if(name.includes("English") || name.includes("Welsh") || name.includes("Irish")){ 720 result.push(list[v]); 721 } 722 } 723 return result; 724 } 725 function speak(stri, vol){ 726 var voices = getEnglishVoices(); 727 var utterance = new SpeechSynthesisUtterance(stri); 728 utterance.voice = voices[r(voices.length)]; 729 utterance.volume = vol; 730 synth.speak(utterance); 731 } 732 733 734 735 function createFreqTable(freq_file){ 736 var freq_lines = freq_file.split("\n"); 737 var freq = new Array(ALPHA_N); 738 var total = freq_lines[0].split("\t")[1]; 739 var summer = 0; 740 for(var a = 0; a < ALPHA_N; a++){ 741 var val = Number(freq_lines[1+a].split("\t")[1]); 742 summer += val; 743 freq[a] = summer/total; 744 } 745 return freq; 746 } 747 function createDicty(dict_text){ 748 var result = new Array(MAX_WORD_LENGTH+1); 749 for(var len = 0; len < MAX_WORD_LENGTH+1; len++){ 750 result[len] = new Set(); 751 } 752 var lines = dict_text.split("\n"); 753 for(var i = 0; i < lines.length; i++){ 754 var len = lines[i].length; 755 if(len <= MAX_WORD_LENGTH){ 756 result[len].add(lines[i]); 757 } 758 } 759 return result; 760 } 761 762 self.setInterval(function(){run()},1000/60); 763 function run(){ 764 if(menu){ 765 drawMenu(); 766 }else{ 767 runGame(); 768 drawScreen(); 769 } 770 } 771 function multiLinePrint(ctx, stris, x, y, col, size){ 772 ctx.fillStyle = col; 773 ctx.font = size+"px Helvetica"; 774 for(var s = 0; s < stris.length; s++){ 775 ctx.fillText(stris[s],x,y+s*size); 776 } 777 } 778 function drawMenu(){ 779 ctx.fillStyle = bgColor; 780 ctx.fillRect(0,0,cW,cH); 781 ctx.fillStyle = statTextColor; 782 ctx.font = "96px Helvetica"; 783 ctx.fillText("SCRABBLE TETRIS",30,110); 784 drawButtons(); 785 var stri = [["In Boring-3 Mode: 3+ letter words will clear","4+ letter words will gift you an explosive (!!!)","5+ letter words will be sent to the opponent."], 786 ["In Explosive-4 Mode:","4+ letter words will clear and explode","5+ letter words will be sent to the opponent."]]; 787 multiLinePrint(ctx,stri[options[1]?1:0],880,677,statTextColor,24); 788 789 for(var g = 0; g < options[0]+1; g++){
790 ctx.drawImage(controlsImages[g], 55+430*g, 380, 350, 250); 791 } 792 793 stri = ["When you send an opponent a 5-letter word,","they will receive it in gray tiles.","A word will only \"register\"","if at least one tile in the word is not gray."]; 794 multiLinePrint(ctx,stri,880,787,statTextColor,24); 795 } 796 function drawButtons(){ 797 for(var b = 0; b < buttons.length; b++){ 798 if(!(b >= 6 && b < 8) || options[0] == 1){ 799 var bu = buttons[b]; 800 bu.drawButton(); 801 } 802 } 803 for(var g = 0; g < options[0]+1; g++){ 804 var stri = "Board height: "+heights[g]; 805 centerText(ctx,stri,230+g*430,345,"#FFFFFF",32); 806 } 807 } 808 function runGame(){ 809 if(games != null && !paused){ 810 for(var g = 0; g < games.length; g++){ 811 games[g].iterate(); 812 } 813 } 814 } 815 function drawScreen(){ 816 ctx.fillStyle = bgColor; 817 ctx.fillRect(0,0,cW,cH); 818 if(games != null){ 819 for(var g = 0; g < games.length; g++){ 820 drawPlayer(games[g],200+800*g,14); 821 } 822 } 823 for(var b = 0; b < gameButtons.length; b++){ 824 var bu = gameButtons[b]; 825 bu.drawButton(); 826 } 827 if(paused){ 828 centerText(ctx,"GAME PAUSED",cW/2,cH/2,"#FFFFFF",96); 829 } 830 } 831 function drawPlayer(game,x,y){ 832 var shakeX = game.getShake(); 833 var shakeY = game.getShake(); 834 drawGrid(game,x+shakeX,y+shakeY,TILE_S); 835 drawQueue(game.queue,x+480,y+TILE_S*0.5,TILE_S,grayedColor); 836 drawHold(game,x-90,y); 837 drawStats(game,x,y+TILE_S*game.board[0].length); 838 drawGameOver(game,x,y); 839 } 840 function drawHold(game,x,y){ 841 if(game.hold != null){ 842 var col = holdColors[game.alreadyHeld ? 1 : 0]; 843 drawQueue([game.hold],x-TILE_S*0.5,y+TILE_S*1.5,TILE_S,col); 844 centerText(ctx,"HOLD",x,y+TILE_S*0.5,col,32); 845 } 846 } 847 function drawGameOver(game,x,y){ 848 if(!game.alive){ 849 ctx.fillStyle = statTextColor; 850 ctx.font = "96px Helvetica"; 851 ctx.fillText("GAME",x+40,y+200); 852 ctx.fillText("OVER",x+40,y+300); 853 if(games.length == 2){ 854 var subtext = (game.winner ? "WIN!" : "LOSE."); 855 ctx.fillText("YOU",x+40,y+500); 856 ctx.fillText(subtext,x+40,y+600); 857 } 858 } 859 } 860 function drawStats(game,x,y){ 861 ctx.fillStyle = statTextColor; 862 ctx.font = "32px Helvetica"; 863 ctx.fillText(game.listRecentWords(),x,y+72); 864 var stri = (options[1] == 1) ? "Score: " : "Bore: "; 865 stri = ((options[2] == 0) ? "Zen " : "") +stri; 866 ctx.fillText(stri+game.score,x,y+40); 867 var sp = game.getSpeed().toFixed(2); 868 ctx.fillText(sp+" dps",x+240,y+40); 869 870 var now = paused ? pauseTime : Date.now(); 871 var gameTime = (game.alive ? now : game.endTime) - game.startTime; 872 ctx.fillText(timify(gameTime),x-145,y+40); 873 } 874 function timify(val){ 875 var mins = ""+Math.floor(val/60000); 876 var ten_sec = ""+Math.floor(val/10000)%6; 877 var sec = ""+Math.floor(val/1000)%10; 878 var sec_tenth = ""+Math.floor(val/100)%10; 879 return mins+":"+ten_sec+sec+"."+sec_tenth; 880 } 881 function drawQueue(queue, grid_X, grid_Y, S, queueColor){ 882 for(var q = 0; q < queue.length; q++){ 883 var py = grid_Y+q*S*3; 884 var piece = queue[q]; 885 var extremes = [999,-999,999,-990]; 886 for(var t = 0; t < piece.info.length; t++){ 887 var tx = piece.info[t][0]; 888 var ty = piece.info[t][1]; 889 extremes[0] = Math.min(extremes[0],tx); 890 extremes[1] = Math.max(extremes[1],tx); 891 extremes[2] = Math.min(extremes[2],ty); 892 extremes[3] = Math.max(extremes[3],ty); 893 } 894 var midX = (extremes[0]+extremes[1])/2; 895 var midY = (extremes[2]+extremes[3])/2; 896 var mar = 0.2;
897 var rx = grid_X+(extremes[0]-midX-mar)*S; 898 var ry = py+(extremes[2]-midY-mar)*S; 899 var rw = (extremes[1]-extremes[0]+1+mar*2)*S; 900 var rh = (extremes[3]-extremes[2]+1+mar*2)*S; 901 var rr = mar*S; 902 ctx.fillStyle = queueColor; 903 ctx.fillRect(rx,ry,rw,rh,rr); 904 905 for(var t = 0; t < piece.info.length; t++){ 906 var ax = piece.info[t][0]-midX; 907 var ay = piece.info[t][1]-midY; 908 var tl = piece.info[t][2]; 909 drawTile(grid_X+ax*S,py+ay*S,0,tl,S,1,0); 910 } 911 } 912 } 913 function getNow(){ 914 if(paused){ 915 return pauseTime; 916 }else{ 917 return Date.now(); 918 } 919 } 920 function drawGrid(game, grid_X, grid_Y, S){ 921 var board = game.board; 922 var drop = game.drop; 923 var timePassed = getNow()-game.disappearDropTime; 924 var fac = Math.min(Math.max(timePassed/game.getTimeToSpirit(),0),1); 925 var decay = 0.5+0.5*Math.cos(fac*Math.PI); 926 927 for(var x = 0; x < board.length; x++){ 928 for(var y = 0; y < board[x].length; y++){ 929 var tile = board[x][y]; 930 var ax = grid_X+S*x; 931 var ay = grid_Y+S*y; 932 drawTile(ax,ay,game.disp[x][y]*S*decay,tile,S,1,0); 933 } 934 } 935 if(decay >= 0.00001){ 936 for(var x = 0; x < board.length; x++){ 937 for(var y = 0; y < board[x].length; y++){ 938 var spirit = game.justVanished[x][y]; 939 var ax = grid_X+S*x; 940 var ay = grid_Y+S*y; 941 if(spirit >= 1){ 942 drawTile(ax,ay,0,spirit,S,decay,2); 943 }else if(spirit <= -1){ 944 drawExplosion(ax,ay,S,decay,timePassed); 945 } 946 } 947 } 948 } 949 if(game.alive){ 950 var fastTimePassed = getNow()-game.queueDropTime; 951 var fastDecay = Math.pow(0.03,fastTimePassed/100); 952 for(var t = 0; t < drop.info.length; t++){ 953 var di = drop.info[t]; 954 var x = drop.x+di[0]; 955 var y = drop.y+di[1]; 956 var tile = di[2]; 957 var ax = grid_X+S*x; 958 var ay = grid_Y+S*y; 959 drawTile(ax,ay,-fastDecay*S,tile,S,1,1); 960 } 961 var timeSince = Date.now()-game.echo[1]; 962 if(timeSince <= ECHO_TIME){ 963 var BW = game.board.length; 964 var BH = game.board[0].length; 965 var size = 0.5-0.5*Math.cos(timeSince/ECHO_TIME*2*Math.PI); 966 var highest_dpy = game.getExtremeDPY(1)-fastDecay; 967 var lowest_dpy = game.getExtremeDPY(-1)-fastDecay; 968 var mid_dpy = (highest_dpy+lowest_dpy)/2; 969 970 var textY = BH*0.3333; 971 if(textY >= lowest_dpy-2 && textY <= highest_dpy+2){ 972 if(textY > mid_dpy){ 973 textY = highest_dpy+2; 974 }else{ 975 textY = lowest_dpy-2; 976 } 977 } 978 centerText(ctx,game.echo[0],grid_X+S*BW*0.5,grid_Y+S*textY+size*65,"#FFCC00",size*130); 979 } 980 } 981 } 982 function getTileColor(tile){ 983 if(tile == BOMB_N){ 984 return rgbToHex([128,128,128]); 985 }else if(tile >= ALPHA_N){ 986 return grayedColor; 987 }else{ 988 return rgbToHex(hue_to_RGB((tile-1)/26,0.65)); 989 } 990 } 991 function componentToHex(c) { 992 var hex = c.toString(16); 993 return hex.length == 1 ? "0" + hex : hex; 994 } 995 function rgbToHex(arr) { 996 var result = "#"; 997 for(var i = 0; i < 3; i++){ 998 result += componentToHex(Math.floor(arr[i])); 999 } 1000 return result; 1001 } 1002 function frand(a, b){ 1003 var sine = Math.sin(a*100+b*37); 1004 var choice = (((sine+1)*100)%1.0); 1005 return choice; 1006 } 1007 function drawExplosion(ax,ay,S,size,tp){ 1008 var R = S*0.5; 1009 var centerX = ax+R; 1010 var centerY = ay+R; 1011 ctx.fillStyle = explosionColor; 1012 ctx.beginPath(); 1013 ctx.moveTo(centerX,centerY); 1014 for(var p = 0; p < 20; p++){ 1015 var far_fac = frand(tp,p*4)*0.3+0.7; 1016 var far_dist = R*size*far_fac; 1017 var far_angle = frand(tp,p*4+1)*Math.PI*2; 1018 ctx.lineTo(centerX+Math.cos(far_angle)*far_dist, 1019 centerY+Math.sin(far_angle)*far_dist); 1020 1021 var close_fac = frand(tp,p*4+2)*0.3; 1022 var close_dist = R*size*close_fac; 1023 var close_angle = frand(tp,p*4+3)*Math.PI*2; 1024 ctx.lineTo(centerX+Math.cos(close_angle)*close_dist, 1025 centerY+Math.sin(close_angle)*close_dist); 1026 } 1027 ctx.fill(); 1028 } 1029 function centerText(ctx,stri,x,y,col,size){ 1030 ctx.fillStyle = col; 1031 ctx.font = size+"px Helvetica"; 1032 var textWidth = ctx.measureText(stri).width; 1033 var textHeight = ctx.measureText(stri).height; 1034 var x_coor = x-textWidth/2-0.5; 1035 ctx.fillText(stri,x_coor,y); 1036 } 1037 function drawTile(ax, ay, ady, tile, S, size, type){ 1038 var M = 1; 1039 var M2 = 2; 1040 if(size == 1 && type != 1){ 1041 ctx.fillStyle = borderColor; 1042 ctx.fillRect(ax,ay,S,S); 1043 ctx.fillStyle = bgColor; 1044 ctx.fillRect(ax+M,ay+M,S-M*2,S-M*2); 1045 } 1046 if(tile >= 1){ 1047 var colors = [getTileColor(tile), dropColor, spiritColor]; 1048 var texts = ["#FFFFFF","#000000","#000000"]; 1049 ctx.fillStyle = colors[type]; 1050 var aax = ax+S*0.5+(M2-S*0.5)*size; 1051 var aay = ay+S+(M2-S)*size+ady; 1052 if(options[1]){ 1053 aay = ay+S*0.5+(M2-S*0.5)*size+ady; 1054 } 1055 var aas = (S-M2*2)*size 1056 ctx.fillRect(aax,aay,aas,aas); 1057 ctx.fillStyle = texts[type]; 1058 var letter = numToLet(tile); 1059 var fs = Math.round(32*size); 1060 ctx.font = fs+"px Helvetica"; 1061 var atx = ax+S*0.5; 1062 var aty = ay+S-(S*0.2)*size+ady; 1063 if(options[1]){ 1064 aty = ay+S*0.5+(S*0.3)*size+ady; 1065 } 1066 centerText(ctx,letter,atx,aty,texts[type],fs); 1067 } 1068 1069 } 1070 function numToLet(n){ 1071 if(n == BOMB_N){ 1072 return "!!!"; 1073 } 1074 var n2 = n; 1075 if(n2 > ALPHA_N){ 1076 n2 -= ALPHA_N; 1077 } 1078 return String.fromCharCode(64+n2); 1079 } 1080 function r(n){ 1081 return Math.floor(Math.random()*n); 1082 } 1083 function startGames(){ 1084 if(dicty == null){ 1085 return; 1086 } 1087 menu = false; 1088 paused = false; 1089 var game_count = options[0] ? 2 : 1; 1090 games = new Array(0); 1091 for(var g = 0; g < game_count; g++){ 1092 games.push(new Game(g, BOARD_W, heights[g])); 1093 } 1094 } 1095 function handleMenuClick(b){ 1096 var bu = buttons[b]; 1097 if(bu.op >= 0){ 1098 options[bu.op] = bu.op_type; 1099 }else if(b == 8){ 1100 startGames(); 1101 }else{ 1102 var g = Math.floor((b-4)/2); 1103 var dire = b%2; 1104 if(g <= options[0]){ 1105 var sign = dire*2-1; 1106 heights[g] = Math.min(Math.max(heights[g]+sign,BOARD_MIN_H),BOARD_MAX_H); 1107 } 1108 } 1109 playAudio("menu"); 1110 } 1111 function pauseGame(){ 1112 paused = !paused; 1113 if(paused){ 1114 pauseTime = Date.now(); 1115 }else{ 1116 var pauseDur = Date.now()-pauseTime; 1117 for(var g = 0; g < games.length; g++){ 1118 games[g].shiftTimers(pauseDur); 1119 } 1120 } 1121 } 1122 function onClick(e){
1123 var mX = e.offsetX; 1124 var mY = e.offsetY; 1125 if(menu){ 1126 for(var b = 0; b < buttons.length; b++){ 1127 var bu = buttons[b]; 1128 if(bu.testClick(mX,mY)){ 1129 handleMenuClick(b); 1130 } 1131 } 1132 }else{ 1133 for(var b = 0; b < gameButtons.length; b++){ 1134 var bu = gameButtons[b]; 1135 if(bu.testClick(mX,mY)){ 1136 if(b == 0){ 1137 pauseGame(); 1138 }if(b == 1){ 1139 menu = true; 1140 } 1141 playAudio("menu"); 1142 } 1143 } 1144 } 1145 } 1146 document.onkeydown = function (e) { 1147 if(!menu){ 1148 var k = e.keyCode; 1149 if(k >= 97){ 1150 k -= 32; 1151 } 1152 var keys = ["ADWESQ","JLIOKU"]; 1153 if(k == 27){ 1154 pauseGame(); 1155 } 1156 for(var g = 0; g < keys.length; g++){ 1157 var game = (games.length == 2 ? games[g] : games[0]); 1158 for(var ki = 0; ki < keys[g].length; ki++){ 1159 if(!paused && k == keys[g].charCodeAt(ki) && game.alive){ 1160 if(ki == 0 || ki == 1){ 1161 game.moveSide(-1+2*ki); 1162 }else if(ki == 2 || ki == 3){ 1163 game.rotate(-1+(ki-2)*2); 1164 }else if(ki == 4){ 1165 game.pressDown(); 1166 }else if(ki == 5){ 1167 game.holdPiece(); 1168 } 1169 } 1170 } 1171 } 1172 } 1173 }; 1174 document.onkeyup = function (e) { 1175 if(!menu){ 1176 for(var g = 0; g < games.length; g++){ 1177 games[g].downPress = false; 1178 } 1179 } 1180 } 1181 1182 </script>
1182 1183 <p><button onclick="location.href='https://www.htwins.net'" style="font-size : 20px">Back to htwins.net</button></p> 1184 </body> 1185</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.