From 1e044b7a8587f67cbe77b235ee6c538d1a05a753 Mon Sep 17 00:00:00 2001 From: Joseph Garcia Date: Sat, 27 Apr 2024 21:45:46 -0700 Subject: [PATCH] Hangman (#97) * link to local games * lol * wip * its late * cool * cool * cool * cool * more * almost * saving * wip * awesome * ayy lmao * nice * wip * ayo * delete * ah fuck * wip * almost * moe * fix --- package-lock.json | 21 + package.json | 3 + src/GameSession.js | 17 - src/common/squish-map.js | 5 +- src/dashboard/HomegamesDashboard.js | 117 ++-- src/games/grid-test/index.js | 55 ++ src/games/hangman/index.js | 1011 +++++++++++++++++++++++++++ src/games/hangman/questions.js | 3 + src/games/input-test/index.js | 8 +- src/homegames_root/HomegamesRoot.js | 28 +- src/homegames_root/settings.js | 38 +- 11 files changed, 1211 insertions(+), 95 deletions(-) create mode 100644 src/games/grid-test/index.js create mode 100644 src/games/hangman/index.js create mode 100644 src/games/hangman/questions.js diff --git a/package-lock.json b/package-lock.json index e7228abc..19729e3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,9 @@ "squish-1005": "npm:squishjs@1.0.5", "squish-1006": "npm:squishjs@1.0.6", "squish-1007": "npm:squishjs@1.0.7", + "squish-1008": "npm:squishjs@1.0.8", + "squish-1009": "npm:squishjs@1.0.9", + "squish-1010": "npm:squishjs@1.0.10", "ws": "7.4.6" } }, @@ -697,6 +700,24 @@ "resolved": "https://registry.npmjs.org/squishjs/-/squishjs-1.0.7.tgz", "integrity": "sha512-sdWhY9fVtDkuDJQxQkP3B/Nqs2VUkFELjNUv70svlDUKxn5CMp+MGmgR7GN5uZiKJThidHxamjua3eFCNBZEnA==" }, + "node_modules/squish-1008": { + "name": "squishjs", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/squishjs/-/squishjs-1.0.8.tgz", + "integrity": "sha512-guCs4JkadkBJ8dF87Od/6wj0VIN557u+jaaXRCY2V/Xr9YopPVK1+60HCOMH9TDsHD9w5Gx0NcEsJnoRV4FbUA==" + }, + "node_modules/squish-1009": { + "name": "squishjs", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/squishjs/-/squishjs-1.0.9.tgz", + "integrity": "sha512-qiij5A4I9JawdfET8Pg1HkBRONyRZ5w7ccXcba51QFsUelBrI4XQg11fgDLSn+QSybpCCCH+vtd7/j0/YoXwrw==" + }, + "node_modules/squish-1010": { + "name": "squishjs", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/squishjs/-/squishjs-1.0.10.tgz", + "integrity": "sha512-S5KZnV2KO8SBgLsiRWZYxYHPn3+4SxxdWXs/q3wouvyM/X8YU12b6P6TlR9HEsCbGP8gzKdWIo954P8igQdKhg==" + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", diff --git a/package.json b/package.json index 1c009449..1063d1c2 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,9 @@ "squish-1005": "npm:squishjs@1.0.5", "squish-1006": "npm:squishjs@1.0.6", "squish-1007": "npm:squishjs@1.0.7", + "squish-1008": "npm:squishjs@1.0.8", + "squish-1009": "npm:squishjs@1.0.9", + "squish-1010": "npm:squishjs@1.0.10", "ws": "7.4.6" } } diff --git a/src/GameSession.js b/src/GameSession.js index e2af4c70..06336626 100644 --- a/src/GameSession.js +++ b/src/GameSession.js @@ -89,23 +89,6 @@ class GameSession { const playerSettings = this.playerSettingsMap[playerId] || {}; let playerFrame = this.squisher.getPlayerFrame(playerId); - - if (playerSettings) { - if ((!playerSettings.SOUND || !playerSettings.SOUND.enabled) && playerFrame) { - playerFrame = playerFrame.filter(f => { - const unsquished = this.squisher.unsquish(f); - if (unsquished.node.asset) { - if (this.game.getAssets && this.game.getAssets() && this.game.getAssets()[Object.keys(unsquished.node.asset)[0]]) { - if (this.game.getAssets()[Object.keys(unsquished.node.asset)[0]].info.type === 'audio') { - return false; - } - } - } - - return true; - }); - } - } if (playerFrame) { this.players[playerId].receiveUpdate(playerFrame.flat()); diff --git a/src/common/squish-map.js b/src/common/squish-map.js index a8b2d41a..602bd088 100644 --- a/src/common/squish-map.js +++ b/src/common/squish-map.js @@ -22,5 +22,8 @@ module.exports = { '1004': require.resolve('squish-1004'), '1005': require.resolve('squish-1005'), '1006': require.resolve('squish-1006'), - '1007': require.resolve('squish-1007') + '1007': require.resolve('squish-1007'), + '1008': require.resolve('squish-1008'), + '1009': require.resolve('squish-1009'), + '1010': require.resolve('squish-1010') }; diff --git a/src/dashboard/HomegamesDashboard.js b/src/dashboard/HomegamesDashboard.js index 0a4bfc43..85f0283a 100644 --- a/src/dashboard/HomegamesDashboard.js +++ b/src/dashboard/HomegamesDashboard.js @@ -4,7 +4,7 @@ const https = require('https'); const path = require('path'); const fs = require('fs'); -const { Asset, Game, ViewableGame, GameNode, Colors, ShapeUtils, Shapes, squish, unsquish, ViewUtils } = require('squish-1006'); +const { Asset, Game, ViewableGame, GameNode, Colors, ShapeUtils, Shapes, squish, unsquish, ViewUtils } = require('squish-1009'); const squishMap = require('../common/squish-map'); @@ -227,25 +227,30 @@ const getGameMap = () => { if (isLocal) { const gameClass = require(gamePath); - const gameMetadata = gameClass.metadata ? gameClass.metadata() : {}; - games[gameClass.name] = { - metadata: { - name: gameMetadata.name || gameClass.name, - thumbnail: gameMetadata.thumbnail, - author: gameMetadata.createdBy || 'Unknown author', - isTest: gameMetadata.isTest || false - }, - versions: { - 'local-game-version': { - gameId: gameClass.name, - class: gameClass, - metadata: {...gameMetadata }, - gamePath, - versionId: 'local-game-version', - description: gameMetadata.description || 'No description available', - version: 0, - isReviewed: true + if (!gameClass.name || !gameClass.metadata) { + log.info('Unknown game at path ' + gamePath); + } else { + const gameMetadata = gameClass.metadata ? gameClass.metadata() : {}; + + games[gameClass.name] = { + metadata: { + name: gameMetadata.name || gameClass.name, + thumbnail: gameMetadata.thumbnail, + author: gameMetadata.createdBy || 'Unknown author', + isTest: gameMetadata.isTest || false + }, + versions: { + 'local-game-version': { + gameId: gameClass.name, + class: gameClass, + metadata: {...gameMetadata }, + gamePath, + versionId: 'local-game-version', + description: gameMetadata.description || 'No description available', + version: 0, + isReviewed: true + } } } } @@ -322,7 +327,7 @@ class HomegamesDashboard extends ViewableGame { return { aspectRatio: {x: 16, y: 9}, author: 'Joseph Garcia', - squishVersion: '1006' + squishVersion: '1009' }; } @@ -682,45 +687,53 @@ class HomegamesDashboard extends ViewableGame { if (requestedGame) { let { gameId, versionId } = requestedGame; - - networkHelper.getGameDetails(gameId).then(gameDetails => { - if (!versionId) { - if (gameDetails.versions.length > 0) { - versionId = gameDetails.versions[gameDetails.versions.length - 1].versionId; + + const lowerCaseToOriginalKey = {}; + Object.keys(this.localGames).forEach(k => { + lowerCaseToOriginalKey[k.toLowerCase()] = k; + }); + if (lowerCaseToOriginalKey[gameId.toLowerCase()] && this.localGames[lowerCaseToOriginalKey[gameId.toLowerCase()]].versions?.['local-game-version']) { + this.showGameModalNew(playerId, lowerCaseToOriginalKey[gameId.toLowerCase()], 'local-game-version'); + } else { + networkHelper.getGameDetails(gameId).then(gameDetails => { + if (!versionId) { + if (gameDetails.versions.length > 0) { + versionId = gameDetails.versions[gameDetails.versions.length - 1].versionId; + } } - } - networkHelper.getGameVersionDetails(gameId, versionId).then(version => { - const ting = { - [gameId]: { - metadata: { - game: gameDetails, - version - }, - versions: { - [versionId]: version + networkHelper.getGameVersionDetails(gameId, versionId).then(version => { + const ting = { + [gameId]: { + metadata: { + game: gameDetails, + version + }, + versions: { + [versionId]: version + } } - } - }; + }; - if (!this.assets[gameId]) { - const asset = new Asset({ - 'id': gameDetails.thumbnail, - 'type': 'image' - }); + if (!this.assets[gameId]) { + const asset = new Asset({ + 'id': gameDetails.thumbnail, + 'type': 'image' + }); - this.assets[gameId] = asset; + this.assets[gameId] = asset; - this.addAsset(gameId, asset).then(() => { - this.showGameModalNew(playerId, gameId, version.versionId); - }); - } else { - this.showGameModalNew(playerId, gameId, version.versionId); - } + this.addAsset(gameId, asset).then(() => { + this.showGameModalNew(playerId, gameId, version.versionId); + }); + } else { + this.showGameModalNew(playerId, gameId, version.versionId); + } + }); + }).catch(err => { + log.error(err); }); - }).catch(err => { - log.error(err); - }); + } } } diff --git a/src/games/grid-test/index.js b/src/games/grid-test/index.js new file mode 100644 index 00000000..461ea2b1 --- /dev/null +++ b/src/games/grid-test/index.js @@ -0,0 +1,55 @@ +const { Asset, Colors, Game, GameNode, Shapes, ShapeUtils } = require('squish-1009'); + +const COLORS = Colors.COLORS; + +class GridTest extends Game { + static metadata() { + return { + aspectRatio: {x: 16, y: 9}, + squishVersion: '1009', + author: 'Joseph Garcia', + thumbnail: '1e844026921f7662a62ce72da869da63' + }; + } + + constructor() { + super(); + + this.base = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: ShapeUtils.rectangle(0, 0, 100, 100) + }); + } + + handleNewPlayer({playerId}) { + const playerRoot = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: ShapeUtils.rectangle(0, 0, 0, 0) + }); + + for (let i = 0; i < 10; i++) { + for (let j = 0; j < 10; j++) { + const node = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: ShapeUtils.rectangle(i * 10, j * 10, 10, 10), + fill: [255, 255, 255, 255], + onHover: () => { + node.node.fill = [255, 0, 0, 255]; + node.node.onStateChange(); + }, + onClick: () => { + + } + }); + playerRoot.addChild(node); + } + } + this.base.addChild(playerRoot); + } + + getLayers() { + return [{root: this.base}]; + } +} + +module.exports = GridTest; diff --git a/src/games/hangman/index.js b/src/games/hangman/index.js new file mode 100644 index 00000000..3acbf2ee --- /dev/null +++ b/src/games/hangman/index.js @@ -0,0 +1,1011 @@ +const { Asset, Game, GameNode, Colors, Shapes, ShapeUtils } = require('squish-1010'); + +const { data: questionsBase64 } = require('./questions');// not sure if i want to do it like this + +const { questions } = JSON.parse(Buffer.from(questionsBase64, 'base64').toString('utf8')); + +const { WHITE, BLACK } = Colors.COLORS; + +class Hangman extends Game { + static metadata() { + return { + aspectRatio: {x: 2, y: 3}, + author: 'Joseph Garcia', + thumbnail: 'f103961541614b68c503a9ae2fd4cc47', + squishVersion: '1010', + tickRate: 60, + assets: { + 'amateur': new Asset({ + 'type': 'font', + 'id': '026a26ef0dd340681f62565eb5bf08fb' + }), + 'heavy-amateur': new Asset({ + 'type': 'font', + 'id': '9f11fac62df9c1559f6bd32de1382c20' + }), + 'hangman_0': new Asset({ + 'type': 'image', + 'id': 'f5c92180db47539d4a92ee947b137aae' + }), + 'hangman_1': new Asset({ + 'type': 'image', + 'id': '6e89789a60d9cc55c490eee96b0a8bfe' + }), + 'hangman_2': new Asset({ + 'type': 'image', + 'id': '413d6a8e94bdb81bac8cc02debbe0c11' + }), + 'hangman_3': new Asset({ + 'type': 'image', + 'id': 'a816e4b84ae74df580c7b93c15e2f2ae' + }), + 'hangman_4': new Asset({ + 'type': 'image', + 'id': 'af52c4ad108055ec0099d9246bc04516' + }), + 'hangman_5': new Asset({ + 'type': 'image', + 'id': '59d08314d9184f8e3f1b54bd65b230a9' + }), + 'strikethrough_0': new Asset({ + 'type': 'image', + 'id': 'bf83d4c187d7f997c5a93547150482b8' + }), + 'strikethrough_1': new Asset({ + 'type': 'image', + 'id': '433ebba7ceea900df41e825fe6445fe5' + }), + 'strikethrough_2': new Asset({ + 'type': 'image', + 'id': '25fa2a38b52580789082a224a66f3ad7' + }), + 'scribbles_1': new Asset({ + 'type': 'audio', + 'id': '28f764486fc2e5d6747d0d7a872cc760' + }), + 'scribbles_2': new Asset({ + 'type': 'audio', + 'id': 'ec68b3be88ba9d892ff29103385fd401' + }), + 'scribbles_3': new Asset({ + 'type': 'audio', + 'id': 'baf78f36dcd7a49390e77e88738e7203' + }) + } + }; + } + + constructor() { + super(); + this.players = {}; + this.overrideRoots = {}; + this.customHangmen = {}; + this.actions = []; + this.needsMouseUp = {}; + + this.base = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: [ + [0, 0], + [100, 0], + [100, 100], + [0, 100], + [0, 0] + ], + fill: WHITE + }); + + this.gameBase = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: ShapeUtils.rectangle(0, 0, 0, 0) + }); + + this.soundRoot = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: ShapeUtils.rectangle(0, 0, 0, 0) + }); + + this.playerOverrideRoot = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: ShapeUtils.rectangle(0, 0, 0, 0) + }); + + this.base.addChildren(this.gameBase, this.playerOverrideRoot, this.soundRoot); + + this.layers = [ + { + root: this.base + } + ]; + } + + showHangmanOptions(playerId, actionPayload) { + const fullScreenTakeOver = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: ShapeUtils.rectangle(0, 0, 100, 100), + fill: WHITE, + playerIds: [playerId] + }); + + const useDefaultHangman = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + fill: BLACK, + coordinates2d: ShapeUtils.rectangle(15, 20, 70, 20), + onClick: () => { + this.actions.push(actionPayload); + } + }); + + const useDefaultHangmanText = new GameNode.Text({ + textInfo: { + text: 'use default hangman', + font: 'heavy-amateur', + color: WHITE, + x: 50, + y: 29, + align: 'center', + size: 4 + } + }); + + useDefaultHangman.addChild(useDefaultHangmanText); + + const createMyOwnHangman = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + fill: BLACK, + coordinates2d: ShapeUtils.rectangle(15, 60, 70, 20), + onClick: () => { + const hangmanCreator = this.hangmanCreator(playerId, actionPayload); + fullScreenTakeOver.clearChildren(); + fullScreenTakeOver.addChild(hangmanCreator); + } + }); + + const createCustomHangmanText = new GameNode.Text({ + textInfo: { + text: 'create custom hangman', + font: 'heavy-amateur', + color: WHITE, + x: 50, + y: 69, + align: 'center', + size: 4 + } + }); + + const orText = new GameNode.Text({ + textInfo: { + text: 'or', + size: 8, + x: 50, + y: 47, + font: 'amateur', + align: 'center', + color: BLACK + } + }); + + createMyOwnHangman.addChild(createCustomHangmanText); + + fullScreenTakeOver.addChildren(useDefaultHangman, createMyOwnHangman, orText); + + this.overrideRoots[playerId] = fullScreenTakeOver; + + this.playerOverrideRoot.addChildren(fullScreenTakeOver); + } + + hangmanCreator(playerId, actionPayload) { + const container = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: ShapeUtils.rectangle(0, 0, 0, 0) + }); + + const canvasContainer = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: ShapeUtils.rectangle(4, 14, 92, 62), + fill: BLACK + }); + + let currentStep = 0; + + const frameHistories = {}; + + for (let i = 0; i < 45; i++) { + for (let j = 0; j < 30; j++) { + const curNode = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + fill: WHITE, + onClick: () => { + curNode.node.fill = BLACK; + curNode.node.onStateChange(); + if (!frameHistories[currentStep]) { + frameHistories[currentStep] = {}; + } + if (!frameHistories[currentStep][i]) { + frameHistories[currentStep][i] = {}; + } + frameHistories[currentStep][i][j] = true; + }, + coordinates2d: ShapeUtils.rectangle(5 + (i * 2), 15 + (j * 2), 2, 2) + }); + canvasContainer.addChild(curNode); + } + } + + const currentTextLabel = new GameNode.Text({ + textInfo: { + x: 50, + y: 5, + text: `Frame ${currentStep + 1} of 6`, + color: BLACK, + size: 5, + align: 'center', + font: 'heavy-amateur' + } + }); + + const doneText = new GameNode.Text({ + textInfo: { + x: 50, + y: 85, + font: 'heavy-amateur', + text: 'next', + color: WHITE, + size: 4, + align: 'center' + } + }); + + const doneButton = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + fill: BLACK, + coordinates2d: ShapeUtils.rectangle(40, 82, 20, 10), + onClick: () => { + if (!this.needsMouseUp[playerId]) { + this.needsMouseUp[playerId] = true; + if (currentStep === 5) { + this.customHangmen[playerId] = frameHistories; + this.actions.push(actionPayload); + } else { + currentStep += 1; + const newText = Object.assign({}, currentTextLabel.node.text); + newText.text = `Frame ${currentStep + 1} of 6`; + currentTextLabel.node.text = newText; + currentTextLabel.node.onStateChange(); + } + } + } + }); + + doneButton.addChild(doneText); + + container.addChildren(canvasContainer, currentTextLabel, doneButton); + + return container; + } + + handleNewPlayer({ playerId, info, settings }) { + this.showHangmanOptions(playerId, {'type': 'addPlayer', payload: { playerId, correctGuesses: 0, incorrectGuesses: 0, kills: 0, info} }); + } + + handlePlayerDisconnect(playerId) { + if (this.players[playerId]) { + delete this.players[playerId]; + } + + if (this.currentRound && playerId == this.currentRound.player) { + this.endRound(); + } + } + + handleMouseUp(playerId) { + this.needsMouseUp[playerId] = false; + } + + getLayers() { + return this.layers; + } + + newGame() { + this.base.clearChildren([this.gameBase.node.id, this.playerOverrideRoot.node.id, this.soundRoot.node.id]); + const playerOrder = Object.keys(this.players).sort((a, b) => Math.random() - Math.random()); + this.playerOrder = playerOrder; + this.nextRoundStartTime = Date.now(); + } + + renderHangmanSection(showMissingCharacters) { + const container = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: ShapeUtils.rectangle(5, 5, 25, 95) + }); + + let secretPhraseText = ""; + + const correctGuesses = new Set(this.currentRound.correctGuesses); + const incorrectGuesses = new Set(this.currentRound.incorrectGuesses); + + const secretPhraseWords = this.currentRound.secretPhrase.split(' '); + const secretPhrasePieces = secretPhraseWords.map(w => { + let secretPhraseText = ""; + for (let i = 0; i < w.length; i++) { + const currentChar = w.charAt(i).toLowerCase(); + if (currentChar <= 'z' && currentChar >= 'a') { + if (showMissingCharacters || correctGuesses.has(currentChar)) { + secretPhraseText += w.charAt(i); + } else { + secretPhraseText += " _"; + } + } else { + secretPhraseText += currentChar; + } + } + return secretPhraseText; + }); + + if (secretPhrasePieces.length < 3) { + const secretPhraseNode = new GameNode.Text({ + textInfo: { + x: 50, + y: 40, + align: 'center', + size: 4, + text: secretPhrasePieces.join(' '), + font: 'amateur', + color: BLACK + } + }); + + container.addChildren(secretPhraseNode); + } else if (secretPhrasePieces.length < 5) { + const secretPhraseNode1 = new GameNode.Text({ + textInfo: { + x: 50, + y: 36, + align: 'center', + size: 4, + text: secretPhrasePieces.slice(0, 2).join(' '), + font: 'amateur', + color: BLACK + } + }); + + const secretPhraseNode2 = new GameNode.Text({ + textInfo: { + x: 50, + y: 42, + align: 'center', + size: 4, + text: secretPhrasePieces.slice(2).join(' '), + font: 'amateur', + color: BLACK + } + }); + + container.addChildren(secretPhraseNode1, secretPhraseNode2); + + } else { + const secretPhraseNode1 = new GameNode.Text({ + textInfo: { + x: 50, + y: 30, + align: 'center', + size: 4, + text: secretPhrasePieces.slice(0, 2).join(' '), + font: 'amateur', + color: BLACK + } + }); + + const secretPhraseNode2 = new GameNode.Text({ + textInfo: { + x: 50, + y: 36, + align: 'center', + size: 4, + text: secretPhrasePieces.slice(2, 4).join(' '), + font: 'amateur', + color: BLACK + } + }); + + const secretPhraseNode3 = new GameNode.Text({ + textInfo: { + x: 50, + y: 42, + align: 'center', + size: 4, + text: secretPhrasePieces.slice(4).join(' '), + font: 'amateur', + color: BLACK + } + }); + + container.addChildren(secretPhraseNode1, secretPhraseNode2, secretPhraseNode3); + + + } + + const currentPlayerId = this.currentRound.player; + + if (currentPlayerId === 'cpu' || !this.customHangmen[currentPlayerId]) { + const key = `hangman_${this.currentRound.incorrectGuesses.length}`; + + const image = new GameNode.Asset({ + coordinates2d: ShapeUtils.rectangle(30, 4, 30, 25), + assetInfo: { + [key]: { + pos: {x: 30, y: 4}, + size: {x: 30, y: 25} + } + } + }); + + container.addChild(image); + } else { + for (let k = 0; k <= this.currentRound.incorrectGuesses.length; k++) { + const frameHistory = this.customHangmen[currentPlayerId][k]; + if (frameHistory) { + for (let i = 0; i < 45; i++) { + for (let j = 0; j < 30; j++) { + if (frameHistory[i] && frameHistory[i][j]) { + const curNode = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + fill: BLACK, + coordinates2d: ShapeUtils.rectangle(27.5 + (i), 5 + (j), 1, 1) + }); + container.addChild(curNode); + } + } + } + } + } + + } + + const currentPlayerName = currentPlayerId === 'cpu' ? 'CPU' : this.players[currentPlayerId].info.name; + + const currentPlayerTextNode = new GameNode.Text({ + textInfo: { + text: `${currentPlayerName}`, + x: 50, + y: 1, + align: 'center', + font: 'amateur', + color: BLACK, + size: 3 + } + }); + + container.addChild(currentPlayerTextNode); + + return container; + } + + renderLettersSection() { + const container = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: ShapeUtils.rectangle(5, 50, 95, 50), + }); + + // A is 65 + // Z is 90 + const alphabet = []; + for (let i = 65; i <= 90; i++) { + alphabet.push(String.fromCharCode(i).toLowerCase()); + } + + let n = 0; + const textNodes = alphabet.map(a => { + const xPos = 6.5 + (14.5 * (n % 7)); + const yPos = 50 + (12 * Math.floor(n / 7)); + + const node = new GameNode.Text({ + textInfo: { + x: xPos, + y: yPos, + size: 10, + text: a.toUpperCase(), + color: BLACK, + align: 'center', + font: 'amateur' + } + }); + + if (this.currentRound.incorrectGuesses.indexOf(a) >= 0 || this.currentRound.correctGuesses.indexOf(a) >= 0) { + let key; + if (this.currentRound.strikethroughs[a]) { + key = this.currentRound.strikethroughs[a]; + } else { + const rand = Math.floor(Math.random() * 3); + key = `strikethrough_${rand}`; + this.currentRound.strikethroughs[a] = key; + } + + const strikethrough = new GameNode.Asset({ + coordinates2d: ShapeUtils.rectangle(xPos - 6, yPos - 2, 13, 13), + assetInfo: { + [key]: { + pos: { x: xPos - 6, y: yPos - 2 }, + size: { x: 13, y: 13} + } + } + }); + node.addChild(strikethrough); + } + + const clickWrapper = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: ShapeUtils.rectangle(xPos - 5, yPos, 10, 10), + onClick: (playerId) => { + if (this.currentRound?.player == playerId) { + return; + } + + this.guess(playerId, a); + }, + onHover: (playerId) => { + if (this.currentRound?.player == playerId) { + return; + } + if (!this.currentRound.strikethroughs[a] && this.currentRound.incorrectGuesses.length < 5) { + const textInfo = Object.assign({}, node.node.text); + textInfo.font = 'heavy-amateur'; + node.node.text = textInfo; + this.base.node.onStateChange(); + } + }, + offHover: (playerId) => { + if (this.currentRound?.player == playerId) { + return; + } + + const textInfo = Object.assign({}, node.node.text); + textInfo.font = 'amateur'; + node.node.text = textInfo; + this.base.node.onStateChange(); + } + + }); + + node.addChild(clickWrapper); + + n++; + + return node; + }); + + container.addChildren(...textNodes); + + return container; + } + + guess(playerId, guessChar) { + if (!this.currentRound) { + return; + } + + const { correctGuesses, incorrectGuesses, secretPhrase } = this.currentRound; + + if (correctGuesses.indexOf(guessChar) >= 0 || incorrectGuesses.indexOf(guessChar) >= 0) { + return; + } + + const scribbleSound = () => { + const randIndex = Math.floor(Math.random() * 3) + 1; + if (this.actions?.[0]?.type === 'clearSound') { + this.actions.shift(); + } + const songNode = new GameNode.Asset({ + coordinates2d: ShapeUtils.rectangle(0, 0, 0, 0), + assetInfo: { + [`scribbles_${randIndex}`]: { + 'pos': Object.assign({}, { x: 0, y: 0 }), + 'size': Object.assign({}, { x: 0, y: 0 }), + 'startTime': 0, + 'loop': false + } + } + }); + + this.soundRoot.clearChildren(); + this.soundRoot.addChild(songNode); + + this.actions.push({'type': 'clearSound', 'timestamp': Date.now() + 1000}); + }; + + if (secretPhrase.toLowerCase().indexOf(guessChar) > -1) { + scribbleSound(); + correctGuesses.push(guessChar); + this.players[playerId].correctGuesses++; + this.nextTurn(); + } else { + scribbleSound(); + incorrectGuesses.push(guessChar); + this.players[playerId].incorrectGuesses++; + if (incorrectGuesses.length == 5) { + this.currentRound.killer = playerId; + this.players[playerId].kills++; + this.nextTurn(); + } else if (incorrectGuesses.length > 5) { + console.log('this shouldnt happen'); + } else { + this.nextTurn(); + } + } + + } + + endRound() { + this.gameBase.clearChildren(); + let count = 0; + + const playerHeader = new GameNode.Text({ + textInfo: { + x: 18, + y: 4, + text: 'Player', + font: 'heavy-amateur', + align: 'center', + color: BLACK, + size: 5 + } + }); + + const scoreHeader = new GameNode.Text({ + textInfo: { + x: 57.5, + y: 4, + text: 'Score', + font: 'heavy-amateur', + align: 'center', + color: BLACK, + size: 5 + } + }); + + const killsHeader = new GameNode.Text({ + textInfo: { + x: 85, + y: 4, + text: 'Kills', + font: 'heavy-amateur', + align: 'center', + color: BLACK, + size: 5 + } + }); + + this.gameBase.addChildren(playerHeader, scoreHeader, killsHeader); + const playerScoreList = Object.keys(this.players).filter(k => k !== 'cpu').map(k => { + const node = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + coordinates2d: ShapeUtils.rectangle(5, 10 + (count * 15), 90, 10) + }); + + const { incorrectGuesses, correctGuesses, info, kills } = this.players[k]; + + const playerName = info.name; + + const playerNameText = new GameNode.Text({ + textInfo: { + x: 6, + y: 14 + (count * 15), + size: 3, + align: 'left', + font: 'amateur', + color: BLACK, + text: `${playerName}` + } + }); + + const incorrectGuessesText = new GameNode.Text({ + textInfo: { + x: 50, + y: 14 + (count * 15), + size: 4, + align: 'center', + font: 'amateur', + color: BLACK, + text: `-${incorrectGuesses}` + } + }); + + const correctGuessesText = new GameNode.Text({ + textInfo: { + x: 60, + y: 14 + (count * 15), + size: 4, + align: 'center', + font: 'amateur', + color: BLACK, + text: `+${correctGuesses}` + } + }); + + const killsText = new GameNode.Text({ + textInfo: { + x: 85, + y: 14 + (count * 15), + size: 4, + align: 'center', + font: 'amateur', + color: BLACK, + text: `${kills}` + } + }); + + node.addChildren(playerNameText, incorrectGuessesText, correctGuessesText, killsText); + + count++; + return node; + }); + + this.gameBase.addChildren(...playerScoreList); + this.nextRoundStartTime = Date.now() + 5000; + } + + nextTurn() { + let charsGuessed = {}; + + for (let i = 0; i < this.currentRound.secretPhrase.length; i++) { + const currentChar = this.currentRound.secretPhrase.charAt(i).toLowerCase(); + if (currentChar !== ' ' && currentChar <= 'z' && currentChar >= 'a') { + charsGuessed[currentChar.toLowerCase()] = false; + } + } + + for (let i = 0; i < this.currentRound.correctGuesses.length; i++) { + charsGuessed[this.currentRound.correctGuesses[i].toLowerCase()] = true; + } + + const allLettersGuessed = Object.keys(charsGuessed).filter(k => !charsGuessed[k]).length === 0; + + if (this.currentRound.incorrectGuesses.length >= 5) { + if (this.lettersSection) { + this.gameBase.removeChild(this.lettersSection.node.id); + } + + const ripText = new GameNode.Text({ + textInfo: { + font: 'heavy-amateur', + text: 'R.I.P', + size: 6, + color: BLACK, + x: 50, + y: 60, + align: 'center' + } + }); + + if (this.currentRound.killer) { + const killedByName = this.players[this.currentRound.killer].info.name; + const killedByText = new GameNode.Text({ + textInfo: { + font: 'heavy-amateur', + text: `killed by ${killedByName}`, + size: 4, + color: BLACK, + x: 50, + y: 75, + align: 'center' + } + }); + + this.gameBase.addChild(killedByText); + } + + this.gameBase.removeChild(this.hangmanSection.node.id); + this.hangmanSection = this.renderHangmanSection(true); + this.gameBase.addChildren(ripText, this.hangmanSection); + this.actions.push({'type': 'endRound', 'timestamp': Date.now() + 3000}); + } else { + this.gameBase.clearChildren(); + if (!this.currentRound.round) { + this.currentRound.round = 1; + } else { + this.currentRound.round++; + } + + this.hangmanSection = this.renderHangmanSection(); + this.gameBase.addChildren(this.hangmanSection); + + if (allLettersGuessed) { + this.actions.push({'type': 'endRound', 'timestamp': Date.now() + 3000}); + } else { + this.lettersSection = this.renderLettersSection(); + this.gameBase.addChildren(this.lettersSection); + } + } + + } + + startRound(playerKey) { + this.base.clearChildren([this.gameBase.node.id, this.playerOverrideRoot.node.id, this.soundRoot.node.id]); + this.nextRoundStartTime = null; + + // if cpu is the only possible guesser, force the only player to be the guesser + let nonCpuPlayers = Object.keys(this.players).filter(k => k !== 'cpu').sort((a, b) => Math.random() - Math.random()); + let guessers = nonCpuPlayers.filter(k => k !== playerKey).sort((a, b) => Math.random() - Math.random()); + let player = playerKey; + + if (nonCpuPlayers.length == 1) { + player = 'cpu'; + guessers = nonCpuPlayers; + } + + this.currentRound = { + player, + guessers, + correctGuesses: [], + incorrectGuesses: [], + strikethroughs: {}, + guesserIndex: 0 + }; + + if (player === 'cpu') { + const randomIndex = Math.floor(Math.random() * questions.length); + const randomPhrase = questions[randomIndex]; + this.currentRound.secretPhrase = randomPhrase; + this.nextTurn(); + } else { + const waitingForPlayerMessage = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + fill: WHITE, + coordinates2d: ShapeUtils.rectangle(0, 0, 100, 100) + }); + + const timerNode = new GameNode.Text({ + textInfo: { + x: 50, + y: 80, + text: '30', + align: 'center', + size: 10, + font: 'heavy-amateur', + color: BLACK + } + }); + + let c = 0; + + let userPromptRoot; + + let ting = () => { + this.timerNodeInfo = { + node: timerNode, + expireAt: Date.now() + (1000), + onExpire: () => { + if (c == 30) { + this.timerNodeInfo = null; + const randomIndex = Math.floor(Math.random() * questions.length); + const randomPhrase = questions[randomIndex]; + this.currentRound.secretPhrase = randomPhrase.trim(); + this.actions.push({type: 'nextTurn'}); + if (userPromptRoot) { + this.playerOverrideRoot.removeChild(userPromptRoot.node.id); + } + } else { + const curText = Object.assign({}, timerNode.node.text); + curText.text = `${30 - c}`; + timerNode.node.text = curText; + timerNode.node.onStateChange(); + ting(); + } + } + } + + c++ + } + + ting(); + + const waitingText1 = new GameNode.Text({ + textInfo: { + x: 50, + y: 45, + align: 'center', + text: `Waiting for`, + color: BLACK, + font: 'amateur', + size: 5 + } + }); + + const waitingText2 = new GameNode.Text({ + textInfo: { + x: 50, + y: 55, + align: 'center', + text: `${this.players[player]?.info?.name || 'Unknown player'}...`, + color: BLACK, + font: 'amateur', + size: 5 + } + }); + + userPromptRoot = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + fill: WHITE, + coordinates2d: ShapeUtils.rectangle(0, 0, 100, 80), + playerIds: [player] + }); + + const userPromptMessage = new GameNode.Shape({ + shapeType: Shapes.POLYGON, + fill: BLACK, + coordinates2d: ShapeUtils.rectangle(15, 40, 70, 20), + input: { + type: 'text', + oninput: (_, text) => { + if (text?.trim().length) { + this.currentRound.secretPhrase = text.trim(); + this.actions.push({type: 'nextTurn'}); + this.playerOverrideRoot.removeChild(userPromptRoot.node.id); + } + } + } + }); + + const promptInputText = new GameNode.Text({ + textInfo: { + x: 50, + y: 50, + text: 'Enter your secret phrase', + color: WHITE, + align: 'center', + font: 'heavy-amateur', + size: 3 + } + }); + + userPromptMessage.addChild(promptInputText); + userPromptRoot.addChild(userPromptMessage); + + waitingForPlayerMessage.addChildren(waitingText1, waitingText2); + + this.gameBase.addChildren(waitingForPlayerMessage, timerNode); + this.playerOverrideRoot.addChild(userPromptRoot); + } + } + + tick() { + if (this.actions.length && (!this.actions[0].timestamp || this.actions[0].timestamp < Date.now())) { + const action = this.actions.shift(); + if (action.type == 'endRound') { + this.endRound(); + } else if (action.type === 'nextTurn') { + this.nextTurn(); + } else if (action.type == 'addPlayer') { + this.players[action.payload.playerId] = action.payload; + if (this.overrideRoots[action.payload.playerId]) { + this.playerOverrideRoot.removeChild(this.overrideRoots[action.payload.playerId].node.id); + } + const playerOrder = Object.keys(this.players).sort((a, b) => Math.random() - Math.random()); + this.playerOrder = playerOrder; + if (!this.players['cpu']) { + this.players['cpu'] = {}; + this.newGame(); + } + } else if (action.type == 'clearSound') { + this.soundRoot.clearChildren(); + } + } + + if (this.nextRoundStartTime && this.nextRoundStartTime < Date.now()) { + if (!this.playerIndex) { + this.playerIndex = 0; + } + const nextPlayer = this.playerOrder[this.playerIndex % this.playerOrder.length]; + this.playerIndex += 1; + this.startRound(nextPlayer); + } + + if (this.timerNodeInfo?.expireAt <= Date.now()) { + if (this.timerNodeInfo.onExpire) { + this.timerNodeInfo.onExpire(); + } + } + } +} + +module.exports = Hangman; diff --git a/src/games/hangman/questions.js b/src/games/hangman/questions.js new file mode 100644 index 00000000..1d87c80c --- /dev/null +++ b/src/games/hangman/questions.js @@ -0,0 +1,3 @@ +module.exports = { + data:"eyJxdWVzdGlvbnMiOlsiQSBQaWVjZSBvZiBDYWtlIiwiQWN0aW9ucyBTcGVhayBMb3VkZXIgVGhhbiBXb3JkcyIsIkFuIEFybSBhbmQgYSBMZWciLCJCYWNrIHRvIFNxdWFyZSBPbmUiLCJCZWF0aW5nIEFyb3VuZCB0aGUgQnVzaCIsIkJldHRlciBMYXRlIFRoYW4gTmV2ZXIiLCJCaXRlIHRoZSBCdWxsZXQiLCJCcmVhayBhIExlZyIsIkNhbGwgaXQgYSBEYXkiLCJDdXR0aW5nIENvcm5lcnMiLCJFYXN5IERvZXMgSXQiLCJFdmVyeSBDbG91ZCBIYXMgYSBTaWx2ZXIgTGluaW5nIiwiR2V0IE91dCBvZiBIYW5kIiwiR2l2ZSB0aGUgQmVuZWZpdCBvZiB0aGUgRG91YnQiLCJHbyBCYWNrIHRvIHRoZSBEcmF3aW5nIEJvYXJkIiwiSGFuZyBpbiBUaGVyZSIsIkhpdCB0aGUgTmFpbCBvbiB0aGUgSGVhZCIsIklnbm9yYW5jZSBpcyBCbGlzcyIsIkl0IFRha2VzIFR3byB0byBUYW5nbyIsIkp1bXAgb24gdGhlIEJhbmR3YWdvbiIsIktlZXAgWW91ciBFeWVzIFBlZWxlZCIsIkxhc3QgU3RyYXciLCJMZXQgU2xlZXBpbmcgRG9ncyBMaWUiLCJNYWtlIGEgTG9uZyBTdG9yeSBTaG9ydCIsIk5vIFBhaW4gTm8gR2FpbiIsIk9uIHRoZSBCYWxsIiwiT25jZSBpbiBhIEJsdWUgTW9vbiIsIlBpZWNlIG9mIENha2UiLCJQdWxsIFlvdXJzZWxmIFRvZ2V0aGVyIiwiUmFpbiBvbiBTb21lb25lJ3MgUGFyYWRlIiwiU2F2aW5nIGZvciBhIFJhaW55IERheSIsIlNwZWFrIG9mIHRoZSBEZXZpbCIsIlRha2UgV2l0aCBhIEdyYWluIG9mIFNhbHQiLCJUaGUgQmVzdCBvZiBCb3RoIFdvcmxkcyIsIlRpbWUgRmxpZXMgV2hlbiBZb3UncmUgSGF2aW5nIEZ1biIsIlVuZGVyIHRoZSBXZWF0aGVyIiwiV2UnbGwgQ3Jvc3MgVGhhdCBCcmlkZ2UgV2hlbiBXZSBDb21lIHRvIEl0IiwiWW91IENhbid0IEp1ZGdlIGEgQm9vayBieSBJdHMgQ292ZXIiLCJCZW5kIE92ZXIgQmFja3dhcmRzIiwiQnVybiB0aGUgTWlkbmlnaHQgT2lsIiwiQ2F1Z2h0IEJldHdlZW4gVHdvIFN0b29scyIsIkRvbid0IENvdW50IFlvdXIgQ2hpY2tlbnMgQmVmb3JlIFRoZXkgSGF0Y2giLCJFbHZpcyBIYXMgTGVmdCB0aGUgQnVpbGRpbmciLCJFdmVyeSBQZW5ueSBDb3VudHMiLCJGZWVsIFVuZGVyIHRoZSBXZWF0aGVyIiwiR2l2ZSB0aGUgQ29sZCBTaG91bGRlciIsIkdvIG9uIGEgV2lsZCBHb29zZSBDaGFzZSIsIkhpdCB0aGUgU2FjayIsIkluIHRoZSBIZWF0IG9mIHRoZSBNb21lbnQiLCJKdW1wIHRvIENvbmNsdXNpb25zIiwiS2VlcCBTb21ldGhpbmcgYXQgQmF5IiwiTGV0IHRoZSBDYXQgT3V0IG9mIHRoZSBCYWciLCJNaXNzIHRoZSBCb2F0IiwiTm90IFBsYXlpbmcgV2l0aCBhIEZ1bGwgRGVjayIsIk9mZiB0aGUgSG9vayIsIlB1dCBTb21ldGhpbmcgb24gSWNlIiwiUmVhZCBCZXR3ZWVuIHRoZSBMaW5lcyIsIlN0ZWFsIFNvbWVvbmUncyBUaHVuZGVyIiwiVGFrZSBJdCBFYXN5IiwiVXAgaW4gdGhlIEFpciIsIldpbGQgR29vc2UgQ2hhc2UiLCJBIEJsZXNzaW5nIGluIERpc2d1aXNlIiwiQSBEaW1lIGEgRG96ZW4iLCJCZWF0IEFyb3VuZCB0aGUgQnVzaCIsIkJldHRlciBTYWZlIFRoYW4gU29ycnkiLCJCaXRlIE9mZiBNb3JlIFRoYW4gWW91IENhbiBDaGV3IiwiQnJlYWsgdGhlIEljZSIsIkJ5IHRoZSBTa2luIG9mIFlvdXIgVGVldGgiLCJDb21wYXJpbmcgQXBwbGVzIHRvIE9yYW5nZXMiLCJDb3N0IGFuIEFybSBhbmQgYSBMZWciLCJEbyBTb21ldGhpbmcgYXQgdGhlIERyb3Agb2YgYSBIYXQiLCJGZWVsIGEgQml0IFVuZGVyIHRoZSBXZWF0aGVyIiwiR2l2ZSB0aGUgR3JlZW4gTGlnaHQiLCJHbyBmb3IgQnJva2UiLCJIaWdoIGFuZCBEcnkiLCJJdCdzIE5vdCBSb2NrZXQgU2NpZW5jZSIsIktlZXAgWW91ciBDaGluIFVwIiwiTGV0IEJ5Z29uZXMgQmUgQnlnb25lcyIsIk1ha2UgYSBNb3VudGFpbiBPdXQgb2YgYSBNb2xlaGlsbCIsIk5vdCBhIFNwYXJrIG9mIERlY2VuY3kiLCJPbiBUaGluIEljZSIsIlBsYXkgRGV2aWwncyBBZHZvY2F0ZSIsIlJ1biBMaWtlIHRoZSBXaW5kIiwiU2VsbCBMaWtlIEhvdGNha2VzIiwiVGFrZSB0aGUgQnVsbCBieSB0aGUgSG9ybnMiLCJUaGUgV2hvbGUgTmluZSBZYXJkcyIsIlZhcmlldHkgaXMgdGhlIFNwaWNlIG9mIExpZmUiLCJXaGVuIFBpZ3MgRmx5IiwiWW91ciBHdWVzcyBpcyBhcyBHb29kIGFzIE1pbmUiLCJDcm9zcyBUaGF0IEJyaWRnZSBXaGVuIFlvdSBDb21lIHRvIEl0IiwiQ3J5IE92ZXIgU3BpbHQgTWlsayIsIkN1cmlvc2l0eSBLaWxsZWQgdGhlIENhdCIsIkN1dCB0byB0aGUgQ2hhc2UiLCJGaWdodGluZyBUb290aCBhbmQgTmFpbCIsIkZpdCBhcyBhIEZpZGRsZSIsIkZ1bGwgb2YgSG90IEFpciIsIkdvIERvd24gaW4gRmxhbWVzIiwiSG9sZCBZb3VyIEhvcnNlcyIsIkluIGEgUGlja2xlIiwiS2ljayB0aGUgQnVja2V0IiwiTGl2ZSBhbmQgTGV0IExpdmUiLCJMb29rIEJlZm9yZSBZb3UgTGVhcCIsIk9uIENsb3VkIE5pbmUiLCJQbGF5IEl0IGJ5IEVhciIsIlNlZSBFeWUgdG8gRXllIiwiVGFrZSBhIFJhaW4gQ2hlY2siLCJUaGUgQmFsbCBpcyBpbiBZb3VyIENvdXJ0IiwiVGhyb3cgaW4gdGhlIFRvd2VsIiwiVW5kZXIgdGhlIEd1biIsIldhbGsgdGhlIFRhbGsiLCJBZGQgRnVlbCB0byB0aGUgRmlyZSIsIkFsbCBCYXJrIGFuZCBObyBCaXRlIiwiQXQgdGhlIERyb3Agb2YgYSBIYXQiLCJCYWNrIHRvIHRoZSBEcmF3aW5nIEJvYXJkIiwiQmVhdCB0aGUgQnVzaCIsIkJpdGUgdGhlIER1c3QiLCJCbGVzc2luZyBpbiBEaXNndWlzZSIsIkJyZWFrIE5ldyBHcm91bmQiLCJCcmluZyBIb21lIHRoZSBCYWNvbiIsIkJ1cnkgdGhlIEhhdGNoZXQiLCJCdXR0ZXJmbGllcyBpbiBZb3VyIFN0b21hY2giLCJDYXRjaCBTb21lIFoncyIsIkNoYXNlIFJhaW5ib3dzIiwiQ2xpbWIgdGhlIENvcnBvcmF0ZSBMYWRkZXIiLCJDbG9zZSBidXQgTm8gQ2lnYXIiLCJDcnkgV29sZiIsIkN1cCBvZiBKb2UiLCJDdXQgdGhlIE11c3RhcmQiLCJEZXZpbCdzIEFkdm9jYXRlIiwiRG9uJ3QgUHV0IEFsbCBZb3VyIEVnZ3MgaW4gT25lIEJhc2tldCIsIkRvd24gdG8gRWFydGgiLCJEcmF3IHRoZSBMaW5lIiwiRHJvcCBpbiB0aGUgQnVja2V0IiwiRWFnZXIgQmVhdmVyIiwiRWFybHkgQmlyZCBHZXRzIHRoZSBXb3JtIiwiRmFpciBXZWF0aGVyIEZyaWVuZCIsIkZhbGwgb24gRGVhZiBFYXJzIiwiRmVuZGVyIEJlbmRlciIsIkZpZnRoIFdoZWVsIiwiRmxhc2ggaW4gdGhlIFBhbiIsIkZseSBieSB0aGUgU2VhdCBvZiBZb3VyIFBhbnRzIiwiRm9hbSBhdCB0aGUgTW91dGgiLCJGb29scycgR29sZCIsIkZvciBQZXRlJ3MgU2FrZSIsIkdldCBhIEtpY2sgT3V0IG9mIiwiR2V0IHRoZSBCYWxsIFJvbGxpbmciLCJHZXQgWW91ciBBY3QgVG9nZXRoZXIiLCJHaXZlIFNvbWVvbmUgdGhlIFNsaXAiLCJHbyB0aGUgRXh0cmEgTWlsZSIsIkdvIFRocm91Z2ggdGhlIE1vdGlvbnMiLCJHb2luZyBEdXRjaCIsIkdvb2QgVGhpbmdzIENvbWUgdG8gVGhvc2UgV2hvIFdhaXQiLCJHcmFzcCB0aGUgTmV0dGxlIiwiR3JlYXQgTWluZHMgVGhpbmsgQWxpa2UiLCJHcmVlbiBUaHVtYiIsIkhhcmQgUGlsbCB0byBTd2FsbG93IiwiSGF2ZSBhIEJsYXN0IiwiSGF2ZSBhbiBBeGUgdG8gR3JpbmQiLCJIZWFkIE92ZXIgSGVlbHMiLCJIaXQgdGhlIEJvb2tzIiwiSGl0dGluZyB0aGUgSGF5IiwiSG9sZCB0aGUgRm9ydCIsIkluIGEgTnV0c2hlbGwiLCJJbiB0aGUgTGltZWxpZ2h0IiwiSXQncyBhIFNtYWxsIFdvcmxkIiwiSXQncyBBbnlvbmUncyBDYWxsIiwiSnVtcCB0aGUgR3VuIiwiS2VlcCB0aGUgQmFsbCBSb2xsaW5nIiwiS2ljayB0aGUgQ2FuIERvd24gdGhlIFJvYWQiLCJLbm9jayBZb3VyIFNvY2tzIE9mZiIsIktub3cgdGhlIFJvcGVzIiwiTGFyZ2VyIFRoYW4gTGlmZSIsIkxhc3QgYnV0IE5vdCBMZWFzdCIsIkxlYXZlIE5vIFN0b25lIFVudHVybmVkIiwiTGV0IE9mZiBTdGVhbSIsIkxpa2UgYSBDaGlja2VuIHdpdGggSXRzIEhlYWQgQ3V0IE9mZiIsIk1ha2UgSGF5IFdoaWxlIHRoZSBTdW4gU2hpbmVzIiwiTWV0aG9kIHRvIEhpcyBNYWRuZXNzIiwiTXVtJ3MgdGhlIFdvcmQiLCJOZWNrIGFuZCBOZWNrIiwiTmVlZGxlIGluIGEgSGF5c3RhY2siLCJPbGQgV2l2ZXMnIFRhbGUiLCJPbiB0aGUgU2FtZSBQYWdlIiwiT25jZSBCaXR0ZW4sIFR3aWNlIFNoeSIsIk91dCBvZiB0aGUgQmx1ZSIsIk92ZXIgdGhlIE1vb24iLCJQYXNzIHRoZSBCdWNrIiwiUGVubnkgZm9yIFlvdXIgVGhvdWdodHMiLCJQdWxsIHRoZSBQbHVnIiwiUHV0IHRoZSBDYXJ0IEJlZm9yZSB0aGUgSG9yc2UiLCJSYWluIENoZWNrIiwiUmFpbmluZyBDYXRzIGFuZCBEb2dzIiwiUmlzZSBhbmQgU2hpbmUiLCJSdWxlIG9mIFRodW1iIiwiUnVuIHRoZSBHYW11dCIsIlNhdmUgRmFjZSIsIlNob290IHRoZSBCcmVlemUiLCJTaXR0aW5nIER1Y2tzIiwiU3BpbGwgdGhlIEJlYW5zIiwiU3BpbiBZb3VyIFdoZWVscyIsIlNwaXR0aW5nIEltYWdlIiwiU3BsaXQgSGFpcnMiLCJTdGVhbCB0aGUgU2hvdyIsIlN0cmFpZ2h0IGZyb20gdGhlIEhvcnNlJ3MgTW91dGgiLCJTdHJpa2UgV2hpbGUgdGhlIElyb24gaXMgSG90IiwiVGFrZSB0aGUgQ2FrZSIsIlRoZSBMYXN0IExhdWdoIiwiVGhyb3VnaCBUaGljayBhbmQgVGhpbiIsIlRocm93IENhdXRpb24gdG8gdGhlIFdpbmQiLCJUaWUgdGhlIEtub3QiLCJUaXAgb2YgdGhlIEljZWJlcmciLCJUdXJuIGEgQmxpbmQgRXllIiwiVHdpc3QgU29tZW9uZSdzIEFybSIsIlR3byBQZWFzIGluIGEgUG9kIiwiVW5kZXIgdGhlIFNhbWUgUm9vZiIsIlVwIHRoZSBBbnRlIiwiVXNlIFlvdXIgTG9hZiIsIldldCBCZWhpbmQgdGhlIEVhcnMiLCJXaGVuIEhlbGwgRnJlZXplcyBPdmVyIiwiV2hvbGUgSG9nIiwiV2luIEhhbmRzIERvd24iLCJZb3UgUmVhcCBXaGF0IFlvdSBTb3ciLCJaZXJvIEluIE9uIiwiQWNjaWRlbnRhbGx5IG9uIFB1cnBvc2UiLCJBc3ltcHRvbWF0aWMgQ2FycmllciIsIkJpYmxpb3BvbGUncyBIYXZlbiIsIkNhcnRvZ3JhcGhlcidzIERlbGlnaHQiLCJEZW94eXJpYm9udWNsZWljIEFjaWQiLCJFbmlnbWF0aWMgQ29udW5kcnVtIiwiRmxpYmJlcnRpZ2liYmV04oCZcyBXaGltcyIsIkdlcnJ5bWFuZGVyaW5nIEJvdW5kYXJpZXMiLCJIaXBwb3BvdG9tb25zdHJvc2VzcXVpcGVkYWxpb3Bob2JpYSIsIkljb25vY2xhc3TigJlzIFJlcXVpZW0iLCJKdXJpc3BydWRlbnRpYWwgRmx1eCIsIkthbGVpZG9zY29waWMgUGFub3JhbWEiLCJMYWJ5cmludGhpbmUgQ29tcGxleGl0aWVzIiwiTWV0ZW1wc3ljaG9zaXMgSm91cm5leSIsIk5lYnVsb3VzIENvbnRpbmdlbmN5IiwiT21waGFsb3NrZXBzaXMgUHJhY3RpY2UiLCJQZXJuaWNpb3VzIEFuZW1pYSIsIlF1aW50ZXNzZW50aWFsIFBhcmFkb3giLCJSYW1idW5jdGlvdXMgQ2Fjb3Bob255IiwiU3luZWNkb2NoZSBSZXByZXNlbnRhdGlvbiIsIlRpbnRpbm5hYnVsYXRpb24gU291bmRzIiwiVW5lbmN1bWJlcmVkIFNlcmVuZGlwaXR5IiwiVmFsZXR1ZGluYXJpYW7igJlzIExpZmVzdHlsZSIsIldoaXBwZXJzbmFwcGVy4oCZcyBBbnRpY3MiLCJTdW1tZXIgYnJlZXplIiwiQ2hvY29sYXRlIGNha2UiLCJMaWdodG5pbmcgc3Rvcm0iLCJTaWxlbnQgbmlnaHQiLCJNb3JuaW5nIGNvZmZlZSIsIkdvbGRlbiByZXRyaWV2ZXIiLCJTdGFycnkgc2t5IiwiU25vd3kgbW91bnRhaW4iLCJIaWRkZW4gdHJlYXN1cmUiLCJTZWNyZXQgZ2FyZGVuIiwiUGVhY2VmdWwgbGFrZSIsIkJ1c3kgc3RyZWV0IiwiTG9uZWx5IGlzbGFuZCIsIkZ1bGwgbW9vbiIsIkhhcnZlc3QgbW9vbiIsIkxvc3QgY2l0eSIsIk1hZ2ljIHBvdGlvbiIsIlNwb29reSBmb3Jlc3QiLCJGbHlpbmcgY2FycGV0IiwiUm95YWwgcGFsYWNlIiwiU3Vua2VuIHNoaXAiLCJIYXVudGVkIGhvdXNlIiwiRGVzZXJ0IG9hc2lzIiwiSWN5IHJpdmVyIiwiV2lsZCBqdW5nbGUiLCJRdWlldCBtZWFkb3ciLCJGaWVyeSB2b2xjYW5vIiwiQW5jaWVudCBydWlucyIsIkNyeXN0YWwgY2F2ZSIsIkRpc3RhbnQgcGxhbmV0IiwiQWxpZW4gaW52YXNpb24iLCJUaW1lIG1hY2hpbmUiLCJEcmFnb24ncyBsYWlyIiwiUGlyYXRlIHNoaXAiLCJLbmlnaHQncyBhcm1vciIsIldpemFyZCdzIHNwZWxsIiwiRW5jaGFudGVkIG1pcnJvciIsIldpY2tlZCB3aXRjaCIsIkZhaXJ5IGdvZG1vdGhlciIsIlByaW5jZSBjaGFybWluZyIsIkNpbmRlcmVsbGEncyBzbGlwcGVyIiwiQmVhc3QncyBjYXN0bGUiLCJTbGVlcGluZyBiZWF1dHkiLCJMaXR0bGUgbWVybWFpZCIsIlJlZCByaWRpbmcgaG9vZCIsIkphY2sgYW5kIHRoZSBiZWFuc3RhbGsiLCJUaHJlZSBsaXR0bGUgcGlncyIsIkdpbmdlcmJyZWFkIG1hbiIsIkhhbnNlbCBhbmQgR3JldGVsIiwiUmFwdW56ZWwncyB0b3dlciIsIkFsYWRkaW4ncyBsYW1wIiwiR29sZGlsb2NrcyBhbmQgdGhlIHRocmVlIGJlYXJzIiwiUHVzcyBpbiBib290cyIsIlNub3cgV2hpdGUncyBhcHBsZSIsIlBpbm9jY2hpbydzIG5vc2UiLCJCbHVlIGZhaXJ5IiwiTWFkIGhhdHRlciIsIkNoZXNoaXJlIGNhdCIsIlF1ZWVuIG9mIGhlYXJ0cyIsIldoaXRlIHJhYmJpdCIsIlBldGVyIFBhbiIsIkNhcHRhaW4gSG9vayIsIlRpbmtlciBCZWxsIiwiV2VuZHkgRGFybGluZyIsIk1vYnkgRGljayIsIlRvbSBTYXd5ZXIiLCJIdWNrbGViZXJyeSBGaW5uIiwiUGhhbnRvbSBvZiB0aGUgT3BlcmEiLCJGcmFua2Vuc3RlaW4ncyBtb25zdGVyIiwiRHJhY3VsYSdzIGNhc3RsZSIsIlNoZXJsb2NrIEhvbG1lcyIsIldhdHNvbidzIHBpcGUiLCJDb3VudCBvZiBNb250ZSBDcmlzdG8iLCJHcmVhdCBFeHBlY3RhdGlvbnMiLCJQcmlkZSBhbmQgUHJlanVkaWNlIiwiV3V0aGVyaW5nIEhlaWdodHMiLCJKYW5lIEV5cmUiLCJMZXMgTWlzZXJhYmxlcyIsIlRoZSBPZHlzc2V5IiwiSWxpYWQiLCJSb21lbyBhbmQgSnVsaWV0IiwiSGFtbGV0J3MgZ2hvc3QiLCJNYWNiZXRoJ3MgZGFnZ2VyIiwiS2luZyBMZWFyJ3MgY3Jvd24iLCJPdGhlbGxvJ3MgamVhbG91c3kiLCJKdWxpdXMgQ2Flc2FyIiwiQW50b255IGFuZCBDbGVvcGF0cmEiLCJNaWRzdW1tZXIgTmlnaHQncyBEcmVhbSIsIlRlbXBlc3QiLCJNdWNoIEFkbyBBYm91dCBOb3RoaW5nIiwiVHdlbGZ0aCBOaWdodCIsIkFzIFlvdSBMaWtlIEl0IiwiTWVyY2hhbnQgb2YgVmVuaWNlIiwiTWVhc3VyZSBmb3IgTWVhc3VyZSIsIlRhbWluZyBvZiB0aGUgU2hyZXciLCJDb21lZHkgb2YgRXJyb3JzIiwiTG92ZSdzIExhYm91cidzIExvc3QiLCJUd28gR2VudGxlbWVuIG9mIFZlcm9uYSIsIk1vcm5pbmcgam9nIiwiQmlydGhkYXkgY2FrZSIsIlN1bW1lciB2YWNhdGlvbiIsIlB1YmxpYyBsaWJyYXJ5IiwiSWNlIGNyZWFtIGNvbmUiLCJHYXJkZW4gaG9zZSIsIkJvb2sgY2x1YiIsIkZseWluZyBraXRlIiwiTG9zdCBhbmQgZm91bmQiLCJSb2xsZXIgY29hc3RlciIsIlNhbmQgY2FzdGxlIiwiV2luZG93IHNob3BwaW5nIiwiUm9hZCB0cmlwIiwiU3VuZmxvd2VyIHNlZWRzIiwiU2tpcHBpbmcgcm9wZSIsIkZpcmVwbGFjZSBtYW50ZWwiLCJTaWRld2FsayBjaGFsayIsIlN0YXIgZ2F6aW5nIiwiRmlyc3QgYWlkIGtpdCIsIlRyZWUgaG91c2UiLCJCYWtpbmcgY29va2llcyIsIkZpc2hpbmcgcm9kIiwiQ2FyIHdhc2giLCJQaWNuaWMgYmFza2V0IiwiSmlnc2F3IHB1enpsZSIsIldhdGVyIGJhbGxvb24iLCJTYW5kYm94IiwiSG9wc2NvdGNoIiwiUGFwZXIgYWlycGxhbmUiLCJTbGVkIHJpZGluZyIsIlB1bXBraW4gcGF0Y2giLCJMZWFmIHBpbGUiLCJTbm93YmFsbCBmaWdodCIsIkxlbW9uYWRlIHN0YW5kIiwiRmxhc2hsaWdodCB0YWciLCJUcmVhc3VyZSBodW50IiwiRG9nIGxlYXNoIiwiQ2F0IG5hcCIsIkJpcmQgZmVlZGVyIiwiU3RhcmdhemVyIGxpbHkiLCJQYWxtIHRyZWUiLCJDb3JuIG1hemUiLCJIYXlyaWRlIiwiU2NhcmVjcm93IiwiQnV0dGVyZmx5IG5ldCIsIkNhbXBmaXJlIiwiTWFyc2htYWxsb3ciLCJDaG9jb2xhdGUgYmFyIiwiR3JhaGFtIGNyYWNrZXIiLCJDdXAgb2YgdGVhIiwiRGlzaHdhc2hlciIsIkxhdW5kcnkgYmFza2V0IiwiQ2xvdGhlc2xpbmUiLCJTZWFzaGVsbCIsIkJlYWNoIHRvd2VsIiwiU3Vuc2NyZWVuIiwiU3dpbW1pbmcgcG9vbCIsIkRpdmluZyBib2FyZCIsIkNhbm5vbmJhbGwiLCJXYXRlciBzbGlkZSIsIklubmVyIHR1YmUiLCJMaWZlIGphY2tldCIsIlN1bmdsYXNzZXMiLCJGbGlwIGZsb3BzIiwiQmVhY2ggYmFsbCIsIlN1cmZib2FyZCIsIlNhbmQgZG9sbGFyIiwiQm9vZ2llIGJvYXJkIiwiU25vcmtlbCIsIkRpdmUgbWFzayIsIldldHN1aXQiLCJCZWFjaCB1bWJyZWxsYSIsIkJlYWNoIGNoYWlyIiwiUGljbmljIHRhYmxlIiwiQmFyYmVjdWUgZ3JpbGwiLCJIb3QgZG9nIiwiSGFtYnVyZ2VyIiwiRnJlbmNoIGZyaWVzIiwiTWlsa3NoYWtlIiwiUm9vdCBiZWVyIGZsb2F0IiwiSWNlIGNyZWFtIHN1bmRhZSIsIlBvcHNpY2xlIiwiRnJ1aXQgc2FsYWQiLCJWZWdldGFibGUgZ2FyZGVuIiwiSGVyYiBnYXJkZW4iLCJHcmVlbmhvdXNlIiwiRmxvd2VyIGJlZCIsIkxhd24gbW93ZXIiLCJTcHJpbmtsZXIiLCJIZWRnZSB0cmltbWVyIiwiTGVhZiBibG93ZXIiLCJDaGFpbnNhdyIsIldoZWVsYmFycm93IiwiR2FyZGVuIGdsb3ZlcyIsIlBydW5pbmcgc2hlYXJzIiwiV2F0ZXJpbmcgY2FuIiwiUGxhbnQgZmVydGlsaXplciIsIkJpcmQgYmF0aCIsIkdub21lIiwiV2luZCBjaGltZSIsIkhhbW1vY2siLCJCYWNreWFyZCBmZW5jZSIsIkZpcmUgcGl0IiwiUGF0aW8gZnVybml0dXJlIiwiRGVjayIsIlBvcmNoIHN3aW5nIiwiRnJvbnQgZG9vciIsIkJhY2sgZG9vciIsIkdhcmFnZSBkb29yIiwiRHJpdmV3YXkiLCJTaWRld2FsayIsIlN0cmVldGxpZ2h0IiwiVHJhZmZpYyBsaWdodCIsIkNyb3Nzd2FsayIsIkJpY3ljbGUgbGFuZSIsIlBhcmtpbmcgbG90IiwiUGFya2luZyBtZXRlciIsIkNhciBhbGFybSIsIlNlYXQgYmVsdCIsIkdhcyBwZWRhbCIsIkJyYWtlIHBlZGFsIiwiU3RlZXJpbmcgd2hlZWwiLCJSZWFydmlldyBtaXJyb3IiLCJXaW5kc2hpZWxkIHdpcGVyIiwiVHVybiBzaWduYWwiLCJIZWFkbGlnaHQiLCJUYWlsIGxpZ2h0IiwiTGljZW5zZSBwbGF0ZSIsIkV4aGF1c3QgcGlwZSIsIk9pbCBjaGFuZ2UiLCJGbGF0IHRpcmUiLCJTcGFyZSB0aXJlIiwiVHJ1bmsiLCJHbG92ZSBjb21wYXJ0bWVudCIsIkRhc2hib2FyZCIsIkFpciBjb25kaXRpb25lciIsIkhlYXRlciIsIlN1bnJvb2YiLCJDYXIgc3RlcmVvIiwiU3BlYWtlciIsIlN1Yndvb2ZlciIsIkFtcGxpZmllciIsIkNEIHBsYXllciIsIk1QMyBwbGF5ZXIiLCJBdXhpbGlhcnkgaW5wdXQiLCJVU0IgcG9ydCIsIkJsdWV0b290aCIsIkdQUyIsIk1hcCIsIkNvbXBhc3MiLCJUaGVybW9tZXRlciIsIkJhcm9tZXRlciIsIkFsdGltZXRlciIsIkFuZW1vbWV0ZXIiLCJIeWdyb21ldGVyIiwiUmFpbiBnYXVnZSIsIldlYXRoZXIgdmFuZSIsIldlYXRoZXIgYmFsbG9vbiIsIkh1cnJpY2FuZSIsIlRvcm5hZG8iLCJFYXJ0aHF1YWtlIiwiVHN1bmFtaSIsIkZsb29kIiwiRHJvdWdodCIsIkJsaXp6YXJkIiwiSGVhdHdhdmUiLCJDb2xkIHNuYXAiLCJGcm9zdCIsIkhhaWxzdG9ybSIsIlNhbmRzdG9ybSIsIlRodW5kZXJzdG9ybSIsIkxpZ2h0bmluZyBib2x0IiwiUmFpbmJvdyIsIkNsb3VkIiwiU3VuIiwiTW9vbiIsIlN0YXIiLCJDb21ldCIsIkFzdGVyb2lkIiwiTWV0ZW9yIiwiQmxhY2sgaG9sZSIsIkdhbGF4eSIsIlVuaXZlcnNlIiwiU29sYXIgc3lzdGVtIiwiUGxhbmV0IiwiU2F0ZWxsaXRlIiwiU3BhY2Ugc3RhdGlvbiIsIlJvY2tldCIsIlNodXR0bGUiLCJTcGFjZSBzdWl0IiwiT3h5Z2VuIHRhbmsiLCJHcmF2aXR5IiwiT3JiaXQiLCJMYXVuY2ggcGFkIiwiQXN0cm9uYXV0IiwiQ29zbW9uYXV0IiwiU3BhY2V3YWxrIiwiTW9vbiBsYW5kaW5nIiwiTWFycyByb3ZlciIsIkFsaWVuIiwiVUZPIiwiRXh0cmF0ZXJyZXN0cmlhbCIsIlNwYWNlIGFsaWVuIiwiTWFydGlhbiIsIlZlbnVzaWFuIiwiSm92aWFuIiwiU2F0dXJuaWFuIiwiTmVwdHVuaWFuIiwiUGx1dG9uaWFuIiwiTWVyY3VyaWFuIiwiTHVuYXIiLCJTb2xhciIsIlN0ZWxsYXIiLCJHYWxhY3RpYyIsIkNvc21pYyIsIkFzdHJvbm9taWNhbCIsIlRlbGVzY29wZSIsIk1pY3Jvc2NvcGUiLCJNYWduaWZ5aW5nIGdsYXNzIiwiQmlub2N1bGFycyIsIlBlcmlzY29wZSIsIlJhZGFyIiwiU29uYXIiLCJMaWRhciIsIkNhbWVyYSIsIlZpZGVvIGNhbWVyYSIsIldlYmNhbSIsIkRpZ2l0YWwgY2FtZXJhIiwiRmlsbSBjYW1lcmEiLCJTTFIgY2FtZXJhIiwiUG9pbnQtYW5kLXNob290IGNhbWVyYSIsIlpvb20gbGVucyIsIldpZGUtYW5nbGUgbGVucyIsIlRlbGVwaG90byBsZW5zIiwiRmlzaC1leWUgbGVucyIsIk1hY3JvIGxlbnMiLCJUcmlwb2QiLCJNb25vcG9kIiwiQ2FtZXJhIGJhZyIsIk1lbW9yeSBjYXJkIiwiQmF0dGVyeSIsIkNoYXJnZXIiLCJGbGFzaCIsIkxpZ2h0IG1ldGVyIiwiVmlld2ZpbmRlciIsIlNodXR0ZXIiLCJBcGVydHVyZSIsIklTTyIsIkV4cG9zdXJlIiwiV2hpdGUgYmFsYW5jZSIsIkNvbG9yIGNvcnJlY3Rpb24iLCJQaG90byBlZGl0aW5nIiwiSW1hZ2UgcHJvY2Vzc2luZyIsIlBob3Rvc2hvcCIsIkxpZ2h0cm9vbSIsIkdJTVAiLCJQYWludCIsIkNhbnZhcyIsIkJydXNoIiwiUGFsZXR0ZSIsIkVhc2VsIiwiU2tldGNoYm9vayIsIkNoYXJjb2FsIiwiUGFzdGVsIiwiV2F0ZXJjb2xvciIsIkFjcnlsaWMiLCJPaWwgcGFpbnQiLCJTcHJheSBwYWludCIsIlN0ZW5jaWwiLCJHcmFmZml0aSIsIk11cmFsIiwiU2N1bHB0dXJlIiwiQ2xheSIsIkNlcmFtaWNzIiwiUG90dGVyeSIsIktpbG4iLCJXaGVlbCIsIkdsYXplIiwiRmlyaW5nIiwiTW9sZGluZyIsIkNhcnZpbmciLCJDaGlzZWxpbmciLCJTYW5kaW5nIiwiUG9saXNoaW5nIiwiV2F4aW5nIiwiVmFybmlzaGluZyIsIkxhY3F1ZXJpbmciLCJXb29kIiwiTWV0YWwiLCJTdG9uZSIsIk1hcmJsZSIsIkdyYW5pdGUiLCJTbGF0ZSIsIlNhbmRzdG9uZSIsIkxpbWVzdG9uZSIsIlF1YXJ0eiIsIkdsYXNzIiwiUGxhc3RpYyIsIlJ1YmJlciIsIkxlYXRoZXIiLCJGYWJyaWMiLCJXb29sIiwiQ290dG9uIiwiU2lsayIsIkxpbmVuIiwiSGVtcCIsIkJhbWJvbyIsIlBhcGVyIiwiQ2FyZGJvYXJkIiwiRm9hbSIsIlNwb25nZSIsIldpcmUiLCJSb3BlIiwiU3RyaW5nIiwiVGhyZWFkIiwiWWFybiIsIlJpYmJvbiIsIlRhcGUiLCJHbHVlIiwiQWRoZXNpdmUiLCJOYWlsIiwiU2NyZXciLCJCb2x0IiwiTnV0IiwiV2FzaGVyIiwiSGluZ2UiLCJMYXRjaCIsIkxvY2siLCJLZXkiLCJIYW5kbGUiLCJLbm9iIiwiQnV0dG9uIiwiU3dpdGNoIiwiTGV2ZXIiLCJEaWFsIiwiR2F1Z2UiLCJNZXRlciIsIkluZGljYXRvciIsIkRpc3BsYXkiLCJTY3JlZW4iLCJNb25pdG9yIiwiUHJvamVjdG9yIiwiVGVsZXZpc2lvbiIsIlJhZGlvIiwiU3RlcmVvIiwiTWljcm9waG9uZSIsIkhlYWRwaG9uZXMiLCJFYXJidWRzIiwiTWl4ZXIiLCJFcXVhbGl6ZXIiLCJDb21wcmVzc29yIiwiTGltaXRlciIsIlJldmVyYiIsIkVjaG8iLCJEZWxheSIsIkZsYW5nZXIiLCJQaGFzZXIiLCJDaG9ydXMiLCJEaXN0b3J0aW9uIiwiT3ZlcmRyaXZlIiwiRnV6eiIsIldhaC13YWgiLCJUcmVtb2xvIiwiVmlicmF0byIsIk9jdGF2ZSIsIkhhcm1vbml6ZXIiLCJQaXRjaCBzaGlmdGVyIiwiTG9vcGVyIiwiU2FtcGxlciIsIlNlcXVlbmNlciIsIlN5bnRoZXNpemVyIiwiRHJ1bSBtYWNoaW5lIiwiVHVybnRhYmxlIiwiUmVjb3JkIHBsYXllciIsIkNhc3NldHRlIHBsYXllciIsIkRWRCBwbGF5ZXIiLCJCbHUtcmF5IHBsYXllciIsIlZDUiIsIkxhc2VyZGlzYyBwbGF5ZXIiLCJHYW1lIGNvbnNvbGUiLCJKb3lzdGljayIsIkdhbWVwYWQiLCJLZXlib2FyZCIsIk1vdXNlIiwiVHJhY2tiYWxsIiwiVG91Y2hwYWQiLCJUb3VjaHNjcmVlbiIsIlN0eWx1cyIsIlBlbiIsIlBlbmNpbCIsIkVyYXNlciIsIlJ1bGVyIiwiUHJvdHJhY3RvciIsIkNhbGN1bGF0b3IiLCJBYmFjdXMiLCJTbGlkZSBydWxlIiwiVHlwZXdyaXRlciIsIlByaW50ZXIiLCJTY2FubmVyIiwiRmF4IG1hY2hpbmUiLCJQaG90b2NvcGllciIsIlNocmVkZGVyIiwiTGFtaW5hdG9yIiwiQmluZGluZyBtYWNoaW5lIiwiU3RhcGxlciIsIlBhcGVyIGNsaXAiLCJCaW5kZXIgY2xpcCIsIlJ1YmJlciBiYW5kIiwiRW52ZWxvcGUiLCJTdGFtcCIsIlBvc3RjYXJkIiwiTGV0dGVyIiwiUGFja2FnZSIsIkJveCIsIkNyYXRlIiwiUGFsbGV0IiwiQ29udGFpbmVyIiwiVHJ1Y2siLCJWYW4iLCJDYXIiLCJCdXMiLCJUcmFpbiIsIlRyYW0iLCJUcm9sbGV5IiwiU3Vid2F5IiwiTWV0cm8iLCJMaWdodCByYWlsIiwiU3RyZWV0Y2FyIiwiQ2FibGUgY2FyIiwiRnVuaWN1bGFyIiwiTW9ub3JhaWwiLCJNYWdsZXYiLCJBaXJwbGFuZSIsIkhlbGljb3B0ZXIiLCJHbGlkZXIiLCJCYWxsb29uIiwiQmxpbXAiLCJaZXBwZWxpbiIsIlBhcmFjaHV0ZSIsIkhhbmcgZ2xpZGVyIiwiSmV0IHBhY2siLCJSb2NrZXQgcGFjayIsIlNwYWNlIHNodXR0bGUiLCJQcm9iZSIsIlJvdmVyIiwiTGFuZGVyIiwiQ2Fwc3VsZSIsIk1vZHVsZSIsIlNwYWNlY3JhZnQiLCJTcGFjZXNoaXAiLCJTdGFyc2hpcCIsIkVudGVycHJpc2UiLCJNaWxsZW5uaXVtIEZhbGNvbiIsIkRlYXRoIFN0YXIiLCJUYXJkaXMiLCJEZWxvcmVhbiIsIkJhdG1vYmlsZSIsIkdob3N0YnVzdGVycycgRWN0by0xIiwiS25pZ2h0IFJpZGVyJ3MgS0lUVCIsIkEtVGVhbSBWYW4iLCJTY29vYnktRG9vJ3MgTXlzdGVyeSBNYWNoaW5lIiwiRmxpbnRzdG9uZXMnIGNhciIsIkNoaXR0eSBDaGl0dHkgQmFuZyBCYW5nIiwiSGVyYmllIiwiQ2hyaXN0aW5lIiwiRWxlYW5vciIsIkdlbmVyYWwgTGVlIiwiSy5JLlQuVC4iLCJPcHRpbXVzIFByaW1lIiwiQnVtYmxlYmVlIiwiTWVnYXRyb24iLCJTdGFyc2NyZWFtIiwiR3JpbWxvY2siLCJTb3VuZHdhdmUiLCJTaG9ja3dhdmUiLCJKYXp6IiwiUmF0Y2hldCJdfQ==" +} diff --git a/src/games/input-test/index.js b/src/games/input-test/index.js index fe0b57d4..22412be9 100644 --- a/src/games/input-test/index.js +++ b/src/games/input-test/index.js @@ -1,4 +1,4 @@ -const { Asset, Game, GameNode, Colors, Shapes, ShapeUtils } = require('squish-0765'); +const { Asset, Game, GameNode, Colors, Shapes, ShapeUtils } = require('squish-1009'); const { dictionary } = require('../../common/util'); const fs = require('fs'); @@ -8,7 +8,7 @@ class InputTest extends Game { static metadata() { return { aspectRatio: {x: 16, y: 9}, - squishVersion: '0765', + squishVersion: '1009', author: 'Joseph Garcia', name: 'Input Test', thumbnail: 'c6d38aca68fed708d08d284a5d201a0a', @@ -59,9 +59,11 @@ class InputTest extends Game { const newText = this.textNode.node.text; newText.text = text; this.textNode.node.textInfo = newText; + this.textNode.node.onStateChange(); } } - } + }, + playerIds: [1] }); const fileInputShape = ShapeUtils.rectangle(60, 10, 20, 20); diff --git a/src/homegames_root/HomegamesRoot.js b/src/homegames_root/HomegamesRoot.js index 134677d0..ce5c279f 100644 --- a/src/homegames_root/HomegamesRoot.js +++ b/src/homegames_root/HomegamesRoot.js @@ -6,7 +6,7 @@ const fs = require('fs'); const process = require('process'); if (!process.env.SQUISH_PATH) { - const defaultVersion = 'squish-0767'; + const defaultVersion = 'squish-1009'; log.info('No SQUISH_PATH found. Using default: ' + defaultVersion); process.env.SQUISH_PATH = defaultVersion; } @@ -364,9 +364,6 @@ class HomegamesRoot { handlePlayerUpdate(playerId, newData) { this.updateLabels(); - if (this.viewStates[playerId] && this.viewStates[playerId].state === 'settings') { - this.topLayerRoot.removeChild(this.viewStates[playerId].node.id); - } } handleNewSpectator(spectator) { @@ -422,7 +419,10 @@ class HomegamesRoot { } showSettings(playerId) { - this.topLayerRoot.clearChildren(); + if (this.viewStates[playerId]?.state == 'settings') { + const oldNodeId = this.viewStates[playerId].node.id; + this.topLayerRoot.removeChild(this.viewStates[playerId].node.id); + } this.viewStates[playerId] = {state: 'settings'}; const playerInfo = this.session.playerInfoMap[playerId] || {}; @@ -438,27 +438,29 @@ class HomegamesRoot { onRemove: () => { this.topLayerRoot.removeChild(modal.node.id); }, - onNameChange: (text) => { + onNameChange: (text) => new Promise((resolve, reject) => { this.homenamesHelper.updatePlayerInfo(playerId, { playerName: text - }); - }, - onSoundToggle: (newVal) => { + }).then(resolve); + }), + onSoundToggle: (newVal) => new Promise((resolve, reject) => { + // used to filter out audio nodes when muted and stuff like that + this.session.squisher.updatePlayerSettings(playerId, {[PLAYER_SETTINGS.SOUND]: {enabled: newVal}}); this.homenamesHelper.updatePlayerSetting(playerId, PLAYER_SETTINGS.SOUND, { enabled: newVal }).then(() => { log.info('just updated setting??'); + resolve(); }); - }, + }), onExportSessionData: () => { return this.exportSessionData(); } }); - - - this.topLayerRoot.addChild(modal); + this.viewStates[playerId].node = modal; + this.topLayerRoot.addChild(modal); }); } diff --git a/src/homegames_root/settings.js b/src/homegames_root/settings.js index 47b29d07..916f4e37 100644 --- a/src/homegames_root/settings.js +++ b/src/homegames_root/settings.js @@ -1,4 +1,4 @@ -const { GameNode, Colors, ShapeUtils, Shapes } = require('squish-0767'); +const { GameNode, Colors, ShapeUtils, Shapes } = require(process.env.SQUISH_PATH); const HomenamesHelper = require('../util/homenames-helper'); const { COLORS } = Colors; const PLAYER_SETTINGS = require('../common/player-settings'); @@ -116,6 +116,8 @@ const nameSettingContainer = ({ playerId, onNameChange, session }) => { const nameSettingContainerHeight = 16; const nameSettingContainerWidth = 35; + let nameText; + const container = new GameNode.Shape({ shapeType: Shapes.POLYGON, coordinates2d: ShapeUtils.rectangle(23, 23, 30, 6), @@ -123,7 +125,16 @@ const nameSettingContainer = ({ playerId, onNameChange, session }) => { type: 'text', oninput: (player, text) => { if (text) { - onNameChange && onNameChange(text); + if (onNameChange) { + onNameChange(text).then(() => { + if (nameText) { + const curText = Object.assign({}, nameText.node.text); + curText.text = `Name: ${text}`; + nameText.node.text = curText; + nameText.node.onStateChange(); + } + }); + } } } } @@ -132,7 +143,7 @@ const nameSettingContainer = ({ playerId, onNameChange, session }) => { const homenamesHelper = session.homenamesHelper; homenamesHelper.getPlayerInfo(playerId).then(playerInfo => { - const nameText = new GameNode.Text({ + nameText = new GameNode.Text({ textInfo: { x: 23, y: 25, @@ -156,14 +167,23 @@ const soundSettingContainer = ({ playerId, onToggle, session }) => { let soundEnabled = true; let _playerSettings = {}; - const handleClick = () => { - onToggle(!soundEnabled); - }; + + let soundEnabledText; const soundSettingContainer = new GameNode.Shape({ shapeType: Shapes.POLYGON, coordinates2d: ShapeUtils.rectangle(23, 34, 30, 6), - onClick: handleClick, + onClick: () => { + soundEnabled = !soundEnabled; + onToggle(soundEnabled).then(() => { + const currentText = soundEnabledText?.node?.text ? Object.assign({}, soundEnabledText.node.text) : null; + if (currentText) { + currentText.text = `Sound: ${soundEnabled ? 'on' : 'off'}`; + soundEnabledText.node.text = currentText; + soundEnabledText.node.onStateChange(); + } + }); + }, playerIds: [playerId] }); @@ -173,7 +193,7 @@ const soundSettingContainer = ({ playerId, onToggle, session }) => { soundEnabled = false; } - const soundEnabledText = new GameNode.Text({ + soundEnabledText = new GameNode.Text({ textInfo: { x: 23, y: 36, @@ -206,7 +226,6 @@ const saveRecordingContainer = ({ playerId, onExportSessionData }) => { const container = new GameNode.Shape({ coordinates2d: ShapeUtils.rectangle(23, 45, 35, 8), shapeType: Shapes.POLYGON, - // fill: COLORS.HG_YELLOW, playerIds: [playerId], onClick: () => { const exportPath = onExportSessionData(); @@ -214,6 +233,7 @@ const saveRecordingContainer = ({ playerId, onExportSessionData }) => { newText.text = 'Wrote recording to ' + exportPath; newText.size = 0.8; text.node.text = newText; + text.node.onStateChange(); } });