From 9788db1a8c069c6f198ba8d53b463d4245b86e1b Mon Sep 17 00:00:00 2001 From: Max Mossberg Date: Sat, 16 Dec 2023 15:15:47 -0500 Subject: [PATCH] sketris-dev input update --- sketris-dev/index.html | 12 ++++++------ sketris-dev/main.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sketris-dev/index.html b/sketris-dev/index.html index 864060f..95763e0 100644 --- a/sketris-dev/index.html +++ b/sketris-dev/index.html @@ -239,18 +239,18 @@

Hold:

-

Rotate Clockwise:

+

Rotate Counterclockwise:

-

+

- +
-

Rotate Counterclockwise:

+

Rotate Clockwise:

-

+

- +

Rotate 180:

diff --git a/sketris-dev/main.js b/sketris-dev/main.js index 0707665..468567f 100644 --- a/sketris-dev/main.js +++ b/sketris-dev/main.js @@ -26,7 +26,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac \************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils.js */ \"./src/utils.js\");\n// import * as LZString from \"lz-string\";\r\n// import { start } from \"repl\";\r\n// import { needValidate } from \"schema-utils\";\r\n\r\n\r\nclass GameBoard {\r\n\r\n constructor(rows=20, cols=10, tileSize=30, spawnArea=2) {\r\n\r\n this.rows = rows;\r\n this.cols = cols;\r\n this.tileSize = tileSize;\r\n this.spawnArea = spawnArea;\r\n this.data = null;\r\n\r\n this.container = document.getElementById(\"boardArea\");\r\n\r\n // this.boardHeight = (rows+this.spawnArea)*tileSize; \r\n // this.boardWidth = cols*tileSize; \r\n\r\n // this.boardHeight = (rows+this.spawnArea)*tileSize; \r\n // this.boardWidth = cols*tileSize; \r\n\r\n this.playfield = new Playfield(this.rows, this.cols, this.tileSize, this.spawnArea);\r\n this.hold = new Hold(this.tileSize);\r\n this.queue = new Queue(this, this.hold, this.tileSize);\r\n\r\n this.sketcher = new Sketcher(this.playfield, () => this.getActivePiece(),\r\n () => this.saveState(),\r\n () => this.pauseActiveRender(),\r\n () => this.resumeActiveRender());\r\n this.history = new BoardHistory();\r\n\r\n this.activePiece = null;\r\n this.pauseActive = false;\r\n this.isPaused = false;\r\n this.qRegen = true;\r\n\r\n\r\n // set up import/export controls\r\n\r\n document.getElementById(\"importButton\").addEventListener(\"click\", (e) => {\r\n let datastring = document.getElementById(\"importText\").value;\r\n this.loadData(datastring);\r\n let el = document.querySelector( ':focus' );\r\n if( el ) el.blur();\r\n\r\n });\r\n document.getElementById(\"clearImport\").addEventListener(\"click\", (e) => {\r\n document.getElementById(\"importText\").value = \"\"\r\n let el = document.querySelector( ':focus' );\r\n if( el ) el.blur();\r\n });\r\n\r\n this.qRegenCheckbox = document.getElementById(\"queueRegen\");\r\n this.qRegenCheckbox.checked = this.qRegen;\r\n this.qRegenCheckbox.addEventListener(\"click\", (e) =>{\r\n this.qRegen = e.target.checked;\r\n this.updateDataPanel();\r\n });\r\n\r\n let holdInput = document.getElementById(\"holdInput\")\r\n holdInput.addEventListener(\"input\", (e) => {\r\n this.hold.setHold(document.getElementById(\"holdInput\").value);\r\n this.updateDataPanel();\r\n })\r\n holdInput.addEventListener(\"keydown\", (e) => {\r\n if(e.key == \"Enter\" || e.key == \"Tab\") {\r\n holdInput.blur();\r\n return;\r\n }\r\n if(e.key == \"Backspace\" || e.key == \"Delete\") {\r\n this.hold.setHold(null);\r\n this.updateDataPanel();\r\n }\r\n document.getElementById(\"holdInput\").value = \"\";\r\n })\r\n\r\n let customQueue = document.getElementById(\"customQueue\")\r\n customQueue.addEventListener(\"input\", (e) => {\r\n this.queue.edit = true;\r\n this.queue.updateQueue(document.getElementById(\"customQueue\").value);\r\n this.spawnPiece();\r\n })\r\n customQueue.addEventListener(\"keydown\", (e) => {\r\n if(e.key == \"Enter\") {\r\n // this.queue.updateQueue();\r\n // this.updateDataPanel();\r\n customQueue.blur();\r\n }\r\n })\r\n\r\n // export buttons\r\n document.getElementById(\"copyAsLink\").addEventListener(\"click\", (e) => {\r\n let link = \"https://mxmoss.me/sketris-dev/?data=\" + document.getElementById(\"exportArea\").value;\r\n // let link = \"localhost:8080/?data=\" + document.getElementById(\"exportArea\").value;\r\n this.setClipboard(link);\r\n e.target.blur();\r\n });\r\n\r\n document.getElementById(\"copyRaw\").addEventListener(\"click\", (e) => {\r\n let data = document.getElementById(\"exportArea\").value;\r\n this.setClipboard(data);\r\n e.target.blur();\r\n });\r\n\r\n\r\n }\r\n\r\n setClipboard(text){\r\n navigator.clipboard.writeText(text).then(\r\n () => {\r\n // console.log(\"success\")\r\n },\r\n () => {\r\n console.log(\"Err: failed to copy to clipboard.\")\r\n },\r\n );\r\n }\r\n\r\n getData(){\r\n let playfieldData = this.playfield.getData();\r\n let queueData = this.queue.getData(false);\r\n\r\n let buffer = new ArrayBuffer();\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.joinBuffers(_utils_js__WEBPACK_IMPORTED_MODULE_0__.encodeNumber(playfieldData, playfieldData.byteLength), buffer)\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.joinBuffers(_utils_js__WEBPACK_IMPORTED_MODULE_0__.encodeNumber(queueData, queueData.byteLength), buffer)\r\n\r\n // behvior settings\r\n let tmp = new Uint8Array(1);\r\n tmp[0] = (this.qRegen & 0x01);\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.joinBuffers(tmp.buffer, buffer);\r\n\r\n let s = _utils_js__WEBPACK_IMPORTED_MODULE_0__.dataToString(buffer);\r\n\r\n return s;\r\n }\r\n\r\n loadData(dataString){\r\n if(dataString == \"\"){\r\n this.data = null;\r\n this.resetBoard();\r\n return;\r\n }\r\n let currentState = this.getData();\r\n try {\r\n // this.history.reset();\r\n let b = _utils_js__WEBPACK_IMPORTED_MODULE_0__.stringToData(dataString);\r\n // load behavior\r\n let tmp = new Uint8Array(b);\r\n this.qRegen = tmp[0] & 0x01;\r\n b = b.slice(1);\r\n\r\n let splits = _utils_js__WEBPACK_IMPORTED_MODULE_0__.splitBuffer(b);\r\n\r\n this.queue.loadData(splits[0]);\r\n this.playfield.loadData(splits[1]);\r\n this.data = dataString;\r\n }\r\n catch {\r\n //restore last state\r\n this.loadData(currentState);\r\n alert(\"Err: Invalid Data\");\r\n }\r\n }\r\n\r\n updateDataPanel(){\r\n document.getElementById(\"holdInput\").value = this.hold.pieceName ? this.hold.pieceName : \"\" ;\r\n document.getElementById(\"customQueue\").value = this.queue.toString();\r\n document.getElementById(\"exportArea\").value = this.getData();\r\n }\r\n \r\n update(){\r\n this.qRegenCheckbox.checked = this.qRegen;\r\n if(!this.isPaused){\r\n this.queue.updateQueue();\r\n }\r\n if((!this.activePiece && this.queue.getCurrent()) || (this.activePiece && this.activePiece.name !== this.queue.getCurrent())){\r\n this.spawnPiece();\r\n }\r\n }\r\n\r\n lstGuide() {\r\n // mirror 0,1 3,4 to tallest\r\n\r\n let getCol = (i) => {return this.playfield.board.map((r) => r[i])}\r\n let topActiveIndex = (c) => {\r\n for(let i=0; i {\r\n for(let i=0; i{\r\n let topA = topActiveIndex(cols[a]);\r\n let topB = topActiveIndex(cols[b]);\r\n\r\n let lowCol = topA >= topB ? cols[a] : cols[b];\r\n let highCol = topA >= topB ? cols[b] : cols[a];\r\n let topInd = topActiveIndex(highCol);\r\n\r\n if(topA == topB){\r\n for(let i=(this.rows + this.spawnArea)-1; i >= 0; i--){\r\n lowCol[i].selected = false;\r\n highCol[i].selected = false;\r\n\r\n };\r\n return;\r\n }\r\n\r\n for(let i=(this.rows + this.spawnArea)-1; i >= 0; i--){\r\n let t = lowCol[i];\r\n if((i >= topInd && !t.active && highCol[i].active) || highCol[i].selected){\r\n t.selected = true;\r\n }else t.selected = false;\r\n\r\n if(i >= topInd){\r\n highCol[i].selected = false;\r\n }\r\n\r\n if(i < topInd && (lowCol[i].selected || highCol[i].selected)){\r\n lowCol[i].selected = true;\r\n highCol[i].selected = true;\r\n }\r\n }\r\n\r\n\r\n }\r\n \r\n mirror(0,4);\r\n mirror(1,3);\r\n\r\n //lst logic\r\n\r\n // return\r\n\r\n // check tops, 2 cases\r\n\r\n // select appropriate\r\n\r\n }\r\n\r\n countToFourGuide() {\r\n let getCol = (i) => {return this.playfield.board.map((r) => r[i])}\r\n let topActiveIndex = (c) => {\r\n for(let i=0; i (this.rows+this.spawnArea) - 1\r\n || tile.x > this.cols - 1 \r\n || playfield.board[tile.y][tile.x].active){\r\n return true;\r\n }\r\n }\r\n return false;\r\n\r\n }\r\n\r\n shiftPiece(dir, p=this.activePiece){\r\n if(p){\r\n // try update\r\n if(dir === \"left\") p.move(0,-1);\r\n else if(dir === \"right\") p.move(0,1);\r\n else if(dir === \"down\") p.move(1,0);\r\n\r\n if(!this.collisionCheck(p, this.playfield)){\r\n return true;\r\n }\r\n\r\n //revert\r\n if(dir === \"left\") p.move(0,1);\r\n else if(dir === \"right\") p.move(0,-1);\r\n else if(dir === \"down\") p.move(-1,0);\r\n }\r\n return false;\r\n }\r\n\r\n shiftInstant(dir, drop=false, p=this.activePiece){\r\n if(p){\r\n if(dir === \"left\") while(this.shiftPiece(\"left\", p)){\r\n if(drop) this.shiftInstant(\"down\", drop, p);\r\n }\r\n else if(dir === \"right\") while(this.shiftPiece(\"right\", p)){\r\n if(drop) this.shiftInstant(\"down\", drop, p);\r\n }\r\n else if(dir === \"down\") while(this.shiftPiece(\"down\", p)){}\r\n }\r\n }\r\n\r\n rotateActive(x){\r\n if(this.activePiece){\r\n let oldRot = this.activePiece.rot;\r\n let mod = (n, m) => ((n % m) + m) % m;\r\n let newRot = mod(this.activePiece.rot + x, 4);\r\n this.activePiece.rotate(newRot);\r\n\r\n let kicks;\r\n if(this.activePiece.name === \"i\") kicks = this.kickTable.i[oldRot][newRot];\r\n else kicks = this.kickTable.n[oldRot][newRot]; \r\n\r\n // TODO null check\r\n if(kicks){\r\n // try all kicks;\r\n for (let i = 0; i < kicks.length; i++) {\r\n let kick = kicks[i];\r\n this.activePiece.move(-kick[1], kick[0]);\r\n\r\n if(!this.collisionCheck(this.activePiece, this.playfield)){\r\n return true;\r\n }\r\n\r\n // revert\r\n this.activePiece.move(kick[1], -kick[0]);\r\n }\r\n };\r\n\r\n // revert\r\n this.activePiece.rotate(oldRot);\r\n }\r\n return false;\r\n }\r\n\r\n hardDrop(p=this.activePiece) {\r\n if(p){\r\n while(this.shiftPiece(\"down\", p)){}\r\n\r\n this.playfield.write(this.activePiece)\r\n this.playfield.clearLines();\r\n this.queue.queueStep();\r\n this.spawnPiece();\r\n }\r\n }\r\n\r\n resetBoard(hard=false) {\r\n if(this.data){\r\n this.loadData(this.data);\r\n }\r\n else{\r\n this.playfield.reset();\r\n this.hold.setHold(\"\");\r\n this.queue.reset();\r\n this.activePiece = null;\r\n this.spawnPiece();\r\n }\r\n }\r\n\r\n // TODO confirm kicks work, think there's some jank with the i\r\n kickTable = {\r\n // srs+ -- uses tetrio 180 kicks\r\n n :{\r\n 0 : {\r\n 0 : [[0,0]],\r\n 1 : [[0,0],[-1,0],[-1,1],[0,-2],[-1,-2]],\r\n 2 : [[0,0],[0,1],[1,1],[-1,1],[1,0],[-1,0]],\r\n 3 : [[0,0],[1,0],[1,1],[0,-2],[1,-2]],\r\n },\r\n 1 : {\r\n 0 : [[0,0],[1,0],[1,-1],[0,2],[1,2]],\r\n 1 : [[0,0]],\r\n 2 : [[0,0],[1,0],[1,-1],[0,2],[1,2]],\r\n 3 : [[0,0],[1,0],[1,2],[1,1],[0,2],[0,1]],\r\n },\r\n 2 : {\r\n 0 : [[0,0],[0,-1],[-1,-1],[1,-1],[-1,0],[1,0]],\r\n 1 : [[0,0],[-1,0],[-1,1],[0,-2],[-1,-2]],\r\n 2 : [[0,0]],\r\n 3 : [[0,0],[1,0],[1,1],[0,-2],[1,-2]],\r\n },\r\n 3 : {\r\n 0 : [[0,0],[-1,0],[-1,-1],[0,2],[-1,2]],\r\n 1 : [[0,0],[-1,0],[-1,2],[-1,1],[0,2],[0,1]],\r\n 2 : [[0,0],[-1,0],[-1,-1],[0,2],[-1,2]],\r\n 3 : [[0,0]],\r\n },\r\n },\r\n i : {\r\n 0 : {\r\n 0 : [[0,0]],\r\n 1 : [[0,0],[-2,0],[1,0],[-2,-1],[1,2]],\r\n 2 : [[0,0],[0,1],[1,1],[-1,1],[1,0],[-1,0]],\r\n 3 : [[0,0],[-1,0],[2,0],[-1,2],[2,-1]],\r\n },\r\n 1 : {\r\n 0 : [[0,0],[2,0],[-1,0],[2,1],[-1,-2]],\r\n 1 : [[0,0]],\r\n 2 : [[0,0],[-1,0],[2,0],[-1,2],[2,-1]],\r\n 3 : [[0,0],[1,0],[1,2],[1,1],[0,2],[0,1]],\r\n },\r\n 2 : {\r\n 0 : [[0,0],[0,-1],[-1,-1],[1,-1],[-1,0],[1,0]],\r\n 1 : [[0,0],[1,0],[-2,0],[1,-2],[-2,1]],\r\n 2 : [[0,0]],\r\n 3 : [[0,0],[2,0],[-1,0],[2,1],[-1,-2]],\r\n },\r\n 3 : {\r\n 0 : [[0,0],[1,0],[-2,0],[1,-2],[-2,1]],\r\n 1 : [[0,0],[-1,0],[-1,2],[-1,1],[0,2],[0,1]],\r\n 2 : [[0,0],[-2,0],[1,0],[-2,-1],[1,2]],\r\n 3 : [[0,0]],\r\n },\r\n\r\n }\r\n }\r\n\r\n}\r\n\r\nclass Playfield {\r\n constructor(rows=20, cols=10, tileSize=30, spawnArea=2, background=\"background\", canvas=\"field\"){\r\n this.rows = rows;\r\n this.cols = cols;\r\n this.tileSize = tileSize;\r\n this.spawnArea = spawnArea;\r\n\r\n\r\n // initialize board\r\n this.board = [];\r\n for (let i = 0; i < this.rows+this.spawnArea; i++) {\r\n let row = [];\r\n for (let j = 0; j < this.cols; j++) {\r\n row.push(new Tile(j,i));\r\n }\r\n this.board.push(row);\r\n }\r\n\r\n this.canvas = document.getElementById(canvas);\r\n this.ctx = this.canvas.getContext(\"2d\");\r\n\r\n this.backgroundCanvas = document.getElementById(background);\r\n this.backgroundCtx = this.backgroundCanvas.getContext(\"2d\");\r\n }\r\n\r\n getData(){\r\n // let colors = Object.entries(Piece.pieces).map((e) => e[1].color).concat([\"#555555\"]);\r\n let colors = [\"#555555\"].concat(Object.entries(Piece.pieces).map((e) => e[1].color));\r\n let getColorIndex = (x) => {\r\n for(const [i,c] of colors.entries()){\r\n if(_utils_js__WEBPACK_IMPORTED_MODULE_0__.colorComp(c, x)){\r\n return i;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n // board to bytes\r\n let totalTiles = 22 * 10 // TODO fix magic number\r\n let buffer = new ArrayBuffer(totalTiles, {maxByteLength: totalTiles})\r\n let byteBoard = new Uint8Array(buffer);\r\n let byteIndex = 0;\r\n\r\n let repeat = 0;\r\n\r\n for (let i = 0; i < totalTiles; i++) {\r\n let tile = this.board[i%22][Math.floor(i/22)]; // TODO fix magic number\r\n let tileData = 0;\r\n\r\n\r\n if(tile.active || tile.ghost || tile.selected){\r\n tileData = (tile.active << 7)\r\n | (tile.ghost << 6)\r\n | (tile.selected << 5)\r\n | (getColorIndex(tile.color) << 2);\r\n }\r\n\r\n if(byteIndex>=1 && tileData == byteBoard[byteIndex-1]){\r\n repeat += 1;\r\n if(i == totalTiles-1){\r\n // write repeat amount\r\n byteBoard[byteIndex-1] = byteBoard[byteIndex-1] | 1; // flip repeat indicator bit\r\n byteBoard[byteIndex] = repeat;\r\n byteIndex += 1;\r\n repeat = 0;\r\n }\r\n }\r\n else{\r\n if(repeat){\r\n // write repeat amount\r\n byteBoard[byteIndex-1] = byteBoard[byteIndex-1] | 1; // flip repeat indicator bit\r\n byteBoard[byteIndex] = repeat;\r\n byteIndex += 1;\r\n repeat = 0;\r\n }\r\n\r\n byteBoard[byteIndex] = tileData;\r\n byteIndex += 1;\r\n }\r\n\r\n // if(i == 16){\r\n // console.log(tile);\r\n // console.log(tileData.toString(2))\r\n // } \r\n\r\n } \r\n\r\n return byteBoard.buffer.slice(0,byteIndex);\r\n }\r\n\r\n loadData(buffer){\r\n this.reset();\r\n let b = new Uint8Array(buffer);\r\n let bIndex = 0;\r\n\r\n // let colors = Object.entries(Piece.pieces).map((e) => e[1].color).concat([\"#555555\"]);\r\n let colors = [\"#555555\"].concat(Object.entries(Piece.pieces).map((e) => e[1].color));\r\n\r\n let totalTiles = 22 * 10 // TODO fix magic number\r\n let i = 0;\r\n\r\n while(i < totalTiles){\r\n if(b[bIndex] & 0x1){\r\n let rNum = b[bIndex+1];\r\n for(let r=0; r> 7;\r\n tile.ghost = (data & 0x40) >> 6;\r\n tile.selected = (data & 0x20) >> 5;\r\n tile.color = colors[(data & 0x1c) >> 2]\r\n i += 1;\r\n }\r\n bIndex += 2;\r\n }\r\n else {\r\n let tile = this.board[i%22][Math.floor(i/22)]; // TODO fix magic number\r\n let data = b[bIndex];\r\n \r\n tile.active = (data & 0x80) >> 7;\r\n tile.ghost = (data & 0x40) >> 6;\r\n tile.selected = (data & 0x20) >> 5;\r\n tile.color = colors[(data & 0x1c) >> 2]\r\n i += 1;\r\n bIndex += 1;\r\n }\r\n }\r\n }\r\n\r\n setField(state){\r\n this.board = state.playfield.copy().board;\r\n }\r\n\r\n getAllTiles(){\r\n let tiles = [];\r\n for (let i = 0; i < this.rows+this.spawnArea; i++) {\r\n for (let j = 0; j < this.cols; j++) {\r\n tiles.push(this.board[i][j]);\r\n }\r\n }\r\n return tiles;\r\n }\r\n\r\n render(paused=false){\r\n // console.log( document.getElementById(\"boardArea\").hasFocus());\r\n // console.log( document.activeElement);\r\n\r\n this.boardHeight = (this.rows+this.spawnArea)*this.tileSize; \r\n this.boardWidth = this.cols*this.tileSize; \r\n\r\n this.canvas.width = this.boardWidth;\r\n this.canvas.height = this.boardHeight;\r\n\r\n this.backgroundCanvas.width = this.boardWidth;\r\n this.backgroundCanvas.height = this.boardHeight;\r\n\r\n // clear \r\n this.backgroundCtx.clearRect(0,0,this.canvas.width,this.canvas.height);\r\n this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);\r\n\r\n //spawn area\r\n this.backgroundCtx.strokeStyle = \"black\";\r\n this.backgroundCtx.globalAlpha = 0.6;\r\n this.backgroundCtx.fillRect(0,0,this.canvas.width,this.spawnArea*this.tileSize);\r\n\r\n // background + grid\r\n this.backgroundCtx.globalAlpha = 1;\r\n this.backgroundCtx.fillRect(0,this.spawnArea*this.tileSize,this.canvas.width,this.canvas.height);\r\n\r\n // grid lines\r\n this.backgroundCtx.strokeStyle = \"gray\";\r\n this.backgroundCtx.globalAlpha = 0.25;\r\n this.backgroundCtx.setLineDash([this.tileSize*0.25, this.tileSize*0.5, this.tileSize*0.25, 0]);\r\n for (let i=this.spawnArea; i < this.rows+3; i++){\r\n this.backgroundCtx.lineWidth = (i === this.spawnArea || i === this.rows+this.spawnArea) ? 1 : 2;\r\n this.backgroundCtx.beginPath();\r\n this.backgroundCtx.moveTo(0, i*(this.tileSize) )\r\n this.backgroundCtx.lineTo(this.boardWidth, i*(this.tileSize));\r\n this.backgroundCtx.stroke();\r\n }\r\n for (let i=0; i < this.cols+1; i++){\r\n this.backgroundCtx.lineWidth = (i === 0 || i === this.cols) ? 1 : 2;\r\n this.backgroundCtx.beginPath();\r\n this.backgroundCtx.moveTo(i*(this.tileSize), this.spawnArea*this.tileSize)\r\n this.backgroundCtx.lineTo(i*(this.tileSize), this.boardHeight);\r\n this.backgroundCtx.stroke();\r\n }\r\n this.backgroundCtx.globalAlpha = 1;\r\n\r\n\r\n // color board tiles\r\n for (let row = 0; row < this.board.length; row++) {\r\n for (let col = 0; col < this.board[row].length; col++) {\r\n let tile = this.board[row][col]\r\n if(tile){\r\n this.colorTile(tile);\r\n }\r\n }\r\n }\r\n\r\n //draw selection outlines\r\n for (let row = 0; row < this.board.length; row++) {\r\n for (let col = 0; col < this.board[row].length; col++) {\r\n let tile = this.board[row][col]\r\n if(tile){\r\n if(tile.selected){\r\n this.ctx.fillStyle = \"rgba(200,200,200, 1)\";\r\n this.ctx.fillRect((col*this.tileSize) - 2, (row*this.tileSize) - 2, this.tileSize+4, this.tileSize+4);\r\n }\r\n }\r\n }\r\n }\r\n\r\n //clear inner\r\n for (let row = 0; row < this.board.length; row++) {\r\n for (let col = 0; col < this.board[row].length; col++) {\r\n let tile = this.board[row][col]\r\n if(tile){\r\n if(tile.selected){\r\n this.ctx.clearRect((col*this.tileSize), (row*this.tileSize), this.tileSize, this.tileSize);\r\n }\r\n }\r\n }\r\n }\r\n\r\n\r\n // redraw tiles\r\n for (let row = 0; row < this.board.length; row++) {\r\n for (let col = 0; col < this.board[row].length; col++) {\r\n let tile = this.board[row][col]\r\n if(tile && tile.selected){\r\n if(!(tile.active||tile.ghost)){\r\n this.ctx.clearRect((col*this.tileSize), (row*this.tileSize), this.tileSize, this.tileSize);\r\n }\r\n else{\r\n this.colorTile(tile);\r\n }\r\n }\r\n }\r\n }\r\n\r\n if(paused){\r\n this.ctx.fillStyle = \"black\";\r\n this.ctx.globalAlpha = 0.4;\r\n this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);\r\n\r\n\r\n //pause symbol\r\n this.ctx.fillStyle = \"gray\";\r\n this.ctx.globalAlpha = 0.6;\r\n this.ctx.fillRect(this.canvas.width*0.5 - 30,this.canvas.height*0.5-30, 20, 60);\r\n this.ctx.fillRect(this.canvas.width*0.5 + 10,this.canvas.height*0.5-30, 20, 60);\r\n }\r\n\r\n }\r\n\r\n write(p){\r\n // write piece to board\r\n for (let i = 0; i < p.tiles.length; i++) {\r\n let tile = p.tiles[i];\r\n this.board[tile.y][tile.x].active = true;\r\n this.board[tile.y][tile.x].color = p.color;\r\n }\r\n }\r\n\r\n colorTile(tile, color=tile.color) {\r\n\r\n if(tile.color){\r\n if(tile.active){\r\n this.ctx.fillStyle = color;\r\n }\r\n else if(tile.ghost){\r\n\r\n let origColor = _utils_js__WEBPACK_IMPORTED_MODULE_0__.parseColor(color);\r\n origColor.a = 0.4;\r\n let ghostColor = \"rgba(\" + origColor.r + \",\" + origColor.g + \",\" + origColor.b + \",\" + origColor.a + \")\";\r\n\r\n this.ctx.fillStyle = ghostColor;\r\n\r\n }\r\n else return;\r\n this.ctx.clearRect(tile.x*this.tileSize, tile.y*this.tileSize, this.tileSize, this.tileSize);\r\n this.ctx.fillRect(tile.x*this.tileSize, tile.y*this.tileSize, this.tileSize, this.tileSize);\r\n }\r\n }\r\n\r\n isFull(row) {\r\n for (let i=0; i < row.length; i++) {\r\n if(!row[i].active) return false;\r\n }\r\n return true;\r\n }\r\n\r\n clearLines() {\r\n let newBoard = [];\r\n let cleared = 0;\r\n // check lines from bottom up and adjust \r\n for(let i=this.board.length-1; i>=0; i--){\r\n let row = this.board[i];\r\n if(this.isFull(row)){\r\n cleared += 1;\r\n }\r\n else{\r\n for(let j=0; j{return null}, saveState= ()=>{}, pauseActive=()=>{}, resumeActive=()=>{}){\r\n this.playfield = playfield;\r\n this.canvas = this.playfield.canvas;\r\n this.getActivePiece = getActivePiece;\r\n this.saveState = saveState;\r\n this.pauseActive = pauseActive;\r\n this.resumeActive = resumeActive;\r\n\r\n this.paletteOpen = false;\r\n\r\n // intialize color palette\r\n\r\n let paletteDiv = document.getElementById(\"palette\");\r\n let colors = [\"#555555\"].concat(Object.entries(Piece.pieces).map((e) => e[1].color));\r\n\r\n for (const [i,c] of colors.entries()) {\r\n let color = document.createElement(\"div\");\r\n color.classList.add(\"paletteColor\");\r\n color.id = \"color-\" + i;\r\n color.style.background = c;\r\n if(i == 0){\r\n color.classList.add(\"activeColor\");\r\n }\r\n\r\n paletteDiv.insertAdjacentElement(\"afterbegin\", color);\r\n this.palette.push(color)\r\n\r\n\r\n color.addEventListener('mousedown', (e) => {\r\n this.drawColor = e.target.style.background;\r\n this.updatePalette();\r\n })\r\n }\r\n\r\n document.getElementById(\"paletteSvg\").addEventListener(\"mousedown\", (e) => {\r\n this.paletteOpen = !this.paletteOpen;\r\n if(this.paletteOpen){\r\n for (const c of document.getElementsByClassName(\"paletteColor\")) {\r\n c.style.animationName = \"showPalette\";\r\n c.style.marginRight = \"0\";\r\n }\r\n }\r\n else{\r\n for (const c of document.getElementsByClassName(\"paletteColor\")) {\r\n c.style.animationName = \"hidePalette\";\r\n c.style.marginRight = \"-4em\";\r\n }\r\n }\r\n })\r\n\r\n // handle events outside of canvas\r\n document.addEventListener('mousedown',(e) => {\r\n if(e.button == 0) this.mouseLeftDown = true; \r\n if(e.button == 2) this.mouseRightDown = true; \r\n this.shiftKey = e.shiftKey;\r\n this.ctrlKey = e.ctrlKey;\r\n this.altKey = e.altKey;\r\n });\r\n document.addEventListener('mouseup',(e) => {\r\n if(e.button == 0) this.mouseLeftDown = false; \r\n if(e.button == 2) this.mouseRightDown = false; \r\n this.shiftKey = e.shiftKey;\r\n this.ctrlKey = e.ctrlKey;\r\n this.altKey = e.altKey;\r\n this.drawMode = false;\r\n });\r\n\r\n // mouse logic\r\n this.canvas.addEventListener('mousedown', (e) => {\r\n this.pauseActive();\r\n this.shiftKey = e.shiftKey;\r\n this.ctrlKey = e.ctrlKey;\r\n this.altKey = e.altKey;\r\n if(e.button == 0) this.mouseLeftDown = true; \r\n if(e.button == 2) this.mouseRightDown = true; \r\n let tile = this.getTile(e.offsetX, e.offsetY);\r\n if(!tile) return;\r\n if(e.button === 1){\r\n // color picker\r\n if(tile.active || tile.ghost){\r\n this.drawColor = tile.color;\r\n this.updatePalette();\r\n }\r\n }\r\n else if(e.shiftKey || e.ctrlKey) {\r\n this.mode = \"select\";\r\n this.selectMode = !tile.selected;\r\n if(this.mouseRightDown){\r\n this.selectRectStart = [tile.x, tile.y];\r\n this.selectRect(this.selectRectStart,[tile.x, tile.y], this.selectMode);\r\n }\r\n else this.select(tile, this.selectMode);\r\n }\r\n else if(tile.selected){\r\n // start drag\r\n // if(tile.selected){\r\n this.mode = \"drag\";\r\n this.startDragTile = tile;\r\n this.selected = this.playfield.getAllTiles().filter((t) => t.selected).map((t) => t.copy());\r\n this.selectionOffsets = this.selected.map((t) => {return {x: t.x - this.startDragTile.x, y: t.y - this.startDragTile.y}}, this);\r\n\r\n\r\n // compute bounds of drag\r\n let lowerX = Math.min(...this.selected.map((t) => t.x));\r\n let lowerY = Math.min(...this.selected.map((t) => t.y));\r\n let higherX = Math.max(...this.selected.map((t) => t.x));\r\n let higherY = Math.max(...this.selected.map((t) => t.y));\r\n\r\n this.dragBounds = {lowX: this.startDragTile.x-lowerX, \r\n highX: this.startDragTile.x+(this.playfield.cols-1)-higherX, \r\n lowY: this.startDragTile.y-lowerY,\r\n highY: this.startDragTile.y+(this.playfield.rows+this.playfield.spawnArea-1)-higherY\r\n };\r\n\r\n this.drag(tile);\r\n // }\r\n // else {\r\n // this.unselectFlag = true;\r\n // }\r\n }\r\n else{ //draw\r\n this.mode = \"draw\";\r\n // this.drawMode = !tile.active || this.drawColor != tile.color;\r\n if(this.altKey){\r\n this.drawMode = !tile.ghost; // || !utils.colorComp(tile.color, this.drawColor);\r\n }\r\n else{\r\n this.drawMode = !tile.active; // || !utils.colorComp(tile.color, this.drawColor);\r\n // this.drawMode = \"ghost\";\r\n }\r\n\r\n this.drawStartColor = tile.color;\r\n tile.color = this.drawMode ? this.drawColor : null;\r\n if(this.mouseRightDown){\r\n this.drawRectStart = [tile.x, tile.y];\r\n this.drawRect(this.drawRectStart,[tile.x, tile.y]);\r\n }\r\n else {\r\n this.draw(tile);\r\n }\r\n }\r\n });\r\n this.canvas.addEventListener('mousemove', (e) => {\r\n let tile = this.getTile(e.offsetX, e.offsetY);\r\n if(!tile) return;\r\n if(this.mode === \"draw\"){\r\n if(this.mouseRightDown){\r\n this.drawRect(this.drawRectStart,[tile.x, tile.y]);\r\n }\r\n else if(this.mouseLeftDown){\r\n this.draw(tile);\r\n }\r\n }\r\n else if (this.mode === \"select\"){\r\n if(this.mouseRightDown){\r\n this.selectRect(this.selectRectStart,[tile.x, tile.y], this.selectMode);\r\n }\r\n else if(this.mouseLeftDown){\r\n this.select(tile, this.selectMode);\r\n }\r\n }\r\n else if (this.mode === \"drag\" && this.mouseLeftDown) {\r\n this.drag(tile);\r\n }\r\n });\r\n this.canvas.addEventListener('mouseup', (e) =>{\r\n this.resumeActive();\r\n this.drawing = [];\r\n if(e.button == 0) this.mouseLeftDown = false; \r\n if(e.button == 2) this.mouseRightDown = false; \r\n this.drawMode = false;\r\n let tile = this.getTile(e.offsetX, e.offsetY);\r\n if(!tile) return;\r\n if(this.mode === \"drag\"){\r\n this.mode = \"select\";\r\n }\r\n\r\n this.saveState()\r\n });\r\n\r\n this.canvas.addEventListener('contextmenu', (e) => {e.preventDefault();e.stopPropagation();return false;}); // disable context menu\r\n this.canvas.addEventListener('focusout', this.resetInputs)\r\n this.canvas.addEventListener('mouseleave', this.resumeActive);\r\n this.canvas.addEventListener('mouseenter', () => {if(this.drawMode) this.pauseActive();});\r\n }\r\n\r\n resetInputs(e){\r\n this.mouseLeftDown = false;\r\n this.mouseRightDown = false;\r\n this.shiftKey = false;\r\n this.ctrlKey = false;\r\n this.altKey = false;\r\n }\r\n\r\n getTile(xOffset,yOffset){\r\n let b = this.canvas.getBoundingClientRect();\r\n let scale = this.canvas.width / parseFloat(b.width);\r\n\r\n\r\n let tileX = Math.floor((xOffset*scale) / (this.playfield.tileSize));\r\n let tileY = Math.floor((yOffset*scale) / (this.playfield.tileSize));\r\n\r\n tileX = Math.min(tileX, this.playfield.cols - 1);\r\n tileY = Math.min(tileY, (this.playfield.rows+this.playfield.spawnArea) - 1);\r\n\r\n return this.playfield.board[tileY][tileX];\r\n }\r\n\r\n isInActivePiece(t){\r\n let notActive = true;\r\n let active = this.getActivePiece();\r\n if(active){\r\n for(let i=0; i t.color = this.drawColor)}\r\n }\r\n }\r\n\r\n drawRect(startPos, mousePos){\r\n let startX = Math.min(startPos[0], mousePos[0]);\r\n let startY = Math.min(startPos[1], mousePos[1]);\r\n let endX = Math.max(startPos[0], mousePos[0]);\r\n let endY = Math.max(startPos[1], mousePos[1]);\r\n\r\n for(let row = startY; row<=endY; row++){\r\n for(let col = startX; col<=endX; col++){\r\n let tile = this.playfield.board[row][col];\r\n this.draw(tile);\r\n\r\n\r\n\r\n // tile.active = this.drawMode;\r\n // // tile.selected = false;\r\n // tile.color = this.drawMode ? this.drawColor : null;\r\n }\r\n }\r\n }\r\n\r\n select(tile, state=true){\r\n // if(tile.active && this.shiftKey && this.mouseLeftDown){\r\n if((this.shiftKey || this.ctrlKey) && (this.mouseLeftDown || this.mouseRightDown)){\r\n tile.selected = state;\r\n }\r\n }\r\n\r\n selectRect(startPos, mousePos, state){\r\n let startX = Math.min(startPos[0], mousePos[0]);\r\n let startY = Math.min(startPos[1], mousePos[1]);\r\n let endX = Math.max(startPos[0], mousePos[0]);\r\n let endY = Math.max(startPos[1], mousePos[1]);\r\n\r\n for(let row = startY; row<=endY; row++){\r\n for(let col = startX; col<=endX; col++){\r\n let tile = this.playfield.board[row][col];\r\n this.select(tile, state);\r\n }\r\n }\r\n }\r\n\r\n unselectAll(){\r\n let tiles = this.playfield.getAllTiles();\r\n for (let i=0; i= this.dragBounds.lowX \r\n // && tile.x <= this.dragBounds.highX \r\n // && tile.y >= this.dragBounds.lowY \r\n // && tile.y <= this.dragBounds.highY \r\n // }\r\n\r\n drag(curTile){\r\n if(curTile !== this.startDragTile){\r\n\r\n // let clamp = (x,lower,higher) => Math.max(Math.min(x, higher), lower);\r\n let shiftX = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(curTile.x, this.dragBounds.lowX, this.dragBounds.highX);\r\n let shiftY = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(curTile.y, this.dragBounds.lowY, this.dragBounds.highY);\r\n\r\n // erase and update selected\r\n for(let i=0; i {\r\n return new Tile(this.pos.x+o[1], this.pos.y+o[0], true, this.color);\r\n });\r\n }\r\n\r\n static pieces = {\r\n i : {\r\n 0 : [[1,0],[1,1],[1,2],[1,3]],\r\n 1 : [[0,2],[1,2],[2,2],[3,2]],\r\n 2 : [[2,0],[2,1],[2,2],[2,3]],\r\n 3 : [[0,1],[1,1],[2,1],[3,1]],\r\n color : \"#0f9bd7\"\r\n },\r\n j :{\r\n 0 : [[0,0],[1,0],[1,1],[1,2]],\r\n 1 : [[0,1],[0,2],[1,1],[2,1]],\r\n 2 : [[1,0],[1,1],[1,2],[2,2]],\r\n 3 : [[2,0],[2,1],[1,1],[0,1]],\r\n color : \"#2141c6\"\r\n },\r\n l :{\r\n 0 : [[1,0],[1,1],[1,2],[0,2]],\r\n 1 : [[0,1],[1,1],[2,1],[2,2]],\r\n 2 : [[1,0],[1,1],[1,2],[2,0]],\r\n 3 : [[0,0],[2,1],[1,1],[0,1]],\r\n color : \"#e35b02\"\r\n },\r\n o :{\r\n 0 : [[0,1],[0,2],[1,1],[1,2]],\r\n 1 : [[0,1],[0,2],[1,1],[1,2]],\r\n 2 : [[0,1],[0,2],[1,1],[1,2]],\r\n 3 : [[0,1],[0,2],[1,1],[1,2]],\r\n color : \"#e39f02\"\r\n },\r\n s :{\r\n 0 : [[1,0],[1,1],[0,1],[0,2]],\r\n 1 : [[0,1],[1,1],[1,2],[2,2]],\r\n 2 : [[2,0],[2,1],[1,1],[1,2]],\r\n 3 : [[0,0],[1,0],[1,1],[2,1]],\r\n color : \"#59b101\"\r\n },\r\n t :{\r\n 0 : [[1,0],[0,1],[1,1],[1,2]],\r\n 1 : [[0,1],[1,1],[1,2],[2,1]],\r\n 2 : [[1,0],[1,1],[1,2],[2,1]],\r\n 3 : [[1,0],[0,1],[1,1],[2,1]],\r\n color : \"#af298a\"\r\n },\r\n z :{\r\n 0 : [[0,0],[0,1],[1,1],[1,2]],\r\n 1 : [[1,1],[0,2],[1,2],[2,1]],\r\n 2 : [[1,0],[1,1],[2,1],[2,2]],\r\n 3 : [[1,0],[2,0],[0,1],[1,1]],\r\n color : \"#d70f37\"\r\n },\r\n }\r\n\r\n\r\n}\r\n\r\nclass Queue {\r\n pieces = [\"i\", \"j\", \"l\", \"t\", \"s\", \"z\", \"o\"];\r\n queue = [];\r\n queueIndex = 0;\r\n customPieces = 0; // counts manually injected pieces for bag indicator\r\n regen = true; // refill on empty queue\r\n\r\n slots = 5;\r\n slotSizeY = 3;\r\n slotSizeX = 6;\r\n showMax = 5;\r\n\r\n\r\n constructor(gameBoard, hold, tileSize=30) {\r\n this.gameBoard = gameBoard;\r\n this.hold = hold;\r\n this.edit = false;\r\n\r\n this.canvas = document.getElementById(\"queue\");\r\n this.ctx = this.canvas.getContext(\"2d\");\r\n this.tileSize = tileSize;\r\n\r\n // document.getElementById(\"customQueue\").addEventListener(\"change\", (e) => {\r\n // e.target.value = this.formatString(e.target.value);\r\n // })\r\n\r\n\r\n this.updateQueue();\r\n this.renderQueue();\r\n }\r\n\r\n getData(history=true){\r\n let pieces = Object.entries(Piece.pieces).map((e) => e[0]);\r\n let getPieceIndex = (x) => {\r\n for(const [i,c] of pieces.entries()){\r\n if(c == x){\r\n return i;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n // build buffer backwards\r\n let buffer = new ArrayBuffer();\r\n\r\n // queue pieces\r\n let q = history ? this.queue : this.queue.slice(this.queueIndex);\r\n let tmp = new Uint8Array(Math.ceil(q.length/2));\r\n for(const [i,p] of q.entries()){\r\n let shift = (i % 2 == 0 ? 4 : 0);\r\n let byte = tmp[Math.floor(i/2)];\r\n byte = byte | (getPieceIndex(p) << shift);\r\n tmp[Math.floor(i/2)] = byte;\r\n // byteIndex += (i % 2 == 0 ? 0 : 1);\r\n }\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.joinBuffers(tmp.buffer, buffer);\r\n\r\n // queue length\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.encodeNumber(buffer, q.length);\r\n\r\n // queue Index\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.encodeNumber(buffer, history ? this.queueIndex : 0);\r\n\r\n // custom pieces number\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.encodeNumber(buffer, this.customPieces);\r\n\r\n // hold data \r\n tmp = new Uint8Array(1);\r\n tmp[0] = (this.hold.pieceName && 1) << 7 | (getPieceIndex(this.hold.pieceName) << 4);\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.joinBuffers(tmp.buffer, buffer);\r\n\r\n\r\n return buffer;\r\n }\r\n\r\n loadData(buffer){\r\n let pieces = Object.entries(Piece.pieces).map((e) => e[0]);\r\n\r\n let newQueue = [];\r\n\r\n // parse hold\r\n let tmp = new Uint8Array(buffer);\r\n let holdByte = tmp[0];\r\n if(holdByte & (1<<7)){\r\n this.hold.pieceName = pieces[(holdByte & 0x70) >> 4];\r\n }\r\n else {\r\n this.hold.pieceName = null;\r\n }\r\n buffer = buffer.slice(1);\r\n\r\n // parse customPieces\r\n let decoded = _utils_js__WEBPACK_IMPORTED_MODULE_0__.decodeNumber(buffer);\r\n this.customPieces = decoded.num;\r\n buffer = decoded.buffer;\r\n\r\n // parse queue index\r\n decoded = _utils_js__WEBPACK_IMPORTED_MODULE_0__.decodeNumber(buffer);\r\n this.queueIndex = decoded.num;\r\n buffer = decoded.buffer;\r\n\r\n\r\n // parse queue length\r\n decoded = _utils_js__WEBPACK_IMPORTED_MODULE_0__.decodeNumber(buffer);\r\n let length = decoded.num;\r\n buffer = decoded.buffer;\r\n\r\n // parse pieces\r\n tmp = new Uint8Array(buffer);\r\n for(let i=0; i> shift]);\r\n }\r\n this.queue = newQueue\r\n }\r\n\r\n toString(){\r\n return this.queue.slice(this.queueIndex).join(\"\");\r\n\r\n // let s = \"\";\r\n // for (let i = this.queueIndex; i < this.queue.length; i++) {\r\n // s += this.queue[i];\r\n // if(((i - this.customPieces) % 7) == 6){\r\n // s += \"\\n\";\r\n // }\r\n // }\r\n // return s;\r\n }\r\n\r\n formatString(s){\r\n let newS = \"\"\r\n let counter = 0;\r\n for(const c of s.trim()){\r\n if(counter >= 7){\r\n newS += '\\n';\r\n counter = 0;\r\n }\r\n else{\r\n newS += c;\r\n counter ++;\r\n }\r\n }\r\n newS = newS.replace(/(\\s)\\1{2,}/g, '\\n'); // replace all repeated whitespace\r\n\r\n return newS\r\n }\r\n\r\n copy() {\r\n let copy = new Queue(this.tileSize);\r\n copy.queue = structuredClone(this.queue);\r\n copy.queueIndex = this.queueIndex;\r\n return copy;\r\n }\r\n\r\n renderQueue() {\r\n this.queueHeight = this.tileSize*this.slots*this.slotSizeY + this.tileSize;\r\n this.queueWidth = this.tileSize*this.slotSizeX;\r\n\r\n this.canvas.height = this.queueHeight;\r\n this.canvas.width = this.queueWidth;\r\n\r\n // if(this.queue.length < this.showMax) return;\r\n this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);\r\n // this.ctx.fillStyle = \"orange\";\r\n // this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);\r\n\r\n\r\n for (let i = 0; i < this.slots; i++) {\r\n if(i < this.showMax && this.queue[this.queueIndex+i+1]){\r\n this.drawPiece(new Piece(this.queue[this.queueIndex+i+1]), this.tileSize, (i*this.slotSizeY*this.tileSize) + this.tileSize); // TODO better spacing ?\r\n }\r\n }\r\n\r\n // bag postion indicator\r\n for(let i=0; i<7; i++){\r\n this.ctx.lineWidth = 4;\r\n this.ctx.strokeStyle = (this.queueIndex - ((this.hold.pieceName ? 1 : 0) + this.customPieces + i)) % 7 == 0 ? \"rgba(200,200,200,0.4)\" : \"rgba(100,100,100,0.3)\" ;\r\n // this.ctx.strokeStyle = (this.queueIndex - (this.customPieces + i)) % 7 == 0 ? \"rgba(200,200,200,0.4)\" : \"rgba(100,100,100,0.3)\" ;\r\n this.ctx.beginPath();\r\n this.ctx.moveTo(i*(this.queueWidth/7)+5, 0)\r\n this.ctx.lineTo((i+1)*(this.queueWidth/7)-5, 0);\r\n this.ctx.stroke();\r\n\r\n }\r\n }\r\n\r\n drawPiece(p,x,y){\r\n\r\n for(let i=0; i {\r\n let copy = structuredClone(arr)\r\n for (let i = copy.length - 1; i > 0; i--) {\r\n let j = Math.floor(Math.random() * (i + 1));\r\n [copy[i], copy[j]] = [copy[j], copy[i]];\r\n }\r\n return copy\r\n }\r\n this.queue.push(...shuffle(Object.entries(Piece.pieces).map((x) => x[0])));\r\n // this.bagStarts.add(this.queue.length);\r\n }\r\n return true;\r\n }\r\n }\r\n\r\n setQueueIndex(i){\r\n this.queueIndex = i;\r\n }\r\n\r\n queueStep(){\r\n this.edit = false;\r\n this.updateQueue();\r\n this.queueIndex += 1;\r\n }\r\n\r\n getCurrent(){\r\n return this.queue[this.queueIndex];\r\n }\r\n\r\n setCurrent(p){\r\n this.queue[this.queueIndex] = p; \r\n }\r\n\r\n reset() {\r\n this.queue = [];\r\n this.queueIndex = 0;\r\n this.customPieces = 0;\r\n this.updateQueue();\r\n }\r\n\r\n}\r\n\r\n\r\nclass Hold {\r\n pieceName;\r\n\r\n constructor(tileSize=30) {\r\n\r\n this.canvas = document.getElementById(\"hold\");\r\n this.ctx = this.canvas.getContext(\"2d\");\r\n this.tileSize = tileSize;\r\n\r\n this.renderHold();\r\n }\r\n\r\n copy() {\r\n let copy = new Hold(this.tileSize);\r\n copy.pieceName = this.pieceName;\r\n return copy;\r\n }\r\n\r\n\r\n renderHold(){\r\n this.holdHeight = this.tileSize*4;\r\n this.holdWidth = this.tileSize*6;\r\n\r\n this.canvas.height = this.holdHeight;\r\n this.canvas.width = this.holdWidth;\r\n\r\n this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);\r\n if(this.pieceName){\r\n this.drawPiece(new Piece(this.pieceName), this.tileSize, this.tileSize); //TODO better spacing \r\n }\r\n }\r\n\r\n drawPiece(p,x,y){\r\n for(let i=0; i e[1].color).concat([\"#555555\"]);\r\n let colors = [\"#555555\"].concat(Object.entries(Piece.pieces).map((e) => e[1].color));\r\n let getColorIndex = (x) => {\r\n for(const [i,c] of colors.entries()){\r\n if(_utils_js__WEBPACK_IMPORTED_MODULE_0__.colorComp(c, x)){\r\n return i;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n // board to bytes\r\n let totalTiles = 22 * 10 // TODO fix magic number\r\n let byteBoard = new Uint8Array(totalTiles+1);\r\n\r\n byteBoard[0] = 0;\r\n foundActive = false;\r\n\r\n for (let i = 0; i < totalTiles; i++) {\r\n let tile = p.board[Math.floor(i/10)][i%10]; // TODO fix magic number\r\n\r\n // assume board starts with many empty tiles store count in first byte\r\n if(!foundActive && !(tile.active || tile.ghost || tile.selected)){\r\n byteBoard[0] += 1;\r\n }\r\n else {\r\n foundActive = true;\r\n let tileData = (tile.active << 7)\r\n | (tile.ghost << 6)\r\n | (tile.selected << 5)\r\n | (getColorIndex(tile.color) << 2);\r\n\r\n }\r\n\r\n\r\n\r\n\r\n if(tile.active || tile.ghost || tile.selected) {\r\n let posByte = i;\r\n let infoByte = (tile.active << 7)\r\n | (tile.ghost << 6)\r\n | (tile.selected << 5)\r\n | (getColorIndex(tile.color) << 2);\r\n // console.log(byte.toString(2).padStart(16, '0'))\r\n byteBoard[size] = posByte;\r\n byteBoard[size+1] = infoByte;\r\n size += 2;\r\n }\r\n // console.log(String(byte.toString(2)).padStart(8, '0'));\r\n }\r\n\r\n byteBoard = byteBoard.subarray(0,size);\r\n\r\n //convert to base-64 string\r\n let s = \"\";\r\n\r\n for(let i=0; i < byteBoard.length; i++){\r\n // console.log(byteBoard[i].toString(2).padStart(8,'0'))\r\n s += String.fromCharCode(byteBoard[i]);\r\n }\r\n\r\n\r\n return window.btoa(s);\r\n }\r\n\r\n static stringToGameState(s){\r\n\r\n //decode string\r\n let decoded = window.atob(s);\r\n\r\n\r\n //build board\r\n let b = new Uint8Array(decoded.length);\r\n for(let i=0; i this.keyDown(e));\r\n window.addEventListener(\"keyup\", (e) => this.keyUp(e));\r\n\r\n document.getElementById(\"field\").addEventListener('focusout', this.resetInputs); \r\n\r\n this.callback = callback;\r\n }\r\n\r\n resetInputs(e){\r\n this.keysDown = {};\r\n this.lastKey = null;\r\n }\r\n\r\n keyDown(e) {\r\n console.log(e.key);\r\n if(!this.keysDown[e.key]) {\r\n this.keysDown[e.key] = performance.now();\r\n this.lastKey = e.key;\r\n this.callback(e.key, true);\r\n }\r\n }\r\n\r\n keyUp(e) {\r\n this.keysDown[e.key] = null;\r\n this.callback(e.key, false);\r\n }\r\n\r\n}\r\n\r\nclass Settings{\r\n // gravity = 1000;\r\n\r\n // Default Settings\r\n das = 130;\r\n arr = 50;\r\n softDrop = 50;\r\n keybinds = {\r\n leftKey: 'ArrowLeft',\r\n rightKey: 'ArrowRight',\r\n softKey: 'ArrowDown',\r\n hardKey: ' ',\r\n holdKey: 'c',\r\n crKey: 'ArrowUp',\r\n ccrKey: 'z',\r\n r180Key: 'a',\r\n restartKey: 'r',\r\n undoKey: 'q',\r\n redoKey: 'w',\r\n // spinKey: 'a',\r\n };\r\n\r\n constructor(input, pauseInput, resumeInput, board){\r\n this.input = input;\r\n this.pause = pauseInput;\r\n this.resume = resumeInput;\r\n this.board = board;\r\n\r\n this.loadSettings();\r\n\r\n let s = document.getElementById(\"settings\")\r\n \r\n // s.addEventListener(\"focusin\", (e) => this.pause());\r\n // s.addEventListener(\"focusout\", (e) => this.resume());\r\n\r\n // queue\r\n\r\n // document.getElementById(\"handling\").addEventListener(\"mousedown\", (e) => {\r\n // console.log(\"data\");\r\n // });\r\n\r\n // handling\r\n for(const [k, v] of Object.entries(this)){\r\n if(k === \"das\" || k === \"arr\" || k === \"softDrop\"){\r\n let slider = document.getElementById(k + \"Slider\");\r\n let label = document.getElementById(k+\"SliderVal\");\r\n slider.value = v;\r\n label.innerHTML = v == 0 ? \"Instant\" : v + \" ms\";\r\n\r\n slider.addEventListener(\"input\", (e) => {\r\n label.innerHTML = e.target.value == 0 ? \"Instant\" : e.target.value + \" ms\";\r\n });\r\n\r\n slider.addEventListener(\"change\", (e) => {\r\n // label.innerHTML = e.target.value + \" ms\";\r\n label.innerHTML = e.target.value == 0 ? \"Instant\" : e.target.value + \" ms\";\r\n this.update(k, e.target.value);\r\n this.saveSettings();\r\n e.target.blur();\r\n });\r\n }\r\n }\r\n\r\n\r\n // keybinds\r\n document.getElementById(\"changeAll\").addEventListener(\"click\", (e) => this.changeAllKeys());\r\n for(const [k, v] of Object.entries(this.keybinds)){\r\n document.getElementById(k+\"Button\").addEventListener(\"click\", (e) => {\r\n document.getElementById(k+\"Button\").blur(); // needed to make sure spacebar doesn't retrigger button\r\n this.updateKey(k);\r\n })\r\n }\r\n\r\n this.updateDisplay();\r\n\r\n\r\n\r\n // html += \"
\"\r\n // for(const [k, v] of Object.entries(this)){\r\n // if(k === \"das\" || k === \"arr\" || k === \"softDrop\"){\r\n // html += \"
\";\r\n // html += \"

\" + k + \" (ms)\" + \" : \" + v + \"

\";\r\n // html += \"\" + \"\";\r\n // html += \"
\";\r\n // }\r\n // }\r\n // html += \"
\"\r\n // let html = \"\";\r\n // html += \"

update queue

\";\r\n\r\n // html += \"
\"\r\n // html += \"\"\r\n // html += \"
\"\r\n // document.getElementById(\"settings\").innerHTML += html;\r\n\r\n // // add listeners\r\n // document.getElementById(\"queueUpdate\").addEventListener(\"keyup\", (e) => {\r\n // if(e.key === \"Enter\"){\r\n // let newQ = document.getElementById(\"queueUpdate\").value.toLowerCase().split(\"\");\r\n\r\n // // validate\r\n // let pieces = [\"i\", \"j\", \"l\", \"t\", \"s\", \"z\", \"o\"];\r\n // for(let i=0; i row.startsWith(\"settings=\"));\r\n if(cookieSettingsRaw){\r\n let cookieSettings = JSON.parse(cookieSettingsRaw.split(\"=\")[1]);\r\n this.das = cookieSettings.das;\r\n this.arr = cookieSettings.arr;\r\n this.softDrop = cookieSettings.softDrop;\r\n this.keybinds = cookieSettings.keybinds;\r\n }\r\n }\r\n\r\n saveSettings() {\r\n let cookie = {\r\n das : this.das,\r\n arr : this.arr,\r\n softDrop : this.softDrop,\r\n keybinds : this.keybinds\r\n }\r\n\r\n let x = new Date();\r\n x.setTime(x.getTime() + 24*60*60*365*1000); // 1 year;\r\n \r\n document.cookie = \"settings=\" + encodeURIComponent(JSON.stringify(cookie))\r\n + \"; expires=\" + x.toUTCString() + \"; SameSite=None; secure\";\r\n\r\n }\r\n\r\n removeFocus() {\r\n document.getElementById(\"queueUpdate\").blur();\r\n }\r\n\r\n updateDisplay(){\r\n for(const [k, v] of Object.entries(this.keybinds)){\r\n document.getElementById(k+\"Text\").innerHTML = (v === \" \" ? \"Space\" : v);\r\n }\r\n\r\n this.saveSettings();\r\n }\r\n\r\n async changeAllKeys(){\r\n for(const [k, v] of Object.entries(this.keybinds)){\r\n let keyText = document.getElementById(k+\"Text\");\r\n keyText.scrollIntoView({ behavior: \"smooth\", block: \"center\", inline: \"nearest\" });\r\n keyText.innerHTML = \"Waiting...\";\r\n let newKey = await this.awaitKey();\r\n this.keybinds[k] = newKey;\r\n this.updateDisplay();\r\n }\r\n document.getElementById(\"changeAll\").blur(); // needed to make sure spacebar doesn't retrigger button\r\n }\r\n\r\n update(s, newVal){\r\n this[s] = parseInt(newVal);\r\n // this.updateDisplay();\r\n }\r\n\r\n async updateKey(k){\r\n this.pause();\r\n document.getElementById(k+\"Text\").innerHTML = \"Waiting...\";\r\n\r\n let newKey = await this.awaitKey();\r\n this.keybinds[k] = newKey;\r\n\r\n this.resume();\r\n this.updateDisplay();\r\n }\r\n\r\n awaitKey() {\r\n let waitKey = new Promise((resolve) => {\r\n document.addEventListener(\"keydown\", (e) => {\r\n e.preventDefault();\r\n resolve(e.key);\r\n }, {once: true}\r\n )\r\n })\r\n return waitKey;\r\n }\r\n}\r\n\r\nclass Slider {\r\n\r\n constructor(type){\r\n this.type = type // horizontal or vertical\r\n }\r\n\r\n\r\n\r\n}\r\n\r\n\r\nclass Paster {\r\n active = false;\r\n dragging = null;\r\n\r\n\r\n highlightSlider = null;\r\n sliderLen = 40;\r\n sliderWid = 10;\r\n sliderColor = \"rgba(150,150,150,1)\"\r\n sliderHighlightColor = \"rgba(220,220,220,1)\"\r\n \r\n constructor(gameBoard) {\r\n this.gameBoard = gameBoard;\r\n this.preview = new Playfield(20, 10, 30, 2, \"previewBackground\", \"previewField\");\r\n // this.sketcher = new Sketcher(this.preview);\r\n\r\n\r\n\r\n this.imgCanvas = document.getElementById(\"imageCanvas\");\r\n this.imgCtx = this.imgCanvas.getContext(\"2d\", { willReadFrequently: true });\r\n\r\n this.ctrlsCanvas = document.getElementById(\"imageControls\");\r\n this.ctrlsCtx = this.ctrlsCanvas.getContext(\"2d\");\r\n\r\n this.pasterAccept = document.getElementById(\"pasterAccept\");\r\n this.pasterAccept.addEventListener(\"click\", (e) => {\r\n if(this.active){\r\n this.accept();\r\n }\r\n })\r\n\r\n this.pasterCancel = document.getElementById(\"pasterCancel\");\r\n this.pasterCancel.addEventListener(\"click\", (e) => {\r\n if(this.active){\r\n this.cancel();\r\n }\r\n })\r\n\r\n\r\n\r\n\r\n // handle pasted images\r\n document.addEventListener(\"paste\", (e) => {\r\n\r\n if(e.clipboardData.getData(\"text\")) return;\r\n\r\n this.openModal();\r\n\r\n // load image\r\n if (e.clipboardData.files.length > 0) {\r\n let file = e.clipboardData.files[0]\r\n let reader = new FileReader();\r\n reader.onload = (e) => {\r\n this.img = new Image();\r\n this.img.onload = () => {\r\n\r\n this.imgCanvas = document.getElementById(\"imageCanvas\");\r\n this.imgCanvas.width = window.innerWidth * 0.4 * 0.8;\r\n this.imgCanvas.height = window.innerHeight * 0.8 * 0.8;\r\n this.imgCtx = this.imgCanvas.getContext(\"2d\", { willReadFrequently: true });\r\n this.imgBounds = {p1:{x:0,y:0},p2:{x:this.imgCanvas.width,y:this.imgCanvas.height}}\r\n\r\n this.ctrlsCanvas = document.getElementById(\"imageControls\");\r\n this.ctrlsCanvas.width = window.innerWidth * 0.4 * 0.8;\r\n this.ctrlsCanvas.height = window.innerHeight * 0.8 * 0.8;\r\n this.ctrlsCtx = this.ctrlsCanvas.getContext(\"2d\");\r\n\r\n\r\n let ratio = Math.min(this.imgCanvas.width / this.img.width, this.imgCanvas.height / this.img.height);\r\n let newWidth = this.img.width * ratio;\r\n let newHeight = this.img.height * ratio;\r\n let x = (this.imgCanvas.width/2) - (newWidth/2);\r\n let y = (this.imgCanvas.height/2) - (newHeight/2);\r\n this.imgBounds = {p1:{x:x,y:y},p2:{x:x+newWidth,y:y+newHeight}};\r\n\r\n this.top = Math.max(this.sliderWid/2, this.imgBounds.p1.y);\r\n this.bottom = Math.min(this.imgCanvas.height - this.sliderWid/2, this.imgBounds.p2.y);\r\n this.left = Math.max(this.sliderWid/2, this.imgBounds.p1.x);\r\n this.right = Math.min(this.imgCanvas.width - this.sliderWid/2, this.imgBounds.p2.x);\r\n\r\n this.drawImage();\r\n this.update();\r\n this.render();\r\n };\r\n this.img.src = e.target.result;\r\n }\r\n reader.readAsDataURL(file);\r\n }\r\n })\r\n\r\n\r\n // mouse event listeners\r\n this.ctrlsCanvas.addEventListener(\"mousedown\", (e) => {\r\n let b = this.ctrlsCanvas.getBoundingClientRect();\r\n let scale = this.ctrlsCanvas.width / parseFloat(b.width);\r\n\r\n let xPos = e.offsetX * scale;\r\n let yPos = e.offsetY * scale;\r\n\r\n\r\n if(Math.abs(xPos - (this.left+(this.right-this.left)/2)) < this.sliderLen+5 && \r\n Math.abs(yPos - this.top) < this.sliderWid+5){\r\n this.dragging = \"top\";\r\n this.highlight = \"top\";\r\n this.top = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(yPos, this.sliderWid/2, this.bottom - this.sliderWid);\r\n }\r\n else if(Math.abs(xPos - (this.left+(this.right-this.left)/2)) < this.sliderLen+5 && \r\n Math.abs(yPos - this.bottom) < this.sliderWid+5){\r\n this.dragging = \"bottom\";\r\n this.highlight = \"bottom\";\r\n this.bottom = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(yPos, this.top+this.sliderWid, this.imgCanvas.height-this.sliderWid/2);\r\n }\r\n else if(Math.abs(yPos - (this.top+(this.bottom-this.top)/2)) < this.sliderLen+5 && \r\n Math.abs(xPos - this.left) < this.sliderWid+5){\r\n this.dragging = \"left\";\r\n this.highlight = \"left\";\r\n this.left = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(xPos, this.sliderWid/2, this.right-this.sliderWid);\r\n }\r\n else if(Math.abs(yPos - (this.top+(this.bottom-this.top)/2)) < this.sliderLen+5 && \r\n Math.abs(xPos - this.right) < this.sliderWid+5){\r\n this.dragging = \"right\";\r\n this.highlight = \"right\";\r\n this.right = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(xPos, this.left+this.sliderWid, this.imgCanvas.width - this.sliderWid/2);\r\n }\r\n });\r\n\r\n this.ctrlsCanvas.addEventListener(\"mousemove\", (e) => {\r\n let b = this.ctrlsCanvas.getBoundingClientRect();\r\n let scale = this.ctrlsCanvas.width / parseFloat(b.width);\r\n\r\n let xPos = e.offsetX * scale;\r\n let yPos = e.offsetY * scale;\r\n\r\n if(this.dragging == \"top\" ||\r\n (Math.abs(xPos - (this.left+(this.right-this.left)/2)) < this.sliderLen+5 && \r\n Math.abs(yPos - this.top) < this.sliderWid+5)){\r\n this.highlight = \"top\";\r\n }\r\n else if(this.dragging == \"bottom\" ||\r\n (Math.abs(xPos - (this.left+(this.right-this.left)/2)) < this.sliderLen+5 && \r\n Math.abs(yPos - this.bottom) < this.sliderWid+5)){\r\n this.highlight = \"bottom\";\r\n }\r\n else if(this.dragging == \"left\" ||\r\n (Math.abs(yPos - (this.top+(this.bottom-this.top)/2)) < this.sliderLen+5 && \r\n Math.abs(xPos - this.left) < this.sliderWid+5)){\r\n this.highlight = \"left\";\r\n }\r\n else if(this.dragging == \"right\" ||\r\n (Math.abs(yPos - (this.top+(this.bottom-this.top)/2)) < this.sliderLen+5 && \r\n Math.abs(xPos - this.right) < this.sliderWid+5)){\r\n this.highlight = \"right\";\r\n }\r\n else this.highlight = null;\r\n\r\n // if(this.dragging) this.update();\r\n if(this.dragging == \"top\"){\r\n this.top = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(yPos, this.sliderWid/2, this.bottom - this.sliderWid);\r\n }\r\n else if(this.dragging == \"bottom\"){\r\n this.bottom = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(yPos, this.top+this.sliderWid, this.imgCanvas.height-this.sliderWid/2);\r\n }\r\n else if(this.dragging == \"left\"){\r\n this.left = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(xPos, this.sliderWid/2, this.right-this.sliderWid);\r\n }\r\n else if(this.dragging == \"right\"){\r\n this.right = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(xPos, this.left+this.sliderWid, this.imgCanvas.width - this.sliderWid/2);\r\n }\r\n });\r\n\r\n document.addEventListener(\"mouseup\", (e) => {\r\n if(this.dragging){\r\n this.update();\r\n }\r\n this.dragging = null;\r\n });\r\n\r\n document.getElementById(\"pasterOverlay\").addEventListener(\"mousedown\", (e) => {\r\n this.closeModal();\r\n });\r\n \r\n document.getElementById(\"pasterModal\").addEventListener(\"mousedown\", (e) => {\r\n e.stopPropagation();\r\n });\r\n\r\n }\r\n\r\n update() {\r\n // for(let i = this.preview.spawnArea; i < this.preview.rows + this.preview.spawnArea; i++ ){\r\n // for(let j = 0; j < this.preview.cols; j++ ){\r\n // let newTileSize = [((this.right-this.left)/this.preview.cols)/2, ((this.bottom-this.top)/this.preview.rows)/2];\r\n\r\n // let x = this.left + ((this.right-this.left)/this.preview.cols)*j + newTileSize[0]/2;\r\n // let y = this.top + ((this.bottom-this.top)/this.preview.rows-this.preview.spawnArea)*i + newTileSize[1]/2;\r\n\r\n for(let i = 0; i < this.preview.rows; i++ ){\r\n for(let j = 0; j < this.preview.cols; j++ ){\r\n let newTileSize = [(this.right-this.left)/this.preview.cols, (this.bottom-this.top)/(this.preview.rows)];\r\n let tile = this.preview.board[i+this.preview.spawnArea][j];\r\n\r\n let x = this.left + newTileSize[0]*j + newTileSize[0]/2;\r\n let y = this.top + newTileSize[1]*i + newTileSize[1]/2;\r\n\r\n\r\n let pixColor = _utils_js__WEBPACK_IMPORTED_MODULE_0__.parseColor(this.getPixelColor(x,y));\r\n let colors = Object.entries(Piece.pieces).map((e) => e[1].color).concat([\"#555555\",\"#000000\"]);\r\n\r\n\r\n if(pixColor.r + pixColor.g + pixColor.b < 50 || pixColor.a == 0){\r\n tile.active = false;\r\n }\r\n else {\r\n tile.color = this.getPixelColor(x,y);\r\n tile.active = true;\r\n }\r\n\r\n\r\n let bestMatch = null;\r\n let bestDelta = null;\r\n for(const c of colors){\r\n let delta = _utils_js__WEBPACK_IMPORTED_MODULE_0__.deltaE(pixColor, _utils_js__WEBPACK_IMPORTED_MODULE_0__.parseColor(c));\r\n\r\n //cyan handicap needed for tetrio colors\r\n if(c == \"#0f9bd7\") delta -= 15;\r\n\r\n if(!bestMatch || bestDelta > delta){\r\n bestMatch = c;\r\n bestDelta = delta\r\n }\r\n }\r\n\r\n if(bestMatch == \"#000000\" || pixColor.a == 0){\r\n tile.active = false;\r\n }\r\n else {\r\n tile.color = bestMatch;\r\n tile.active = true;\r\n }\r\n }\r\n }\r\n }\r\n\r\n drawImage (){\r\n let ratio = Math.min(this.imgCanvas.width / this.img.width, this.imgCanvas.height / this.img.height);\r\n let newWidth = this.img.width * ratio;\r\n let newHeight = this.img.height * ratio;\r\n let x = (this.imgCanvas.width/2) - (newWidth/2);\r\n let y = (this.imgCanvas.height/2) - (newHeight/2);\r\n this.imgBounds = {p1:{x:x,y:y},p2:{x:x+newWidth,y:y+newHeight}};\r\n\r\n this.imgCtx.drawImage(this.img, this.imgBounds.p1.x, this.imgBounds.p1.y, \r\n this.imgBounds.p2.x-this.imgBounds.p1.x,\r\n this.imgBounds.p2.y-this.imgBounds.p1.y);\r\n }\r\n\r\n render(){\r\n if(this.active && this.img){\r\n this.preview.render();\r\n\r\n this.drawImage();\r\n\r\n // draw sliders\r\n this.ctrlsCtx.clearRect(0,0,this.imgCanvas.width,this.imgCanvas.height);\r\n this.ctrlsCtx.fillStyle = \"rgba(50,54,69,0.5)\"\r\n this.ctrlsCtx.fillRect(0,0,this.imgCanvas.width,this.top);\r\n this.ctrlsCtx.fillRect(0,this.bottom, this.imgCanvas.width, this.imgCanvas.height);\r\n this.ctrlsCtx.fillRect(0,this.top,this.left, this.bottom-this.top);\r\n this.ctrlsCtx.fillRect(this.right,this.top,this.imgCanvas.width, this.bottom-this.top);\r\n\r\n this.ctrlsCtx.strokeStyle = \"white\";\r\n this.ctrlsCtx.setLineDash([10,10]) \r\n this.ctrlsCtx.beginPath();\r\n this.ctrlsCtx.moveTo(0,this.top)\r\n this.ctrlsCtx.lineTo(this.imgCanvas.width, this.top);\r\n this.ctrlsCtx.moveTo(0,this.bottom)\r\n this.ctrlsCtx.lineTo(this.imgCanvas.width, this.bottom);\r\n this.ctrlsCtx.moveTo(this.left,0)\r\n this.ctrlsCtx.lineTo(this.left, this.imgCanvas.height);\r\n this.ctrlsCtx.moveTo(this.right,0)\r\n this.ctrlsCtx.lineTo(this.right, this.imgCanvas.height);\r\n this.ctrlsCtx.stroke();\r\n this.ctrlsCtx.setLineDash([]) \r\n\r\n this.ctrlsCtx.fillStyle = this.highlight == \"top\" ? this.sliderHighlightColor : this.sliderColor; \r\n this.ctrlsCtx.fillRect((this.left+((this.right-this.left)/2))-(this.sliderLen/2),this.top-(this.sliderWid/2),this.sliderLen,this.sliderWid);\r\n this.ctrlsCtx.fillStyle = this.highlight == \"bottom\" ? this.sliderHighlightColor : this.sliderColor; \r\n this.ctrlsCtx.fillRect((this.left+((this.right-this.left)/2))-(this.sliderLen/2),this.bottom-(this.sliderWid/2),this.sliderLen,this.sliderWid);\r\n this.ctrlsCtx.fillStyle = this.highlight == \"left\" ? this.sliderHighlightColor : this.sliderColor; \r\n this.ctrlsCtx.fillRect(this.left-(this.sliderWid/2),(this.top+((this.bottom-this.top)/2))-(this.sliderLen/2),this.sliderWid,this.sliderLen);\r\n this.ctrlsCtx.fillStyle = this.highlight == \"right\" ? this.sliderHighlightColor : this.sliderColor; \r\n this.ctrlsCtx.fillRect(this.right-(this.sliderWid/2),(this.top+((this.bottom-this.top)/2))-(this.sliderLen/2),this.sliderWid,this.sliderLen);\r\n\r\n // draw dots\r\n for(let i = 0; i < this.preview.rows; i++ ){\r\n for(let j = 0; j < this.preview.cols; j++ ){\r\n let newTileSize = [(this.right-this.left)/this.preview.cols, (this.bottom-this.top)/(this.preview.rows)];\r\n\r\n let x = this.left + newTileSize[0]*j + newTileSize[0]/2;\r\n let y = this.top + newTileSize[1]*i + newTileSize[1]/2;\r\n\r\n // this.ctrlsCtx.fillStyle = this.getPixelColor(x,y);\r\n this.ctrlsCtx.fillStyle = \"rgba(255,255,255,0.5)\";\r\n // this.ctrlsCtx.strokeStyle = \"black\";\r\n this.ctrlsCtx.beginPath();\r\n this.ctrlsCtx.arc(x, y, 1, 0, 2 * Math.PI);\r\n // this.ctrlsCtx.stroke();\r\n this.ctrlsCtx.fill();\r\n }\r\n }\r\n\r\n }\r\n }\r\n\r\n getPixelColor(x,y){\r\n if(!x || !y) return \"rgba(0,0,0,0)\";\r\n let pixelData = this.imgCtx.getImageData(x,y,1,1).data;\r\n let rgba = `rgba(${pixelData[0]}, ${pixelData[1]}, ${pixelData[2]}, ${pixelData[3] / 255})`;\r\n\r\n return rgba;\r\n }\r\n\r\n accept(){\r\n this.gameBoard.playfield.board = this.preview.copy().board;\r\n this.closeModal();\r\n this.active = false;\r\n }\r\n\r\n cancel(){\r\n this.closeModal();\r\n this.active = false;\r\n }\r\n\r\n openModal () {\r\n this.active = true;\r\n document.getElementById(\"pasterOverlay\").style.display = \"block\";\r\n }\r\n\r\n closeModal () {\r\n this.active = false;\r\n document.getElementById(\"pasterOverlay\").style.display = \"none\";\r\n }\r\n}\r\n\r\n// class Data {\r\n\r\n\r\n// constructor(gameBoard){\r\n// this.gameBoard = gameBoard\r\n// // this.includeQueue = true;\r\n\r\n// document.getElementById(\"import\")\r\n\r\n// // this.includeQueueCheckbox = document.getElementById(\"includeQueue\");\r\n// // this.includeQueueCheckbox.checked = this.includeQueue;\r\n// // this.includeQueueCheckbox.addEventListener(\"click\", (e) => {\r\n// // this.includeQueue = e.target.checked;\r\n// // })\r\n\r\n\r\n\r\n\r\n// }\r\n\r\n// document.getelementbyid(\"importbutton\").addeventlistener(\"click\", (e) => this.import());\r\n// document.getelementbyid(\"clearimport\").addeventlistener(\"click\", (e) => this.clearimport());\r\n// import(){\r\n// let datastring = document.getelementbyid(\"importtext\").value;\r\n// this.gameboard.loaddata(datastring);\r\n// let el = document.queryselector( ':focus' );\r\n// if( el ) el.blur();\r\n// }\r\n\r\n// clearImport(){\r\n// document.getElementById(\"importText\").value = \"\";\r\n// }\r\n\r\n\r\n// copyToClipboard(s){\r\n\r\n// }\r\n// }\r\n\r\nclass App {\r\n start = true;\r\n startTime;\r\n takingInput = true;\r\n\r\n curDir;\r\n dasFired = false;\r\n softDrop = false;\r\n dasCancel;\r\n arrCancel;\r\n softCancel;\r\n\r\n constructor() {\r\n\r\n this.input = new InputManager((key, state) => this.handleInputs(key, state));\r\n this.board = new GameBoard(20, 10, 30);\r\n this.settings = new Settings(this.input, () => this.pauseInput(), () => this.resumeInput(), this.board);\r\n this.paster = new Paster(this.board);\r\n // this.data = new Data(this.board);\r\n\r\n this.panelOpen = false;\r\n\r\n this.lastTick = performance.now();\r\n this.lastRender = this.lastTick;\r\n // this.tickLength = 50;\r\n this.tickLength = 17;\r\n\r\n // load board if parameter given\r\n let url = window.location.href;\r\n let params = new URLSearchParams(url.split('?')[1]);\r\n\r\n if(params.has('data')){\r\n this.board.loadData(params.get('data'));\r\n }\r\n\r\n // App setup\r\n \r\n // change Log\r\n document.getElementById(\"changeLogSvg\").addEventListener(\"click\", (e) => {\r\n document.getElementById(\"changeLogOverlay\").style.display = \"block\";\r\n });\r\n document.getElementById(\"changeLogCloseSvg\").addEventListener(\"click\", (e) => {\r\n document.getElementById(\"changeLogOverlay\").style.display = \"none\";\r\n });\r\n\r\n\r\n // side panel\r\n document.getElementById(\"panelTab\").addEventListener(\"click\", (e) => {\r\n let left = document.getElementById(\"panelIconLeft\")\r\n left.style.animationName = this.panelOpen ? \"collapseIcon\" : \"expandIcon\";\r\n left.style.width = this.panelOpen ? \"0%\" : \"55%\";\r\n\r\n let right = document.getElementById(\"panelIconRight\");\r\n right.style.animationName = this.panelOpen ? \"expandIcon\" : \"collapseIcon\";\r\n right.style.width = this.panelOpen ? \"55%\" : \"0%\";\r\n\r\n let tab = document.getElementById(\"panelTab\");\r\n tab.style.boxShadow = this.panelOpen ? \"0 0 10px black\" : \"0 0 30px black\";\r\n\r\n let sidePanel = document.getElementById(\"sidePanel\");\r\n sidePanel.style.animationName = this.panelOpen ? \"collapsePanel\" : \"expandPanel\";\r\n sidePanel.style.width = this.panelOpen ? \"0\" : \"30em\";\r\n\r\n this.panelOpen = !this.panelOpen\r\n });\r\n\r\n let sP = document.getElementById(\"sidePanelContent\");\r\n sP.addEventListener(\"focusin\", (e) => this.pauseInput());\r\n sP.addEventListener(\"focusout\", (e) => this.resumeInput());\r\n\r\n\r\n\r\n let tabs = document.getElementsByClassName(\"tab\");\r\n for( const t of tabs){\r\n t.addEventListener(\"mousedown\", (e) => {\r\n if(!e.currentTarget.classList.contains(\"activeTab\")){\r\n let allTabs = document.getElementsByClassName(\"tab\");\r\n\r\n // remove activeTab class\r\n for(const tab of allTabs){\r\n tab.classList.remove(\"activeTab\");\r\n let content = document.getElementById(tab.id.replace(\"Tab\",\"\"));\r\n content.style.display = \"none\";\r\n }\r\n\r\n e.currentTarget.classList.add(\"activeTab\");\r\n let content = document.getElementById(e.currentTarget.id.replace(\"Tab\",\"\"));\r\n content.style.display = \"flex\";\r\n\r\n }\r\n }) \r\n }\r\n\r\n\r\n\r\n }\r\n\r\n\r\n\r\n update() {\r\n // game logic\r\n if(this.start){\r\n if(!this.takingInput){\r\n this.board.isPaused = true;\r\n }\r\n else{\r\n this.board.isPaused = false;\r\n this.board.update();\r\n }\r\n // this.board.activePiece = this.board.queue.getCurrent();\r\n if(this.dasFired && this.settings.arr === 0){\r\n this.board.shiftInstant(this.curDir);\r\n }\r\n // if(this.paster.active){\r\n // this.paster.update();\r\n // }\r\n }\r\n }\r\n\r\n initiateDas(key,dir) {\r\n this.cancelDas();\r\n this.curDir = dir;\r\n\r\n this.board.shiftPiece(dir);\r\n this.dasCancel = setTimeout( () => {\r\n this.dasFired = true;\r\n if(this.input.keysDown[key]) {\r\n if(this.settings.arr === 0) this.board.shiftInstant(dir, this.settings.softDrop === 0 && this.softDrop);\r\n else{\r\n this.arrCancel = setInterval(() => {\r\n if(this.input.keysDown[key]) this.board.shiftPiece(dir) \r\n else this.cancelDas();\r\n }, this.settings.arr);\r\n }\r\n }\r\n else this.cancelDas();\r\n }, this.settings.das);\r\n }\r\n\r\n cancelDas() {\r\n if(this.dasCancel) clearTimeout(this.dasCancel);\r\n if(this.arrCancel) clearInterval(this.arrCancel);\r\n this.dasFired = false;\r\n this.dasCancel = null;\r\n this.arrCancel = null;\r\n this.curDir = null;\r\n }\r\n\r\n handleInputs(key, state) {\r\n\r\n\r\n if(this.takingInput){\r\n if(key === \"Escape\"){\r\n this.board.sketcher.unselectAll();\r\n this.paster.closeModal();\r\n }\r\n if(key === \"Enter\"){\r\n if(this.paster.active){\r\n this.paster.accept();\r\n this.board.updateDataPanel();\r\n }\r\n }\r\n\r\n // game controls\r\n if(key === this.settings.keybinds.leftKey){\r\n if(state) this.initiateDas(key,\"left\");\r\n else if(this.curDir === \"left\") this.cancelDas();\r\n }\r\n else if(key === this.settings.keybinds.rightKey){\r\n if(state) this.initiateDas(key,\"right\");\r\n else if(this.curDir === \"right\") this.cancelDas();\r\n }\r\n else if(key === this.settings.keybinds.softKey){\r\n if(state){\r\n this.softDrop = true;\r\n if(this.settings.softDrop === 0) this.board.shiftInstant(\"down\");\r\n this.softCancel = setInterval(() => {\r\n if(this.input.keysDown[key]) this.board.shiftPiece(\"down\");\r\n else clearInterval(this.softCancel); \r\n }, this.settings.softDrop);\r\n }\r\n else if(this.softDrop) {\r\n this.softDrop = false;\r\n clearInterval(this.softCancel);\r\n }\r\n }\r\n else if(state && key === this.settings.keybinds.hardKey){\r\n this.board.hardDrop();\r\n }\r\n else if(state && key === this.settings.keybinds.holdKey){\r\n this.board.holdPiece();\r\n }\r\n else if(state && key === this.settings.keybinds.crKey){\r\n this.board.rotateActive(1);\r\n }\r\n else if(state && key === this.settings.keybinds.ccrKey){\r\n this.board.rotateActive(-1);\r\n }\r\n else if(state && key === this.settings.keybinds.r180Key){\r\n this.board.rotateActive(2);\r\n }\r\n else if(state && key === this.settings.keybinds.restartKey){\r\n this.startGame();\r\n }\r\n else if(state && key === this.settings.keybinds.undoKey){\r\n this.board.setState(-1);\r\n }\r\n else if(state && key === this.settings.keybinds.redoKey){\r\n this.board.setState(1);\r\n }\r\n\r\n }\r\n }\r\n\r\n render() {\r\n this.board.renderBoard();\r\n this.paster.render();\r\n }\r\n\r\n startGame() {\r\n this.startTime = performance.now();\r\n this.board.resetBoard();\r\n this.start = true;\r\n }\r\n\r\n pauseInput(){\r\n this.takingInput = false;\r\n }\r\n\r\n resumeInput(){\r\n this.takingInput = true;\r\n }\r\n\r\n}\r\n\r\n\r\nlet app = new App();\r\n(() => {\r\n function main(tFrame){\r\n app.stopId = window.requestAnimationFrame(main);\r\n\r\n const nextTick = app.lastTick + app.tickLength;\r\n let numTicks = 0;\r\n\r\n if(tFrame > nextTick){\r\n const timeSinceTick = tFrame - app.lastTick;\r\n numTicks = Math.floor(timeSinceTick / app.tickLength);\r\n\r\n }\r\n\r\n for (let i = 0; i < numTicks; i++) {\r\n app.lastTick += app.tickLength;\r\n app.update(app.lastTick);\r\n }\r\n\r\n app.render();\r\n }\r\n\r\n main(performance.now());\r\n})()\r\n\r\n\r\n\n\n//# sourceURL=webpack://sketris/./src/sketris.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils.js */ \"./src/utils.js\");\n// import * as LZString from \"lz-string\";\r\n// import { start } from \"repl\";\r\n// import { needValidate } from \"schema-utils\";\r\n\r\n\r\nclass GameBoard {\r\n\r\n constructor(rows=20, cols=10, tileSize=30, spawnArea=2) {\r\n\r\n this.rows = rows;\r\n this.cols = cols;\r\n this.tileSize = tileSize;\r\n this.spawnArea = spawnArea;\r\n this.data = null;\r\n\r\n this.container = document.getElementById(\"boardArea\");\r\n\r\n // this.boardHeight = (rows+this.spawnArea)*tileSize; \r\n // this.boardWidth = cols*tileSize; \r\n\r\n // this.boardHeight = (rows+this.spawnArea)*tileSize; \r\n // this.boardWidth = cols*tileSize; \r\n\r\n this.playfield = new Playfield(this.rows, this.cols, this.tileSize, this.spawnArea);\r\n this.hold = new Hold(this.tileSize);\r\n this.queue = new Queue(this, this.hold, this.tileSize);\r\n\r\n this.sketcher = new Sketcher(this.playfield, () => this.getActivePiece(),\r\n () => this.saveState(),\r\n () => this.pauseActiveRender(),\r\n () => this.resumeActiveRender());\r\n this.history = new BoardHistory();\r\n\r\n this.activePiece = null;\r\n this.pauseActive = false;\r\n this.isPaused = false;\r\n this.qRegen = true;\r\n\r\n\r\n // set up import/export controls\r\n\r\n document.getElementById(\"importButton\").addEventListener(\"click\", (e) => {\r\n let datastring = document.getElementById(\"importText\").value;\r\n this.loadData(datastring);\r\n let el = document.querySelector( ':focus' );\r\n if( el ) el.blur();\r\n\r\n });\r\n document.getElementById(\"clearImport\").addEventListener(\"click\", (e) => {\r\n document.getElementById(\"importText\").value = \"\"\r\n let el = document.querySelector( ':focus' );\r\n if( el ) el.blur();\r\n });\r\n\r\n this.qRegenCheckbox = document.getElementById(\"queueRegen\");\r\n this.qRegenCheckbox.checked = this.qRegen;\r\n this.qRegenCheckbox.addEventListener(\"click\", (e) =>{\r\n this.qRegen = e.target.checked;\r\n this.updateDataPanel();\r\n });\r\n\r\n let holdInput = document.getElementById(\"holdInput\")\r\n holdInput.addEventListener(\"input\", (e) => {\r\n this.hold.setHold(document.getElementById(\"holdInput\").value);\r\n this.updateDataPanel();\r\n })\r\n holdInput.addEventListener(\"keydown\", (e) => {\r\n if(e.key == \"Enter\" || e.key == \"Tab\") {\r\n holdInput.blur();\r\n return;\r\n }\r\n if(e.key == \"Backspace\" || e.key == \"Delete\") {\r\n this.hold.setHold(null);\r\n this.updateDataPanel();\r\n }\r\n document.getElementById(\"holdInput\").value = \"\";\r\n })\r\n\r\n let customQueue = document.getElementById(\"customQueue\")\r\n customQueue.addEventListener(\"input\", (e) => {\r\n this.queue.edit = true;\r\n this.queue.updateQueue(document.getElementById(\"customQueue\").value);\r\n this.spawnPiece();\r\n })\r\n customQueue.addEventListener(\"keydown\", (e) => {\r\n if(e.key == \"Enter\") {\r\n // this.queue.updateQueue();\r\n // this.updateDataPanel();\r\n customQueue.blur();\r\n }\r\n })\r\n\r\n // export buttons\r\n document.getElementById(\"copyAsLink\").addEventListener(\"click\", (e) => {\r\n let link = \"https://mxmoss.me/sketris-dev/?data=\" + document.getElementById(\"exportArea\").value;\r\n // let link = \"localhost:8080/?data=\" + document.getElementById(\"exportArea\").value;\r\n this.setClipboard(link);\r\n e.target.blur();\r\n });\r\n\r\n document.getElementById(\"copyRaw\").addEventListener(\"click\", (e) => {\r\n let data = document.getElementById(\"exportArea\").value;\r\n this.setClipboard(data);\r\n e.target.blur();\r\n });\r\n\r\n\r\n }\r\n\r\n setClipboard(text){\r\n navigator.clipboard.writeText(text).then(\r\n () => {\r\n // console.log(\"success\")\r\n },\r\n () => {\r\n console.log(\"Err: failed to copy to clipboard.\")\r\n },\r\n );\r\n }\r\n\r\n getData(){\r\n let playfieldData = this.playfield.getData();\r\n let queueData = this.queue.getData(false);\r\n\r\n let buffer = new ArrayBuffer();\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.joinBuffers(_utils_js__WEBPACK_IMPORTED_MODULE_0__.encodeNumber(playfieldData, playfieldData.byteLength), buffer)\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.joinBuffers(_utils_js__WEBPACK_IMPORTED_MODULE_0__.encodeNumber(queueData, queueData.byteLength), buffer)\r\n\r\n // behvior settings\r\n let tmp = new Uint8Array(1);\r\n tmp[0] = (this.qRegen & 0x01);\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.joinBuffers(tmp.buffer, buffer);\r\n\r\n let s = _utils_js__WEBPACK_IMPORTED_MODULE_0__.dataToString(buffer);\r\n\r\n return s;\r\n }\r\n\r\n loadData(dataString){\r\n if(dataString == \"\"){\r\n this.data = null;\r\n this.resetBoard();\r\n return;\r\n }\r\n let currentState = this.getData();\r\n try {\r\n // this.history.reset();\r\n let b = _utils_js__WEBPACK_IMPORTED_MODULE_0__.stringToData(dataString);\r\n // load behavior\r\n let tmp = new Uint8Array(b);\r\n this.qRegen = tmp[0] & 0x01;\r\n b = b.slice(1);\r\n\r\n let splits = _utils_js__WEBPACK_IMPORTED_MODULE_0__.splitBuffer(b);\r\n\r\n this.queue.loadData(splits[0]);\r\n this.playfield.loadData(splits[1]);\r\n this.data = dataString;\r\n }\r\n catch {\r\n //restore last state\r\n this.loadData(currentState);\r\n alert(\"Err: Invalid Data\");\r\n }\r\n }\r\n\r\n updateDataPanel(){\r\n document.getElementById(\"holdInput\").value = this.hold.pieceName ? this.hold.pieceName : \"\" ;\r\n document.getElementById(\"customQueue\").value = this.queue.toString();\r\n document.getElementById(\"exportArea\").value = this.getData();\r\n }\r\n \r\n update(){\r\n this.qRegenCheckbox.checked = this.qRegen;\r\n if(!this.isPaused){\r\n this.queue.updateQueue();\r\n }\r\n if((!this.activePiece && this.queue.getCurrent()) || (this.activePiece && this.activePiece.name !== this.queue.getCurrent())){\r\n this.spawnPiece();\r\n }\r\n }\r\n\r\n lstGuide() {\r\n // mirror 0,1 3,4 to tallest\r\n\r\n let getCol = (i) => {return this.playfield.board.map((r) => r[i])}\r\n let topActiveIndex = (c) => {\r\n for(let i=0; i {\r\n for(let i=0; i{\r\n let topA = topActiveIndex(cols[a]);\r\n let topB = topActiveIndex(cols[b]);\r\n\r\n let lowCol = topA >= topB ? cols[a] : cols[b];\r\n let highCol = topA >= topB ? cols[b] : cols[a];\r\n let topInd = topActiveIndex(highCol);\r\n\r\n if(topA == topB){\r\n for(let i=(this.rows + this.spawnArea)-1; i >= 0; i--){\r\n lowCol[i].selected = false;\r\n highCol[i].selected = false;\r\n\r\n };\r\n return;\r\n }\r\n\r\n for(let i=(this.rows + this.spawnArea)-1; i >= 0; i--){\r\n let t = lowCol[i];\r\n if((i >= topInd && !t.active && highCol[i].active) || highCol[i].selected){\r\n t.selected = true;\r\n }else t.selected = false;\r\n\r\n if(i >= topInd){\r\n highCol[i].selected = false;\r\n }\r\n\r\n if(i < topInd && (lowCol[i].selected || highCol[i].selected)){\r\n lowCol[i].selected = true;\r\n highCol[i].selected = true;\r\n }\r\n }\r\n\r\n\r\n }\r\n \r\n mirror(0,4);\r\n mirror(1,3);\r\n\r\n //lst logic\r\n\r\n // return\r\n\r\n // check tops, 2 cases\r\n\r\n // select appropriate\r\n\r\n }\r\n\r\n countToFourGuide() {\r\n let getCol = (i) => {return this.playfield.board.map((r) => r[i])}\r\n let topActiveIndex = (c) => {\r\n for(let i=0; i (this.rows+this.spawnArea) - 1\r\n || tile.x > this.cols - 1 \r\n || playfield.board[tile.y][tile.x].active){\r\n return true;\r\n }\r\n }\r\n return false;\r\n\r\n }\r\n\r\n shiftPiece(dir, p=this.activePiece){\r\n if(p){\r\n // try update\r\n if(dir === \"left\") p.move(0,-1);\r\n else if(dir === \"right\") p.move(0,1);\r\n else if(dir === \"down\") p.move(1,0);\r\n\r\n if(!this.collisionCheck(p, this.playfield)){\r\n return true;\r\n }\r\n\r\n //revert\r\n if(dir === \"left\") p.move(0,1);\r\n else if(dir === \"right\") p.move(0,-1);\r\n else if(dir === \"down\") p.move(-1,0);\r\n }\r\n return false;\r\n }\r\n\r\n shiftInstant(dir, drop=false, p=this.activePiece){\r\n if(p){\r\n if(dir === \"left\") while(this.shiftPiece(\"left\", p)){\r\n if(drop) this.shiftInstant(\"down\", drop, p);\r\n }\r\n else if(dir === \"right\") while(this.shiftPiece(\"right\", p)){\r\n if(drop) this.shiftInstant(\"down\", drop, p);\r\n }\r\n else if(dir === \"down\") while(this.shiftPiece(\"down\", p)){}\r\n }\r\n }\r\n\r\n rotateActive(x){\r\n if(this.activePiece){\r\n let oldRot = this.activePiece.rot;\r\n let mod = (n, m) => ((n % m) + m) % m;\r\n let newRot = mod(this.activePiece.rot + x, 4);\r\n this.activePiece.rotate(newRot);\r\n\r\n let kicks;\r\n if(this.activePiece.name === \"i\") kicks = this.kickTable.i[oldRot][newRot];\r\n else kicks = this.kickTable.n[oldRot][newRot]; \r\n\r\n // TODO null check\r\n if(kicks){\r\n // try all kicks;\r\n for (let i = 0; i < kicks.length; i++) {\r\n let kick = kicks[i];\r\n this.activePiece.move(-kick[1], kick[0]);\r\n\r\n if(!this.collisionCheck(this.activePiece, this.playfield)){\r\n return true;\r\n }\r\n\r\n // revert\r\n this.activePiece.move(kick[1], -kick[0]);\r\n }\r\n };\r\n\r\n // revert\r\n this.activePiece.rotate(oldRot);\r\n }\r\n return false;\r\n }\r\n\r\n hardDrop(p=this.activePiece) {\r\n if(p){\r\n while(this.shiftPiece(\"down\", p)){}\r\n\r\n this.playfield.write(this.activePiece)\r\n this.playfield.clearLines();\r\n this.queue.queueStep();\r\n this.spawnPiece();\r\n }\r\n }\r\n\r\n resetBoard(hard=false) {\r\n if(this.data){\r\n this.loadData(this.data);\r\n }\r\n else{\r\n this.playfield.reset();\r\n this.hold.setHold(\"\");\r\n this.queue.reset();\r\n this.activePiece = null;\r\n this.spawnPiece();\r\n }\r\n }\r\n\r\n // TODO confirm kicks work, think there's some jank with the i\r\n kickTable = {\r\n // srs+ -- uses tetrio 180 kicks\r\n n :{\r\n 0 : {\r\n 0 : [[0,0]],\r\n 1 : [[0,0],[-1,0],[-1,1],[0,-2],[-1,-2]],\r\n 2 : [[0,0],[0,1],[1,1],[-1,1],[1,0],[-1,0]],\r\n 3 : [[0,0],[1,0],[1,1],[0,-2],[1,-2]],\r\n },\r\n 1 : {\r\n 0 : [[0,0],[1,0],[1,-1],[0,2],[1,2]],\r\n 1 : [[0,0]],\r\n 2 : [[0,0],[1,0],[1,-1],[0,2],[1,2]],\r\n 3 : [[0,0],[1,0],[1,2],[1,1],[0,2],[0,1]],\r\n },\r\n 2 : {\r\n 0 : [[0,0],[0,-1],[-1,-1],[1,-1],[-1,0],[1,0]],\r\n 1 : [[0,0],[-1,0],[-1,1],[0,-2],[-1,-2]],\r\n 2 : [[0,0]],\r\n 3 : [[0,0],[1,0],[1,1],[0,-2],[1,-2]],\r\n },\r\n 3 : {\r\n 0 : [[0,0],[-1,0],[-1,-1],[0,2],[-1,2]],\r\n 1 : [[0,0],[-1,0],[-1,2],[-1,1],[0,2],[0,1]],\r\n 2 : [[0,0],[-1,0],[-1,-1],[0,2],[-1,2]],\r\n 3 : [[0,0]],\r\n },\r\n },\r\n i : {\r\n 0 : {\r\n 0 : [[0,0]],\r\n 1 : [[0,0],[-2,0],[1,0],[-2,-1],[1,2]],\r\n 2 : [[0,0],[0,1],[1,1],[-1,1],[1,0],[-1,0]],\r\n 3 : [[0,0],[-1,0],[2,0],[-1,2],[2,-1]],\r\n },\r\n 1 : {\r\n 0 : [[0,0],[2,0],[-1,0],[2,1],[-1,-2]],\r\n 1 : [[0,0]],\r\n 2 : [[0,0],[-1,0],[2,0],[-1,2],[2,-1]],\r\n 3 : [[0,0],[1,0],[1,2],[1,1],[0,2],[0,1]],\r\n },\r\n 2 : {\r\n 0 : [[0,0],[0,-1],[-1,-1],[1,-1],[-1,0],[1,0]],\r\n 1 : [[0,0],[1,0],[-2,0],[1,-2],[-2,1]],\r\n 2 : [[0,0]],\r\n 3 : [[0,0],[2,0],[-1,0],[2,1],[-1,-2]],\r\n },\r\n 3 : {\r\n 0 : [[0,0],[1,0],[-2,0],[1,-2],[-2,1]],\r\n 1 : [[0,0],[-1,0],[-1,2],[-1,1],[0,2],[0,1]],\r\n 2 : [[0,0],[-2,0],[1,0],[-2,-1],[1,2]],\r\n 3 : [[0,0]],\r\n },\r\n\r\n }\r\n }\r\n\r\n}\r\n\r\nclass Playfield {\r\n constructor(rows=20, cols=10, tileSize=30, spawnArea=2, background=\"background\", canvas=\"field\"){\r\n this.rows = rows;\r\n this.cols = cols;\r\n this.tileSize = tileSize;\r\n this.spawnArea = spawnArea;\r\n\r\n\r\n // initialize board\r\n this.board = [];\r\n for (let i = 0; i < this.rows+this.spawnArea; i++) {\r\n let row = [];\r\n for (let j = 0; j < this.cols; j++) {\r\n row.push(new Tile(j,i));\r\n }\r\n this.board.push(row);\r\n }\r\n\r\n this.canvas = document.getElementById(canvas);\r\n this.ctx = this.canvas.getContext(\"2d\");\r\n\r\n this.backgroundCanvas = document.getElementById(background);\r\n this.backgroundCtx = this.backgroundCanvas.getContext(\"2d\");\r\n }\r\n\r\n getData(){\r\n // let colors = Object.entries(Piece.pieces).map((e) => e[1].color).concat([\"#555555\"]);\r\n let colors = [\"#555555\"].concat(Object.entries(Piece.pieces).map((e) => e[1].color));\r\n let getColorIndex = (x) => {\r\n for(const [i,c] of colors.entries()){\r\n if(_utils_js__WEBPACK_IMPORTED_MODULE_0__.colorComp(c, x)){\r\n return i;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n // board to bytes\r\n let totalTiles = 22 * 10 // TODO fix magic number\r\n let buffer = new ArrayBuffer(totalTiles, {maxByteLength: totalTiles})\r\n let byteBoard = new Uint8Array(buffer);\r\n let byteIndex = 0;\r\n\r\n let repeat = 0;\r\n\r\n for (let i = 0; i < totalTiles; i++) {\r\n let tile = this.board[i%22][Math.floor(i/22)]; // TODO fix magic number\r\n let tileData = 0;\r\n\r\n\r\n if(tile.active || tile.ghost || tile.selected){\r\n tileData = (tile.active << 7)\r\n | (tile.ghost << 6)\r\n | (tile.selected << 5)\r\n | (getColorIndex(tile.color) << 2);\r\n }\r\n\r\n if(byteIndex>=1 && tileData == byteBoard[byteIndex-1]){\r\n repeat += 1;\r\n if(i == totalTiles-1){\r\n // write repeat amount\r\n byteBoard[byteIndex-1] = byteBoard[byteIndex-1] | 1; // flip repeat indicator bit\r\n byteBoard[byteIndex] = repeat;\r\n byteIndex += 1;\r\n repeat = 0;\r\n }\r\n }\r\n else{\r\n if(repeat){\r\n // write repeat amount\r\n byteBoard[byteIndex-1] = byteBoard[byteIndex-1] | 1; // flip repeat indicator bit\r\n byteBoard[byteIndex] = repeat;\r\n byteIndex += 1;\r\n repeat = 0;\r\n }\r\n\r\n byteBoard[byteIndex] = tileData;\r\n byteIndex += 1;\r\n }\r\n\r\n // if(i == 16){\r\n // console.log(tile);\r\n // console.log(tileData.toString(2))\r\n // } \r\n\r\n } \r\n\r\n return byteBoard.buffer.slice(0,byteIndex);\r\n }\r\n\r\n loadData(buffer){\r\n this.reset();\r\n let b = new Uint8Array(buffer);\r\n let bIndex = 0;\r\n\r\n // let colors = Object.entries(Piece.pieces).map((e) => e[1].color).concat([\"#555555\"]);\r\n let colors = [\"#555555\"].concat(Object.entries(Piece.pieces).map((e) => e[1].color));\r\n\r\n let totalTiles = 22 * 10 // TODO fix magic number\r\n let i = 0;\r\n\r\n while(i < totalTiles){\r\n if(b[bIndex] & 0x1){\r\n let rNum = b[bIndex+1];\r\n for(let r=0; r> 7;\r\n tile.ghost = (data & 0x40) >> 6;\r\n tile.selected = (data & 0x20) >> 5;\r\n tile.color = colors[(data & 0x1c) >> 2]\r\n i += 1;\r\n }\r\n bIndex += 2;\r\n }\r\n else {\r\n let tile = this.board[i%22][Math.floor(i/22)]; // TODO fix magic number\r\n let data = b[bIndex];\r\n \r\n tile.active = (data & 0x80) >> 7;\r\n tile.ghost = (data & 0x40) >> 6;\r\n tile.selected = (data & 0x20) >> 5;\r\n tile.color = colors[(data & 0x1c) >> 2]\r\n i += 1;\r\n bIndex += 1;\r\n }\r\n }\r\n }\r\n\r\n setField(state){\r\n this.board = state.playfield.copy().board;\r\n }\r\n\r\n getAllTiles(){\r\n let tiles = [];\r\n for (let i = 0; i < this.rows+this.spawnArea; i++) {\r\n for (let j = 0; j < this.cols; j++) {\r\n tiles.push(this.board[i][j]);\r\n }\r\n }\r\n return tiles;\r\n }\r\n\r\n render(paused=false){\r\n // console.log( document.getElementById(\"boardArea\").hasFocus());\r\n // console.log( document.activeElement);\r\n\r\n this.boardHeight = (this.rows+this.spawnArea)*this.tileSize; \r\n this.boardWidth = this.cols*this.tileSize; \r\n\r\n this.canvas.width = this.boardWidth;\r\n this.canvas.height = this.boardHeight;\r\n\r\n this.backgroundCanvas.width = this.boardWidth;\r\n this.backgroundCanvas.height = this.boardHeight;\r\n\r\n // clear \r\n this.backgroundCtx.clearRect(0,0,this.canvas.width,this.canvas.height);\r\n this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);\r\n\r\n //spawn area\r\n this.backgroundCtx.strokeStyle = \"black\";\r\n this.backgroundCtx.globalAlpha = 0.6;\r\n this.backgroundCtx.fillRect(0,0,this.canvas.width,this.spawnArea*this.tileSize);\r\n\r\n // background + grid\r\n this.backgroundCtx.globalAlpha = 1;\r\n this.backgroundCtx.fillRect(0,this.spawnArea*this.tileSize,this.canvas.width,this.canvas.height);\r\n\r\n // grid lines\r\n this.backgroundCtx.strokeStyle = \"gray\";\r\n this.backgroundCtx.globalAlpha = 0.25;\r\n this.backgroundCtx.setLineDash([this.tileSize*0.25, this.tileSize*0.5, this.tileSize*0.25, 0]);\r\n for (let i=this.spawnArea; i < this.rows+3; i++){\r\n this.backgroundCtx.lineWidth = (i === this.spawnArea || i === this.rows+this.spawnArea) ? 1 : 2;\r\n this.backgroundCtx.beginPath();\r\n this.backgroundCtx.moveTo(0, i*(this.tileSize) )\r\n this.backgroundCtx.lineTo(this.boardWidth, i*(this.tileSize));\r\n this.backgroundCtx.stroke();\r\n }\r\n for (let i=0; i < this.cols+1; i++){\r\n this.backgroundCtx.lineWidth = (i === 0 || i === this.cols) ? 1 : 2;\r\n this.backgroundCtx.beginPath();\r\n this.backgroundCtx.moveTo(i*(this.tileSize), this.spawnArea*this.tileSize)\r\n this.backgroundCtx.lineTo(i*(this.tileSize), this.boardHeight);\r\n this.backgroundCtx.stroke();\r\n }\r\n this.backgroundCtx.globalAlpha = 1;\r\n\r\n\r\n // color board tiles\r\n for (let row = 0; row < this.board.length; row++) {\r\n for (let col = 0; col < this.board[row].length; col++) {\r\n let tile = this.board[row][col]\r\n if(tile){\r\n this.colorTile(tile);\r\n }\r\n }\r\n }\r\n\r\n //draw selection outlines\r\n for (let row = 0; row < this.board.length; row++) {\r\n for (let col = 0; col < this.board[row].length; col++) {\r\n let tile = this.board[row][col]\r\n if(tile){\r\n if(tile.selected){\r\n this.ctx.fillStyle = \"rgba(200,200,200, 1)\";\r\n this.ctx.fillRect((col*this.tileSize) - 2, (row*this.tileSize) - 2, this.tileSize+4, this.tileSize+4);\r\n }\r\n }\r\n }\r\n }\r\n\r\n //clear inner\r\n for (let row = 0; row < this.board.length; row++) {\r\n for (let col = 0; col < this.board[row].length; col++) {\r\n let tile = this.board[row][col]\r\n if(tile){\r\n if(tile.selected){\r\n this.ctx.clearRect((col*this.tileSize), (row*this.tileSize), this.tileSize, this.tileSize);\r\n }\r\n }\r\n }\r\n }\r\n\r\n\r\n // redraw tiles\r\n for (let row = 0; row < this.board.length; row++) {\r\n for (let col = 0; col < this.board[row].length; col++) {\r\n let tile = this.board[row][col]\r\n if(tile && tile.selected){\r\n if(!(tile.active||tile.ghost)){\r\n this.ctx.clearRect((col*this.tileSize), (row*this.tileSize), this.tileSize, this.tileSize);\r\n }\r\n else{\r\n this.colorTile(tile);\r\n }\r\n }\r\n }\r\n }\r\n\r\n if(paused){\r\n this.ctx.fillStyle = \"black\";\r\n this.ctx.globalAlpha = 0.4;\r\n this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);\r\n\r\n\r\n //pause symbol\r\n this.ctx.fillStyle = \"gray\";\r\n this.ctx.globalAlpha = 0.6;\r\n this.ctx.fillRect(this.canvas.width*0.5 - 30,this.canvas.height*0.5-30, 20, 60);\r\n this.ctx.fillRect(this.canvas.width*0.5 + 10,this.canvas.height*0.5-30, 20, 60);\r\n }\r\n\r\n }\r\n\r\n write(p){\r\n // write piece to board\r\n for (let i = 0; i < p.tiles.length; i++) {\r\n let tile = p.tiles[i];\r\n this.board[tile.y][tile.x].active = true;\r\n this.board[tile.y][tile.x].color = p.color;\r\n }\r\n }\r\n\r\n colorTile(tile, color=tile.color) {\r\n\r\n if(tile.color){\r\n if(tile.active){\r\n this.ctx.fillStyle = color;\r\n }\r\n else if(tile.ghost){\r\n\r\n let origColor = _utils_js__WEBPACK_IMPORTED_MODULE_0__.parseColor(color);\r\n origColor.a = 0.4;\r\n let ghostColor = \"rgba(\" + origColor.r + \",\" + origColor.g + \",\" + origColor.b + \",\" + origColor.a + \")\";\r\n\r\n this.ctx.fillStyle = ghostColor;\r\n\r\n }\r\n else return;\r\n this.ctx.clearRect(tile.x*this.tileSize, tile.y*this.tileSize, this.tileSize, this.tileSize);\r\n this.ctx.fillRect(tile.x*this.tileSize, tile.y*this.tileSize, this.tileSize, this.tileSize);\r\n }\r\n }\r\n\r\n isFull(row) {\r\n for (let i=0; i < row.length; i++) {\r\n if(!row[i].active) return false;\r\n }\r\n return true;\r\n }\r\n\r\n clearLines() {\r\n let newBoard = [];\r\n let cleared = 0;\r\n // check lines from bottom up and adjust \r\n for(let i=this.board.length-1; i>=0; i--){\r\n let row = this.board[i];\r\n if(this.isFull(row)){\r\n cleared += 1;\r\n }\r\n else{\r\n for(let j=0; j{return null}, saveState= ()=>{}, pauseActive=()=>{}, resumeActive=()=>{}){\r\n this.playfield = playfield;\r\n this.canvas = this.playfield.canvas;\r\n this.getActivePiece = getActivePiece;\r\n this.saveState = saveState;\r\n this.pauseActive = pauseActive;\r\n this.resumeActive = resumeActive;\r\n\r\n this.paletteOpen = false;\r\n\r\n // intialize color palette\r\n\r\n let paletteDiv = document.getElementById(\"palette\");\r\n let colors = [\"#555555\"].concat(Object.entries(Piece.pieces).map((e) => e[1].color));\r\n\r\n for (const [i,c] of colors.entries()) {\r\n let color = document.createElement(\"div\");\r\n color.classList.add(\"paletteColor\");\r\n color.id = \"color-\" + i;\r\n color.style.background = c;\r\n if(i == 0){\r\n color.classList.add(\"activeColor\");\r\n }\r\n\r\n paletteDiv.insertAdjacentElement(\"afterbegin\", color);\r\n this.palette.push(color)\r\n\r\n\r\n color.addEventListener('mousedown', (e) => {\r\n this.drawColor = e.target.style.background;\r\n this.updatePalette();\r\n })\r\n }\r\n\r\n document.getElementById(\"paletteSvg\").addEventListener(\"mousedown\", (e) => {\r\n this.paletteOpen = !this.paletteOpen;\r\n if(this.paletteOpen){\r\n for (const c of document.getElementsByClassName(\"paletteColor\")) {\r\n c.style.animationName = \"showPalette\";\r\n c.style.marginRight = \"0\";\r\n }\r\n }\r\n else{\r\n for (const c of document.getElementsByClassName(\"paletteColor\")) {\r\n c.style.animationName = \"hidePalette\";\r\n c.style.marginRight = \"-4em\";\r\n }\r\n }\r\n })\r\n\r\n // handle events outside of canvas\r\n document.addEventListener('mousedown',(e) => {\r\n if(e.button == 0) this.mouseLeftDown = true; \r\n if(e.button == 2) this.mouseRightDown = true; \r\n this.shiftKey = e.shiftKey;\r\n this.ctrlKey = e.ctrlKey;\r\n this.altKey = e.altKey;\r\n });\r\n document.addEventListener('mouseup',(e) => {\r\n if(e.button == 0) this.mouseLeftDown = false; \r\n if(e.button == 2) this.mouseRightDown = false; \r\n this.shiftKey = e.shiftKey;\r\n this.ctrlKey = e.ctrlKey;\r\n this.altKey = e.altKey;\r\n this.drawMode = false;\r\n });\r\n\r\n // mouse logic\r\n this.canvas.addEventListener('mousedown', (e) => {\r\n this.pauseActive();\r\n this.shiftKey = e.shiftKey;\r\n this.ctrlKey = e.ctrlKey;\r\n this.altKey = e.altKey;\r\n if(e.button == 0) this.mouseLeftDown = true; \r\n if(e.button == 2) this.mouseRightDown = true; \r\n let tile = this.getTile(e.offsetX, e.offsetY);\r\n if(!tile) return;\r\n if(e.button === 1){\r\n // color picker\r\n if(tile.active || tile.ghost){\r\n this.drawColor = tile.color;\r\n this.updatePalette();\r\n }\r\n }\r\n else if(e.shiftKey || e.ctrlKey) {\r\n this.mode = \"select\";\r\n this.selectMode = !tile.selected;\r\n if(this.mouseRightDown){\r\n this.selectRectStart = [tile.x, tile.y];\r\n this.selectRect(this.selectRectStart,[tile.x, tile.y], this.selectMode);\r\n }\r\n else this.select(tile, this.selectMode);\r\n }\r\n else if(tile.selected){\r\n // start drag\r\n // if(tile.selected){\r\n this.mode = \"drag\";\r\n this.startDragTile = tile;\r\n this.selected = this.playfield.getAllTiles().filter((t) => t.selected).map((t) => t.copy());\r\n this.selectionOffsets = this.selected.map((t) => {return {x: t.x - this.startDragTile.x, y: t.y - this.startDragTile.y}}, this);\r\n\r\n\r\n // compute bounds of drag\r\n let lowerX = Math.min(...this.selected.map((t) => t.x));\r\n let lowerY = Math.min(...this.selected.map((t) => t.y));\r\n let higherX = Math.max(...this.selected.map((t) => t.x));\r\n let higherY = Math.max(...this.selected.map((t) => t.y));\r\n\r\n this.dragBounds = {lowX: this.startDragTile.x-lowerX, \r\n highX: this.startDragTile.x+(this.playfield.cols-1)-higherX, \r\n lowY: this.startDragTile.y-lowerY,\r\n highY: this.startDragTile.y+(this.playfield.rows+this.playfield.spawnArea-1)-higherY\r\n };\r\n\r\n this.drag(tile);\r\n // }\r\n // else {\r\n // this.unselectFlag = true;\r\n // }\r\n }\r\n else{ //draw\r\n this.mode = \"draw\";\r\n // this.drawMode = !tile.active || this.drawColor != tile.color;\r\n if(this.altKey){\r\n this.drawMode = !tile.ghost; // || !utils.colorComp(tile.color, this.drawColor);\r\n }\r\n else{\r\n this.drawMode = !tile.active; // || !utils.colorComp(tile.color, this.drawColor);\r\n // this.drawMode = \"ghost\";\r\n }\r\n\r\n this.drawStartColor = tile.color;\r\n tile.color = this.drawMode ? this.drawColor : null;\r\n if(this.mouseRightDown){\r\n this.drawRectStart = [tile.x, tile.y];\r\n this.drawRect(this.drawRectStart,[tile.x, tile.y]);\r\n }\r\n else {\r\n this.draw(tile);\r\n }\r\n }\r\n });\r\n this.canvas.addEventListener('mousemove', (e) => {\r\n let tile = this.getTile(e.offsetX, e.offsetY);\r\n if(!tile) return;\r\n if(this.mode === \"draw\"){\r\n if(this.mouseRightDown){\r\n this.drawRect(this.drawRectStart,[tile.x, tile.y]);\r\n }\r\n else if(this.mouseLeftDown){\r\n this.draw(tile);\r\n }\r\n }\r\n else if (this.mode === \"select\"){\r\n if(this.mouseRightDown){\r\n this.selectRect(this.selectRectStart,[tile.x, tile.y], this.selectMode);\r\n }\r\n else if(this.mouseLeftDown){\r\n this.select(tile, this.selectMode);\r\n }\r\n }\r\n else if (this.mode === \"drag\" && this.mouseLeftDown) {\r\n this.drag(tile);\r\n }\r\n });\r\n this.canvas.addEventListener('mouseup', (e) =>{\r\n this.resumeActive();\r\n this.drawing = [];\r\n if(e.button == 0) this.mouseLeftDown = false; \r\n if(e.button == 2) this.mouseRightDown = false; \r\n this.drawMode = false;\r\n let tile = this.getTile(e.offsetX, e.offsetY);\r\n if(!tile) return;\r\n if(this.mode === \"drag\"){\r\n this.mode = \"select\";\r\n }\r\n\r\n this.saveState()\r\n });\r\n\r\n this.canvas.addEventListener('contextmenu', (e) => {e.preventDefault();e.stopPropagation();return false;}); // disable context menu\r\n this.canvas.addEventListener('focusout', this.resetInputs)\r\n this.canvas.addEventListener('mouseleave', this.resumeActive);\r\n this.canvas.addEventListener('mouseenter', () => {if(this.drawMode) this.pauseActive();});\r\n }\r\n\r\n resetInputs(e){\r\n this.mouseLeftDown = false;\r\n this.mouseRightDown = false;\r\n this.shiftKey = false;\r\n this.ctrlKey = false;\r\n this.altKey = false;\r\n }\r\n\r\n getTile(xOffset,yOffset){\r\n let b = this.canvas.getBoundingClientRect();\r\n let scale = this.canvas.width / parseFloat(b.width);\r\n\r\n\r\n let tileX = Math.floor((xOffset*scale) / (this.playfield.tileSize));\r\n let tileY = Math.floor((yOffset*scale) / (this.playfield.tileSize));\r\n\r\n tileX = Math.min(tileX, this.playfield.cols - 1);\r\n tileY = Math.min(tileY, (this.playfield.rows+this.playfield.spawnArea) - 1);\r\n\r\n return this.playfield.board[tileY][tileX];\r\n }\r\n\r\n isInActivePiece(t){\r\n let notActive = true;\r\n let active = this.getActivePiece();\r\n if(active){\r\n for(let i=0; i t.color = this.drawColor)}\r\n }\r\n }\r\n\r\n drawRect(startPos, mousePos){\r\n let startX = Math.min(startPos[0], mousePos[0]);\r\n let startY = Math.min(startPos[1], mousePos[1]);\r\n let endX = Math.max(startPos[0], mousePos[0]);\r\n let endY = Math.max(startPos[1], mousePos[1]);\r\n\r\n for(let row = startY; row<=endY; row++){\r\n for(let col = startX; col<=endX; col++){\r\n let tile = this.playfield.board[row][col];\r\n this.draw(tile);\r\n\r\n\r\n\r\n // tile.active = this.drawMode;\r\n // // tile.selected = false;\r\n // tile.color = this.drawMode ? this.drawColor : null;\r\n }\r\n }\r\n }\r\n\r\n select(tile, state=true){\r\n // if(tile.active && this.shiftKey && this.mouseLeftDown){\r\n if((this.shiftKey || this.ctrlKey) && (this.mouseLeftDown || this.mouseRightDown)){\r\n tile.selected = state;\r\n }\r\n }\r\n\r\n selectRect(startPos, mousePos, state){\r\n let startX = Math.min(startPos[0], mousePos[0]);\r\n let startY = Math.min(startPos[1], mousePos[1]);\r\n let endX = Math.max(startPos[0], mousePos[0]);\r\n let endY = Math.max(startPos[1], mousePos[1]);\r\n\r\n for(let row = startY; row<=endY; row++){\r\n for(let col = startX; col<=endX; col++){\r\n let tile = this.playfield.board[row][col];\r\n this.select(tile, state);\r\n }\r\n }\r\n }\r\n\r\n unselectAll(){\r\n let tiles = this.playfield.getAllTiles();\r\n for (let i=0; i= this.dragBounds.lowX \r\n // && tile.x <= this.dragBounds.highX \r\n // && tile.y >= this.dragBounds.lowY \r\n // && tile.y <= this.dragBounds.highY \r\n // }\r\n\r\n drag(curTile){\r\n if(curTile !== this.startDragTile){\r\n\r\n // let clamp = (x,lower,higher) => Math.max(Math.min(x, higher), lower);\r\n let shiftX = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(curTile.x, this.dragBounds.lowX, this.dragBounds.highX);\r\n let shiftY = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(curTile.y, this.dragBounds.lowY, this.dragBounds.highY);\r\n\r\n // erase and update selected\r\n for(let i=0; i {\r\n return new Tile(this.pos.x+o[1], this.pos.y+o[0], true, this.color);\r\n });\r\n }\r\n\r\n static pieces = {\r\n i : {\r\n 0 : [[1,0],[1,1],[1,2],[1,3]],\r\n 1 : [[0,2],[1,2],[2,2],[3,2]],\r\n 2 : [[2,0],[2,1],[2,2],[2,3]],\r\n 3 : [[0,1],[1,1],[2,1],[3,1]],\r\n color : \"#0f9bd7\"\r\n },\r\n j :{\r\n 0 : [[0,0],[1,0],[1,1],[1,2]],\r\n 1 : [[0,1],[0,2],[1,1],[2,1]],\r\n 2 : [[1,0],[1,1],[1,2],[2,2]],\r\n 3 : [[2,0],[2,1],[1,1],[0,1]],\r\n color : \"#2141c6\"\r\n },\r\n l :{\r\n 0 : [[1,0],[1,1],[1,2],[0,2]],\r\n 1 : [[0,1],[1,1],[2,1],[2,2]],\r\n 2 : [[1,0],[1,1],[1,2],[2,0]],\r\n 3 : [[0,0],[2,1],[1,1],[0,1]],\r\n color : \"#e35b02\"\r\n },\r\n o :{\r\n 0 : [[0,1],[0,2],[1,1],[1,2]],\r\n 1 : [[0,1],[0,2],[1,1],[1,2]],\r\n 2 : [[0,1],[0,2],[1,1],[1,2]],\r\n 3 : [[0,1],[0,2],[1,1],[1,2]],\r\n color : \"#e39f02\"\r\n },\r\n s :{\r\n 0 : [[1,0],[1,1],[0,1],[0,2]],\r\n 1 : [[0,1],[1,1],[1,2],[2,2]],\r\n 2 : [[2,0],[2,1],[1,1],[1,2]],\r\n 3 : [[0,0],[1,0],[1,1],[2,1]],\r\n color : \"#59b101\"\r\n },\r\n t :{\r\n 0 : [[1,0],[0,1],[1,1],[1,2]],\r\n 1 : [[0,1],[1,1],[1,2],[2,1]],\r\n 2 : [[1,0],[1,1],[1,2],[2,1]],\r\n 3 : [[1,0],[0,1],[1,1],[2,1]],\r\n color : \"#af298a\"\r\n },\r\n z :{\r\n 0 : [[0,0],[0,1],[1,1],[1,2]],\r\n 1 : [[1,1],[0,2],[1,2],[2,1]],\r\n 2 : [[1,0],[1,1],[2,1],[2,2]],\r\n 3 : [[1,0],[2,0],[0,1],[1,1]],\r\n color : \"#d70f37\"\r\n },\r\n }\r\n\r\n\r\n}\r\n\r\nclass Queue {\r\n pieces = [\"i\", \"j\", \"l\", \"t\", \"s\", \"z\", \"o\"];\r\n queue = [];\r\n queueIndex = 0;\r\n customPieces = 0; // counts manually injected pieces for bag indicator\r\n regen = true; // refill on empty queue\r\n\r\n slots = 5;\r\n slotSizeY = 3;\r\n slotSizeX = 6;\r\n showMax = 5;\r\n\r\n\r\n constructor(gameBoard, hold, tileSize=30) {\r\n this.gameBoard = gameBoard;\r\n this.hold = hold;\r\n this.edit = false;\r\n\r\n this.canvas = document.getElementById(\"queue\");\r\n this.ctx = this.canvas.getContext(\"2d\");\r\n this.tileSize = tileSize;\r\n\r\n // document.getElementById(\"customQueue\").addEventListener(\"change\", (e) => {\r\n // e.target.value = this.formatString(e.target.value);\r\n // })\r\n\r\n\r\n this.updateQueue();\r\n this.renderQueue();\r\n }\r\n\r\n getData(history=true){\r\n let pieces = Object.entries(Piece.pieces).map((e) => e[0]);\r\n let getPieceIndex = (x) => {\r\n for(const [i,c] of pieces.entries()){\r\n if(c == x){\r\n return i;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n // build buffer backwards\r\n let buffer = new ArrayBuffer();\r\n\r\n // queue pieces\r\n let q = history ? this.queue : this.queue.slice(this.queueIndex);\r\n let tmp = new Uint8Array(Math.ceil(q.length/2));\r\n for(const [i,p] of q.entries()){\r\n let shift = (i % 2 == 0 ? 4 : 0);\r\n let byte = tmp[Math.floor(i/2)];\r\n byte = byte | (getPieceIndex(p) << shift);\r\n tmp[Math.floor(i/2)] = byte;\r\n // byteIndex += (i % 2 == 0 ? 0 : 1);\r\n }\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.joinBuffers(tmp.buffer, buffer);\r\n\r\n // queue length\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.encodeNumber(buffer, q.length);\r\n\r\n // queue Index\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.encodeNumber(buffer, history ? this.queueIndex : 0);\r\n\r\n // custom pieces number\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.encodeNumber(buffer, this.customPieces);\r\n\r\n // hold data \r\n tmp = new Uint8Array(1);\r\n tmp[0] = (this.hold.pieceName && 1) << 7 | (getPieceIndex(this.hold.pieceName) << 4);\r\n buffer = _utils_js__WEBPACK_IMPORTED_MODULE_0__.joinBuffers(tmp.buffer, buffer);\r\n\r\n\r\n return buffer;\r\n }\r\n\r\n loadData(buffer){\r\n let pieces = Object.entries(Piece.pieces).map((e) => e[0]);\r\n\r\n let newQueue = [];\r\n\r\n // parse hold\r\n let tmp = new Uint8Array(buffer);\r\n let holdByte = tmp[0];\r\n if(holdByte & (1<<7)){\r\n this.hold.pieceName = pieces[(holdByte & 0x70) >> 4];\r\n }\r\n else {\r\n this.hold.pieceName = null;\r\n }\r\n buffer = buffer.slice(1);\r\n\r\n // parse customPieces\r\n let decoded = _utils_js__WEBPACK_IMPORTED_MODULE_0__.decodeNumber(buffer);\r\n this.customPieces = decoded.num;\r\n buffer = decoded.buffer;\r\n\r\n // parse queue index\r\n decoded = _utils_js__WEBPACK_IMPORTED_MODULE_0__.decodeNumber(buffer);\r\n this.queueIndex = decoded.num;\r\n buffer = decoded.buffer;\r\n\r\n\r\n // parse queue length\r\n decoded = _utils_js__WEBPACK_IMPORTED_MODULE_0__.decodeNumber(buffer);\r\n let length = decoded.num;\r\n buffer = decoded.buffer;\r\n\r\n // parse pieces\r\n tmp = new Uint8Array(buffer);\r\n for(let i=0; i> shift]);\r\n }\r\n this.queue = newQueue\r\n }\r\n\r\n toString(){\r\n return this.queue.slice(this.queueIndex).join(\"\");\r\n\r\n // let s = \"\";\r\n // for (let i = this.queueIndex; i < this.queue.length; i++) {\r\n // s += this.queue[i];\r\n // if(((i - this.customPieces) % 7) == 6){\r\n // s += \"\\n\";\r\n // }\r\n // }\r\n // return s;\r\n }\r\n\r\n formatString(s){\r\n let newS = \"\"\r\n let counter = 0;\r\n for(const c of s.trim()){\r\n if(counter >= 7){\r\n newS += '\\n';\r\n counter = 0;\r\n }\r\n else{\r\n newS += c;\r\n counter ++;\r\n }\r\n }\r\n newS = newS.replace(/(\\s)\\1{2,}/g, '\\n'); // replace all repeated whitespace\r\n\r\n return newS\r\n }\r\n\r\n copy() {\r\n let copy = new Queue(this.tileSize);\r\n copy.queue = structuredClone(this.queue);\r\n copy.queueIndex = this.queueIndex;\r\n return copy;\r\n }\r\n\r\n renderQueue() {\r\n this.queueHeight = this.tileSize*this.slots*this.slotSizeY + this.tileSize;\r\n this.queueWidth = this.tileSize*this.slotSizeX;\r\n\r\n this.canvas.height = this.queueHeight;\r\n this.canvas.width = this.queueWidth;\r\n\r\n // if(this.queue.length < this.showMax) return;\r\n this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);\r\n // this.ctx.fillStyle = \"orange\";\r\n // this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);\r\n\r\n\r\n for (let i = 0; i < this.slots; i++) {\r\n if(i < this.showMax && this.queue[this.queueIndex+i+1]){\r\n this.drawPiece(new Piece(this.queue[this.queueIndex+i+1]), this.tileSize, (i*this.slotSizeY*this.tileSize) + this.tileSize); // TODO better spacing ?\r\n }\r\n }\r\n\r\n // bag postion indicator\r\n for(let i=0; i<7; i++){\r\n this.ctx.lineWidth = 4;\r\n this.ctx.strokeStyle = (this.queueIndex - ((this.hold.pieceName ? 1 : 0) + this.customPieces + i)) % 7 == 0 ? \"rgba(200,200,200,0.4)\" : \"rgba(100,100,100,0.3)\" ;\r\n // this.ctx.strokeStyle = (this.queueIndex - (this.customPieces + i)) % 7 == 0 ? \"rgba(200,200,200,0.4)\" : \"rgba(100,100,100,0.3)\" ;\r\n this.ctx.beginPath();\r\n this.ctx.moveTo(i*(this.queueWidth/7)+5, 0)\r\n this.ctx.lineTo((i+1)*(this.queueWidth/7)-5, 0);\r\n this.ctx.stroke();\r\n\r\n }\r\n }\r\n\r\n drawPiece(p,x,y){\r\n\r\n for(let i=0; i {\r\n let copy = structuredClone(arr)\r\n for (let i = copy.length - 1; i > 0; i--) {\r\n let j = Math.floor(Math.random() * (i + 1));\r\n [copy[i], copy[j]] = [copy[j], copy[i]];\r\n }\r\n return copy\r\n }\r\n this.queue.push(...shuffle(Object.entries(Piece.pieces).map((x) => x[0])));\r\n // this.bagStarts.add(this.queue.length);\r\n }\r\n return true;\r\n }\r\n }\r\n\r\n setQueueIndex(i){\r\n this.queueIndex = i;\r\n }\r\n\r\n queueStep(){\r\n this.edit = false;\r\n this.updateQueue();\r\n this.queueIndex += 1;\r\n }\r\n\r\n getCurrent(){\r\n return this.queue[this.queueIndex];\r\n }\r\n\r\n setCurrent(p){\r\n this.queue[this.queueIndex] = p; \r\n }\r\n\r\n reset() {\r\n this.queue = [];\r\n this.queueIndex = 0;\r\n this.customPieces = 0;\r\n this.updateQueue();\r\n }\r\n\r\n}\r\n\r\n\r\nclass Hold {\r\n pieceName;\r\n\r\n constructor(tileSize=30) {\r\n\r\n this.canvas = document.getElementById(\"hold\");\r\n this.ctx = this.canvas.getContext(\"2d\");\r\n this.tileSize = tileSize;\r\n\r\n this.renderHold();\r\n }\r\n\r\n copy() {\r\n let copy = new Hold(this.tileSize);\r\n copy.pieceName = this.pieceName;\r\n return copy;\r\n }\r\n\r\n\r\n renderHold(){\r\n this.holdHeight = this.tileSize*4;\r\n this.holdWidth = this.tileSize*6;\r\n\r\n this.canvas.height = this.holdHeight;\r\n this.canvas.width = this.holdWidth;\r\n\r\n this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);\r\n if(this.pieceName){\r\n this.drawPiece(new Piece(this.pieceName), this.tileSize, this.tileSize); //TODO better spacing \r\n }\r\n }\r\n\r\n drawPiece(p,x,y){\r\n for(let i=0; i e[1].color).concat([\"#555555\"]);\r\n let colors = [\"#555555\"].concat(Object.entries(Piece.pieces).map((e) => e[1].color));\r\n let getColorIndex = (x) => {\r\n for(const [i,c] of colors.entries()){\r\n if(_utils_js__WEBPACK_IMPORTED_MODULE_0__.colorComp(c, x)){\r\n return i;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n // board to bytes\r\n let totalTiles = 22 * 10 // TODO fix magic number\r\n let byteBoard = new Uint8Array(totalTiles+1);\r\n\r\n byteBoard[0] = 0;\r\n foundActive = false;\r\n\r\n for (let i = 0; i < totalTiles; i++) {\r\n let tile = p.board[Math.floor(i/10)][i%10]; // TODO fix magic number\r\n\r\n // assume board starts with many empty tiles store count in first byte\r\n if(!foundActive && !(tile.active || tile.ghost || tile.selected)){\r\n byteBoard[0] += 1;\r\n }\r\n else {\r\n foundActive = true;\r\n let tileData = (tile.active << 7)\r\n | (tile.ghost << 6)\r\n | (tile.selected << 5)\r\n | (getColorIndex(tile.color) << 2);\r\n\r\n }\r\n\r\n\r\n\r\n\r\n if(tile.active || tile.ghost || tile.selected) {\r\n let posByte = i;\r\n let infoByte = (tile.active << 7)\r\n | (tile.ghost << 6)\r\n | (tile.selected << 5)\r\n | (getColorIndex(tile.color) << 2);\r\n // console.log(byte.toString(2).padStart(16, '0'))\r\n byteBoard[size] = posByte;\r\n byteBoard[size+1] = infoByte;\r\n size += 2;\r\n }\r\n // console.log(String(byte.toString(2)).padStart(8, '0'));\r\n }\r\n\r\n byteBoard = byteBoard.subarray(0,size);\r\n\r\n //convert to base-64 string\r\n let s = \"\";\r\n\r\n for(let i=0; i < byteBoard.length; i++){\r\n // console.log(byteBoard[i].toString(2).padStart(8,'0'))\r\n s += String.fromCharCode(byteBoard[i]);\r\n }\r\n\r\n\r\n return window.btoa(s);\r\n }\r\n\r\n static stringToGameState(s){\r\n\r\n //decode string\r\n let decoded = window.atob(s);\r\n\r\n\r\n //build board\r\n let b = new Uint8Array(decoded.length);\r\n for(let i=0; i this.keyDown(e));\r\n window.addEventListener(\"keyup\", (e) => this.keyUp(e));\r\n\r\n document.getElementById(\"field\").addEventListener('focusout', this.resetInputs); \r\n\r\n this.callback = callback;\r\n }\r\n\r\n resetInputs(e){\r\n this.keysDown = {};\r\n this.lastKey = null;\r\n }\r\n\r\n keyDown(e) {\r\n if(!this.keysDown[e.code]) {\r\n this.keysDown[e.code] = performance.now();\r\n this.lastKey = e.code;\r\n this.callback(e.code, true);\r\n }\r\n }\r\n\r\n keyUp(e) {\r\n this.keysDown[e.code] = null;\r\n this.callback(e.code, false);\r\n }\r\n\r\n}\r\n\r\nclass Settings{\r\n // gravity = 1000;\r\n\r\n // Default Settings\r\n das = 130;\r\n arr = 50;\r\n softDrop = 50;\r\n keybinds = {\r\n leftKey: 'ArrowLeft',\r\n rightKey: 'ArrowRight',\r\n softKey: 'ArrowDown',\r\n hardKey: 'Space',\r\n holdKey: 'KeyC',\r\n ccrKey: 'KeyZ',\r\n crKey: 'ArrowUp',\r\n r180Key: 'KeyA',\r\n restartKey: 'KeyR',\r\n undoKey: 'KeyQ',\r\n redoKey: 'KeyW',\r\n // spinKey: 'a',\r\n };\r\n\r\n constructor(input, pauseInput, resumeInput, board){\r\n this.input = input;\r\n this.pause = pauseInput;\r\n this.resume = resumeInput;\r\n this.board = board;\r\n\r\n this.loadSettings();\r\n\r\n let s = document.getElementById(\"settings\")\r\n \r\n // s.addEventListener(\"focusin\", (e) => this.pause());\r\n // s.addEventListener(\"focusout\", (e) => this.resume());\r\n\r\n // queue\r\n\r\n // document.getElementById(\"handling\").addEventListener(\"mousedown\", (e) => {\r\n // console.log(\"data\");\r\n // });\r\n\r\n // handling\r\n for(const [k, v] of Object.entries(this)){\r\n if(k === \"das\" || k === \"arr\" || k === \"softDrop\"){\r\n let slider = document.getElementById(k + \"Slider\");\r\n let label = document.getElementById(k+\"SliderVal\");\r\n slider.value = v;\r\n label.innerHTML = v == 0 ? \"Instant\" : v + \" ms\";\r\n\r\n slider.addEventListener(\"input\", (e) => {\r\n label.innerHTML = e.target.value == 0 ? \"Instant\" : e.target.value + \" ms\";\r\n });\r\n\r\n slider.addEventListener(\"change\", (e) => {\r\n // label.innerHTML = e.target.value + \" ms\";\r\n label.innerHTML = e.target.value == 0 ? \"Instant\" : e.target.value + \" ms\";\r\n this.update(k, e.target.value);\r\n this.saveSettings();\r\n e.target.blur();\r\n });\r\n }\r\n }\r\n\r\n\r\n // keybinds\r\n document.getElementById(\"changeAll\").addEventListener(\"click\", (e) => this.changeAllKeys());\r\n for(const [k, v] of Object.entries(this.keybinds)){\r\n document.getElementById(k+\"Button\").addEventListener(\"click\", (e) => {\r\n document.getElementById(k+\"Button\").blur(); // needed to make sure spacebar doesn't retrigger button\r\n this.updateKey(k);\r\n })\r\n }\r\n\r\n this.updateDisplay();\r\n }\r\n \r\n loadSettings() {\r\n let cookieSettingsRaw = decodeURIComponent(document.cookie).split(\"; \").find((row) => row.startsWith(\"settings=\"));\r\n if(cookieSettingsRaw){\r\n let cookieSettings = JSON.parse(cookieSettingsRaw.split(\"=\")[1]);\r\n this.das = cookieSettings.das;\r\n this.arr = cookieSettings.arr;\r\n this.softDrop = cookieSettings.softDrop;\r\n this.keybinds = cookieSettings.keybinds;\r\n }\r\n }\r\n\r\n saveSettings() {\r\n let cookie = {\r\n das : this.das,\r\n arr : this.arr,\r\n softDrop : this.softDrop,\r\n keybinds : this.keybinds\r\n }\r\n\r\n let x = new Date();\r\n x.setTime(x.getTime() + 24*60*60*365*1000); // 1 year;\r\n \r\n document.cookie = \"settings=\" + encodeURIComponent(JSON.stringify(cookie))\r\n + \"; expires=\" + x.toUTCString() + \"; SameSite=None; secure\";\r\n\r\n }\r\n\r\n removeFocus() {\r\n document.getElementById(\"queueUpdate\").blur();\r\n }\r\n\r\n updateDisplay(){\r\n for(const [k, v] of Object.entries(this.keybinds)){\r\n let keyText = v;\r\n if(v.slice(0,3) == \"Key\") keyText = keyText.slice(3);\r\n // document.getElementById(k+\"Text\").innerHTML = (v === \" \" ? \"Space\" : v);\r\n document.getElementById(k+\"Text\").innerHTML = keyText;\r\n }\r\n\r\n this.saveSettings();\r\n }\r\n\r\n async changeAllKeys(){\r\n for(const [k, v] of Object.entries(this.keybinds)){\r\n let keyText = document.getElementById(k+\"Text\");\r\n keyText.scrollIntoView({ behavior: \"smooth\", block: \"center\", inline: \"nearest\" });\r\n keyText.innerHTML = \"Waiting...\";\r\n let newKey = await this.awaitKey();\r\n\r\n\r\n this.keybinds[k] = newKey;\r\n this.updateDisplay();\r\n }\r\n document.getElementById(\"changeAll\").blur(); // needed to make sure spacebar doesn't retrigger button\r\n }\r\n\r\n update(s, newVal){\r\n this[s] = parseInt(newVal);\r\n // this.updateDisplay();\r\n }\r\n\r\n async updateKey(k){\r\n this.pause();\r\n document.getElementById(k+\"Text\").innerHTML = \"Waiting...\";\r\n\r\n let newKey = await this.awaitKey();\r\n this.keybinds[k] = newKey;\r\n\r\n this.resume();\r\n this.updateDisplay();\r\n }\r\n\r\n awaitKey() {\r\n let waitKey = new Promise((resolve) => {\r\n document.addEventListener(\"keydown\", (e) => {\r\n e.preventDefault();\r\n resolve(e.code);\r\n }, {once: true}\r\n )\r\n })\r\n return waitKey;\r\n }\r\n}\r\n\r\nclass Slider {\r\n\r\n constructor(type){\r\n this.type = type // horizontal or vertical\r\n }\r\n\r\n\r\n\r\n}\r\n\r\n\r\nclass Paster {\r\n active = false;\r\n dragging = null;\r\n\r\n\r\n highlightSlider = null;\r\n sliderLen = 40;\r\n sliderWid = 10;\r\n sliderColor = \"rgba(150,150,150,1)\"\r\n sliderHighlightColor = \"rgba(220,220,220,1)\"\r\n \r\n constructor(gameBoard) {\r\n this.gameBoard = gameBoard;\r\n this.preview = new Playfield(20, 10, 30, 2, \"previewBackground\", \"previewField\");\r\n // this.sketcher = new Sketcher(this.preview);\r\n\r\n\r\n\r\n this.imgCanvas = document.getElementById(\"imageCanvas\");\r\n this.imgCtx = this.imgCanvas.getContext(\"2d\", { willReadFrequently: true });\r\n\r\n this.ctrlsCanvas = document.getElementById(\"imageControls\");\r\n this.ctrlsCtx = this.ctrlsCanvas.getContext(\"2d\");\r\n\r\n this.pasterAccept = document.getElementById(\"pasterAccept\");\r\n this.pasterAccept.addEventListener(\"click\", (e) => {\r\n if(this.active){\r\n this.accept();\r\n }\r\n })\r\n\r\n this.pasterCancel = document.getElementById(\"pasterCancel\");\r\n this.pasterCancel.addEventListener(\"click\", (e) => {\r\n if(this.active){\r\n this.cancel();\r\n }\r\n })\r\n\r\n\r\n\r\n\r\n // handle pasted images\r\n document.addEventListener(\"paste\", (e) => {\r\n\r\n if(e.clipboardData.getData(\"text\")) return;\r\n\r\n this.openModal();\r\n\r\n // load image\r\n if (e.clipboardData.files.length > 0) {\r\n let file = e.clipboardData.files[0]\r\n let reader = new FileReader();\r\n reader.onload = (e) => {\r\n this.img = new Image();\r\n this.img.onload = () => {\r\n\r\n this.imgCanvas = document.getElementById(\"imageCanvas\");\r\n this.imgCanvas.width = window.innerWidth * 0.4 * 0.8;\r\n this.imgCanvas.height = window.innerHeight * 0.8 * 0.8;\r\n this.imgCtx = this.imgCanvas.getContext(\"2d\", { willReadFrequently: true });\r\n this.imgBounds = {p1:{x:0,y:0},p2:{x:this.imgCanvas.width,y:this.imgCanvas.height}}\r\n\r\n this.ctrlsCanvas = document.getElementById(\"imageControls\");\r\n this.ctrlsCanvas.width = window.innerWidth * 0.4 * 0.8;\r\n this.ctrlsCanvas.height = window.innerHeight * 0.8 * 0.8;\r\n this.ctrlsCtx = this.ctrlsCanvas.getContext(\"2d\");\r\n\r\n\r\n let ratio = Math.min(this.imgCanvas.width / this.img.width, this.imgCanvas.height / this.img.height);\r\n let newWidth = this.img.width * ratio;\r\n let newHeight = this.img.height * ratio;\r\n let x = (this.imgCanvas.width/2) - (newWidth/2);\r\n let y = (this.imgCanvas.height/2) - (newHeight/2);\r\n this.imgBounds = {p1:{x:x,y:y},p2:{x:x+newWidth,y:y+newHeight}};\r\n\r\n this.top = Math.max(this.sliderWid/2, this.imgBounds.p1.y);\r\n this.bottom = Math.min(this.imgCanvas.height - this.sliderWid/2, this.imgBounds.p2.y);\r\n this.left = Math.max(this.sliderWid/2, this.imgBounds.p1.x);\r\n this.right = Math.min(this.imgCanvas.width - this.sliderWid/2, this.imgBounds.p2.x);\r\n\r\n this.drawImage();\r\n this.update();\r\n this.render();\r\n };\r\n this.img.src = e.target.result;\r\n }\r\n reader.readAsDataURL(file);\r\n }\r\n })\r\n\r\n\r\n // mouse event listeners\r\n this.ctrlsCanvas.addEventListener(\"mousedown\", (e) => {\r\n let b = this.ctrlsCanvas.getBoundingClientRect();\r\n let scale = this.ctrlsCanvas.width / parseFloat(b.width);\r\n\r\n let xPos = e.offsetX * scale;\r\n let yPos = e.offsetY * scale;\r\n\r\n\r\n if(Math.abs(xPos - (this.left+(this.right-this.left)/2)) < this.sliderLen+5 && \r\n Math.abs(yPos - this.top) < this.sliderWid+5){\r\n this.dragging = \"top\";\r\n this.highlight = \"top\";\r\n this.top = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(yPos, this.sliderWid/2, this.bottom - this.sliderWid);\r\n }\r\n else if(Math.abs(xPos - (this.left+(this.right-this.left)/2)) < this.sliderLen+5 && \r\n Math.abs(yPos - this.bottom) < this.sliderWid+5){\r\n this.dragging = \"bottom\";\r\n this.highlight = \"bottom\";\r\n this.bottom = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(yPos, this.top+this.sliderWid, this.imgCanvas.height-this.sliderWid/2);\r\n }\r\n else if(Math.abs(yPos - (this.top+(this.bottom-this.top)/2)) < this.sliderLen+5 && \r\n Math.abs(xPos - this.left) < this.sliderWid+5){\r\n this.dragging = \"left\";\r\n this.highlight = \"left\";\r\n this.left = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(xPos, this.sliderWid/2, this.right-this.sliderWid);\r\n }\r\n else if(Math.abs(yPos - (this.top+(this.bottom-this.top)/2)) < this.sliderLen+5 && \r\n Math.abs(xPos - this.right) < this.sliderWid+5){\r\n this.dragging = \"right\";\r\n this.highlight = \"right\";\r\n this.right = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(xPos, this.left+this.sliderWid, this.imgCanvas.width - this.sliderWid/2);\r\n }\r\n });\r\n\r\n this.ctrlsCanvas.addEventListener(\"mousemove\", (e) => {\r\n let b = this.ctrlsCanvas.getBoundingClientRect();\r\n let scale = this.ctrlsCanvas.width / parseFloat(b.width);\r\n\r\n let xPos = e.offsetX * scale;\r\n let yPos = e.offsetY * scale;\r\n\r\n if(this.dragging == \"top\" ||\r\n (Math.abs(xPos - (this.left+(this.right-this.left)/2)) < this.sliderLen+5 && \r\n Math.abs(yPos - this.top) < this.sliderWid+5)){\r\n this.highlight = \"top\";\r\n }\r\n else if(this.dragging == \"bottom\" ||\r\n (Math.abs(xPos - (this.left+(this.right-this.left)/2)) < this.sliderLen+5 && \r\n Math.abs(yPos - this.bottom) < this.sliderWid+5)){\r\n this.highlight = \"bottom\";\r\n }\r\n else if(this.dragging == \"left\" ||\r\n (Math.abs(yPos - (this.top+(this.bottom-this.top)/2)) < this.sliderLen+5 && \r\n Math.abs(xPos - this.left) < this.sliderWid+5)){\r\n this.highlight = \"left\";\r\n }\r\n else if(this.dragging == \"right\" ||\r\n (Math.abs(yPos - (this.top+(this.bottom-this.top)/2)) < this.sliderLen+5 && \r\n Math.abs(xPos - this.right) < this.sliderWid+5)){\r\n this.highlight = \"right\";\r\n }\r\n else this.highlight = null;\r\n\r\n // if(this.dragging) this.update();\r\n if(this.dragging == \"top\"){\r\n this.top = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(yPos, this.sliderWid/2, this.bottom - this.sliderWid);\r\n }\r\n else if(this.dragging == \"bottom\"){\r\n this.bottom = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(yPos, this.top+this.sliderWid, this.imgCanvas.height-this.sliderWid/2);\r\n }\r\n else if(this.dragging == \"left\"){\r\n this.left = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(xPos, this.sliderWid/2, this.right-this.sliderWid);\r\n }\r\n else if(this.dragging == \"right\"){\r\n this.right = _utils_js__WEBPACK_IMPORTED_MODULE_0__.clamp(xPos, this.left+this.sliderWid, this.imgCanvas.width - this.sliderWid/2);\r\n }\r\n });\r\n\r\n document.addEventListener(\"mouseup\", (e) => {\r\n if(this.dragging){\r\n this.update();\r\n }\r\n this.dragging = null;\r\n });\r\n\r\n document.getElementById(\"pasterOverlay\").addEventListener(\"mousedown\", (e) => {\r\n this.closeModal();\r\n });\r\n \r\n document.getElementById(\"pasterModal\").addEventListener(\"mousedown\", (e) => {\r\n e.stopPropagation();\r\n });\r\n\r\n }\r\n\r\n update() {\r\n // for(let i = this.preview.spawnArea; i < this.preview.rows + this.preview.spawnArea; i++ ){\r\n // for(let j = 0; j < this.preview.cols; j++ ){\r\n // let newTileSize = [((this.right-this.left)/this.preview.cols)/2, ((this.bottom-this.top)/this.preview.rows)/2];\r\n\r\n // let x = this.left + ((this.right-this.left)/this.preview.cols)*j + newTileSize[0]/2;\r\n // let y = this.top + ((this.bottom-this.top)/this.preview.rows-this.preview.spawnArea)*i + newTileSize[1]/2;\r\n\r\n for(let i = 0; i < this.preview.rows; i++ ){\r\n for(let j = 0; j < this.preview.cols; j++ ){\r\n let newTileSize = [(this.right-this.left)/this.preview.cols, (this.bottom-this.top)/(this.preview.rows)];\r\n let tile = this.preview.board[i+this.preview.spawnArea][j];\r\n\r\n let x = this.left + newTileSize[0]*j + newTileSize[0]/2;\r\n let y = this.top + newTileSize[1]*i + newTileSize[1]/2;\r\n\r\n\r\n let pixColor = _utils_js__WEBPACK_IMPORTED_MODULE_0__.parseColor(this.getPixelColor(x,y));\r\n let colors = Object.entries(Piece.pieces).map((e) => e[1].color).concat([\"#555555\",\"#000000\"]);\r\n\r\n\r\n if(pixColor.r + pixColor.g + pixColor.b < 50 || pixColor.a == 0){\r\n tile.active = false;\r\n }\r\n else {\r\n tile.color = this.getPixelColor(x,y);\r\n tile.active = true;\r\n }\r\n\r\n\r\n let bestMatch = null;\r\n let bestDelta = null;\r\n for(const c of colors){\r\n let delta = _utils_js__WEBPACK_IMPORTED_MODULE_0__.deltaE(pixColor, _utils_js__WEBPACK_IMPORTED_MODULE_0__.parseColor(c));\r\n\r\n //cyan handicap needed for tetrio colors\r\n if(c == \"#0f9bd7\") delta -= 15;\r\n\r\n if(!bestMatch || bestDelta > delta){\r\n bestMatch = c;\r\n bestDelta = delta\r\n }\r\n }\r\n\r\n if(bestMatch == \"#000000\" || pixColor.a == 0){\r\n tile.active = false;\r\n }\r\n else {\r\n tile.color = bestMatch;\r\n tile.active = true;\r\n }\r\n }\r\n }\r\n }\r\n\r\n drawImage (){\r\n let ratio = Math.min(this.imgCanvas.width / this.img.width, this.imgCanvas.height / this.img.height);\r\n let newWidth = this.img.width * ratio;\r\n let newHeight = this.img.height * ratio;\r\n let x = (this.imgCanvas.width/2) - (newWidth/2);\r\n let y = (this.imgCanvas.height/2) - (newHeight/2);\r\n this.imgBounds = {p1:{x:x,y:y},p2:{x:x+newWidth,y:y+newHeight}};\r\n\r\n this.imgCtx.drawImage(this.img, this.imgBounds.p1.x, this.imgBounds.p1.y, \r\n this.imgBounds.p2.x-this.imgBounds.p1.x,\r\n this.imgBounds.p2.y-this.imgBounds.p1.y);\r\n }\r\n\r\n render(){\r\n if(this.active && this.img){\r\n this.preview.render();\r\n\r\n this.drawImage();\r\n\r\n // draw sliders\r\n this.ctrlsCtx.clearRect(0,0,this.imgCanvas.width,this.imgCanvas.height);\r\n this.ctrlsCtx.fillStyle = \"rgba(50,54,69,0.5)\"\r\n this.ctrlsCtx.fillRect(0,0,this.imgCanvas.width,this.top);\r\n this.ctrlsCtx.fillRect(0,this.bottom, this.imgCanvas.width, this.imgCanvas.height);\r\n this.ctrlsCtx.fillRect(0,this.top,this.left, this.bottom-this.top);\r\n this.ctrlsCtx.fillRect(this.right,this.top,this.imgCanvas.width, this.bottom-this.top);\r\n\r\n this.ctrlsCtx.strokeStyle = \"white\";\r\n this.ctrlsCtx.setLineDash([10,10]) \r\n this.ctrlsCtx.beginPath();\r\n this.ctrlsCtx.moveTo(0,this.top)\r\n this.ctrlsCtx.lineTo(this.imgCanvas.width, this.top);\r\n this.ctrlsCtx.moveTo(0,this.bottom)\r\n this.ctrlsCtx.lineTo(this.imgCanvas.width, this.bottom);\r\n this.ctrlsCtx.moveTo(this.left,0)\r\n this.ctrlsCtx.lineTo(this.left, this.imgCanvas.height);\r\n this.ctrlsCtx.moveTo(this.right,0)\r\n this.ctrlsCtx.lineTo(this.right, this.imgCanvas.height);\r\n this.ctrlsCtx.stroke();\r\n this.ctrlsCtx.setLineDash([]) \r\n\r\n this.ctrlsCtx.fillStyle = this.highlight == \"top\" ? this.sliderHighlightColor : this.sliderColor; \r\n this.ctrlsCtx.fillRect((this.left+((this.right-this.left)/2))-(this.sliderLen/2),this.top-(this.sliderWid/2),this.sliderLen,this.sliderWid);\r\n this.ctrlsCtx.fillStyle = this.highlight == \"bottom\" ? this.sliderHighlightColor : this.sliderColor; \r\n this.ctrlsCtx.fillRect((this.left+((this.right-this.left)/2))-(this.sliderLen/2),this.bottom-(this.sliderWid/2),this.sliderLen,this.sliderWid);\r\n this.ctrlsCtx.fillStyle = this.highlight == \"left\" ? this.sliderHighlightColor : this.sliderColor; \r\n this.ctrlsCtx.fillRect(this.left-(this.sliderWid/2),(this.top+((this.bottom-this.top)/2))-(this.sliderLen/2),this.sliderWid,this.sliderLen);\r\n this.ctrlsCtx.fillStyle = this.highlight == \"right\" ? this.sliderHighlightColor : this.sliderColor; \r\n this.ctrlsCtx.fillRect(this.right-(this.sliderWid/2),(this.top+((this.bottom-this.top)/2))-(this.sliderLen/2),this.sliderWid,this.sliderLen);\r\n\r\n // draw dots\r\n for(let i = 0; i < this.preview.rows; i++ ){\r\n for(let j = 0; j < this.preview.cols; j++ ){\r\n let newTileSize = [(this.right-this.left)/this.preview.cols, (this.bottom-this.top)/(this.preview.rows)];\r\n\r\n let x = this.left + newTileSize[0]*j + newTileSize[0]/2;\r\n let y = this.top + newTileSize[1]*i + newTileSize[1]/2;\r\n\r\n // this.ctrlsCtx.fillStyle = this.getPixelColor(x,y);\r\n this.ctrlsCtx.fillStyle = \"rgba(255,255,255,0.5)\";\r\n // this.ctrlsCtx.strokeStyle = \"black\";\r\n this.ctrlsCtx.beginPath();\r\n this.ctrlsCtx.arc(x, y, 1, 0, 2 * Math.PI);\r\n // this.ctrlsCtx.stroke();\r\n this.ctrlsCtx.fill();\r\n }\r\n }\r\n\r\n }\r\n }\r\n\r\n getPixelColor(x,y){\r\n if(!x || !y) return \"rgba(0,0,0,0)\";\r\n let pixelData = this.imgCtx.getImageData(x,y,1,1).data;\r\n let rgba = `rgba(${pixelData[0]}, ${pixelData[1]}, ${pixelData[2]}, ${pixelData[3] / 255})`;\r\n\r\n return rgba;\r\n }\r\n\r\n accept(){\r\n this.gameBoard.playfield.board = this.preview.copy().board;\r\n this.closeModal();\r\n this.active = false;\r\n }\r\n\r\n cancel(){\r\n this.closeModal();\r\n this.active = false;\r\n }\r\n\r\n openModal () {\r\n this.active = true;\r\n document.getElementById(\"pasterOverlay\").style.display = \"block\";\r\n }\r\n\r\n closeModal () {\r\n this.active = false;\r\n document.getElementById(\"pasterOverlay\").style.display = \"none\";\r\n }\r\n}\r\n\r\n// class Data {\r\n\r\n\r\n// constructor(gameBoard){\r\n// this.gameBoard = gameBoard\r\n// // this.includeQueue = true;\r\n\r\n// document.getElementById(\"import\")\r\n\r\n// // this.includeQueueCheckbox = document.getElementById(\"includeQueue\");\r\n// // this.includeQueueCheckbox.checked = this.includeQueue;\r\n// // this.includeQueueCheckbox.addEventListener(\"click\", (e) => {\r\n// // this.includeQueue = e.target.checked;\r\n// // })\r\n\r\n\r\n\r\n\r\n// }\r\n\r\n// document.getelementbyid(\"importbutton\").addeventlistener(\"click\", (e) => this.import());\r\n// document.getelementbyid(\"clearimport\").addeventlistener(\"click\", (e) => this.clearimport());\r\n// import(){\r\n// let datastring = document.getelementbyid(\"importtext\").value;\r\n// this.gameboard.loaddata(datastring);\r\n// let el = document.queryselector( ':focus' );\r\n// if( el ) el.blur();\r\n// }\r\n\r\n// clearImport(){\r\n// document.getElementById(\"importText\").value = \"\";\r\n// }\r\n\r\n\r\n// copyToClipboard(s){\r\n\r\n// }\r\n// }\r\n\r\nclass App {\r\n start = true;\r\n startTime;\r\n takingInput = true;\r\n\r\n curDir;\r\n dasFired = false;\r\n softDrop = false;\r\n dasCancel;\r\n arrCancel;\r\n softCancel;\r\n\r\n constructor() {\r\n\r\n this.input = new InputManager((key, state) => this.handleInputs(key, state));\r\n this.board = new GameBoard(20, 10, 30);\r\n this.settings = new Settings(this.input, () => this.pauseInput(), () => this.resumeInput(), this.board);\r\n this.paster = new Paster(this.board);\r\n // this.data = new Data(this.board);\r\n\r\n this.panelOpen = false;\r\n\r\n this.lastTick = performance.now();\r\n this.lastRender = this.lastTick;\r\n // this.tickLength = 50;\r\n this.tickLength = 17;\r\n\r\n // load board if parameter given\r\n let url = window.location.href;\r\n let params = new URLSearchParams(url.split('?')[1]);\r\n\r\n if(params.has('data')){\r\n this.board.loadData(params.get('data'));\r\n }\r\n\r\n // App setup\r\n \r\n // change Log\r\n document.getElementById(\"changeLogSvg\").addEventListener(\"click\", (e) => {\r\n document.getElementById(\"changeLogOverlay\").style.display = \"block\";\r\n });\r\n document.getElementById(\"changeLogCloseSvg\").addEventListener(\"click\", (e) => {\r\n document.getElementById(\"changeLogOverlay\").style.display = \"none\";\r\n });\r\n\r\n\r\n // side panel\r\n document.getElementById(\"panelTab\").addEventListener(\"click\", (e) => {\r\n let left = document.getElementById(\"panelIconLeft\")\r\n left.style.animationName = this.panelOpen ? \"collapseIcon\" : \"expandIcon\";\r\n left.style.width = this.panelOpen ? \"0%\" : \"55%\";\r\n\r\n let right = document.getElementById(\"panelIconRight\");\r\n right.style.animationName = this.panelOpen ? \"expandIcon\" : \"collapseIcon\";\r\n right.style.width = this.panelOpen ? \"55%\" : \"0%\";\r\n\r\n let tab = document.getElementById(\"panelTab\");\r\n tab.style.boxShadow = this.panelOpen ? \"0 0 10px black\" : \"0 0 30px black\";\r\n\r\n let sidePanel = document.getElementById(\"sidePanel\");\r\n sidePanel.style.animationName = this.panelOpen ? \"collapsePanel\" : \"expandPanel\";\r\n sidePanel.style.width = this.panelOpen ? \"0\" : \"30em\";\r\n\r\n this.panelOpen = !this.panelOpen\r\n });\r\n\r\n let sP = document.getElementById(\"sidePanelContent\");\r\n sP.addEventListener(\"focusin\", (e) => this.pauseInput());\r\n sP.addEventListener(\"focusout\", (e) => this.resumeInput());\r\n\r\n\r\n\r\n let tabs = document.getElementsByClassName(\"tab\");\r\n for( const t of tabs){\r\n t.addEventListener(\"mousedown\", (e) => {\r\n if(!e.currentTarget.classList.contains(\"activeTab\")){\r\n let allTabs = document.getElementsByClassName(\"tab\");\r\n\r\n // remove activeTab class\r\n for(const tab of allTabs){\r\n tab.classList.remove(\"activeTab\");\r\n let content = document.getElementById(tab.id.replace(\"Tab\",\"\"));\r\n content.style.display = \"none\";\r\n }\r\n\r\n e.currentTarget.classList.add(\"activeTab\");\r\n let content = document.getElementById(e.currentTarget.id.replace(\"Tab\",\"\"));\r\n content.style.display = \"flex\";\r\n\r\n }\r\n }) \r\n }\r\n\r\n\r\n\r\n }\r\n\r\n\r\n\r\n update() {\r\n // game logic\r\n if(this.start){\r\n if(!this.takingInput){\r\n this.board.isPaused = true;\r\n }\r\n else{\r\n this.board.isPaused = false;\r\n this.board.update();\r\n }\r\n // this.board.activePiece = this.board.queue.getCurrent();\r\n if(this.dasFired && this.settings.arr === 0){\r\n this.board.shiftInstant(this.curDir);\r\n }\r\n // if(this.paster.active){\r\n // this.paster.update();\r\n // }\r\n }\r\n }\r\n\r\n initiateDas(key,dir) {\r\n this.cancelDas();\r\n this.curDir = dir;\r\n\r\n this.board.shiftPiece(dir);\r\n this.dasCancel = setTimeout( () => {\r\n this.dasFired = true;\r\n if(this.input.keysDown[key]) {\r\n if(this.settings.arr === 0) this.board.shiftInstant(dir, this.settings.softDrop === 0 && this.softDrop);\r\n else{\r\n this.arrCancel = setInterval(() => {\r\n if(this.input.keysDown[key]) this.board.shiftPiece(dir) \r\n else this.cancelDas();\r\n }, this.settings.arr);\r\n }\r\n }\r\n else this.cancelDas();\r\n }, this.settings.das);\r\n }\r\n\r\n cancelDas() {\r\n if(this.dasCancel) clearTimeout(this.dasCancel);\r\n if(this.arrCancel) clearInterval(this.arrCancel);\r\n this.dasFired = false;\r\n this.dasCancel = null;\r\n this.arrCancel = null;\r\n this.curDir = null;\r\n }\r\n\r\n handleInputs(keyCode, state) {\r\n if(this.takingInput){\r\n if(keyCode === \"Escape\"){\r\n this.board.sketcher.unselectAll();\r\n this.paster.closeModal();\r\n }\r\n if(keyCode === \"Enter\"){\r\n if(this.paster.active){\r\n this.paster.accept();\r\n this.board.updateDataPanel();\r\n }\r\n }\r\n\r\n // game controls\r\n if(keyCode === this.settings.keybinds.leftKey){\r\n if(state) this.initiateDas(keyCode,\"left\");\r\n else if(this.curDir === \"left\") this.cancelDas();\r\n }\r\n else if(keyCode === this.settings.keybinds.rightKey){\r\n if(state) this.initiateDas(keyCode,\"right\");\r\n else if(this.curDir === \"right\") this.cancelDas();\r\n }\r\n else if(keyCode === this.settings.keybinds.softKey){\r\n if(state){\r\n this.softDrop = true;\r\n if(this.settings.softDrop === 0) this.board.shiftInstant(\"down\");\r\n this.softCancel = setInterval(() => {\r\n if(this.input.keysDown[keyCode]) this.board.shiftPiece(\"down\");\r\n else clearInterval(this.softCancel); \r\n }, this.settings.softDrop);\r\n }\r\n else if(this.softDrop) {\r\n this.softDrop = false;\r\n clearInterval(this.softCancel);\r\n }\r\n }\r\n else if(state && keyCode === this.settings.keybinds.hardKey){\r\n this.board.hardDrop();\r\n }\r\n else if(state && keyCode === this.settings.keybinds.holdKey){\r\n this.board.holdPiece();\r\n }\r\n else if(state && keyCode === this.settings.keybinds.crKey){\r\n this.board.rotateActive(1);\r\n }\r\n else if(state && keyCode === this.settings.keybinds.ccrKey){\r\n this.board.rotateActive(-1);\r\n }\r\n else if(state && keyCode === this.settings.keybinds.r180Key){\r\n this.board.rotateActive(2);\r\n }\r\n else if(state && keyCode === this.settings.keybinds.restartKey){\r\n this.startGame();\r\n }\r\n else if(state && keyCode === this.settings.keybinds.undoKey){\r\n this.board.setState(-1);\r\n }\r\n else if(state && keyCode === this.settings.keybinds.redoKey){\r\n this.board.setState(1);\r\n }\r\n\r\n }\r\n }\r\n\r\n render() {\r\n this.board.renderBoard();\r\n this.paster.render();\r\n }\r\n\r\n startGame() {\r\n this.startTime = performance.now();\r\n this.board.resetBoard();\r\n this.start = true;\r\n }\r\n\r\n pauseInput(){\r\n this.takingInput = false;\r\n }\r\n\r\n resumeInput(){\r\n this.takingInput = true;\r\n }\r\n\r\n}\r\n\r\n\r\nlet app = new App();\r\n(() => {\r\n function main(tFrame){\r\n app.stopId = window.requestAnimationFrame(main);\r\n\r\n const nextTick = app.lastTick + app.tickLength;\r\n let numTicks = 0;\r\n\r\n if(tFrame > nextTick){\r\n const timeSinceTick = tFrame - app.lastTick;\r\n numTicks = Math.floor(timeSinceTick / app.tickLength);\r\n\r\n }\r\n\r\n for (let i = 0; i < numTicks; i++) {\r\n app.lastTick += app.tickLength;\r\n app.update(app.lastTick);\r\n }\r\n\r\n app.render();\r\n }\r\n\r\n main(performance.now());\r\n})()\r\n\r\n\r\n\n\n//# sourceURL=webpack://sketris/./src/sketris.js?"); /***/ }),