diff --git a/README.md b/README.md index f889cf6..b8ef6b4 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,7 @@ The following resources were used for the creation of the game's logic: * https://www.zapsplat.com/?s=scrabble * https://www.scrabble3d.info/t2342f139-Default-rules.html - -The solution to locale-ise language in HTML is extremely naive and there are a lot better ways to do it, i did what i could in the time without a full refractor and SSR. -However it is not at much detriment of readability and maintainability of the code so i didn't deem it neccesary. - -The locales are stored in different language files so it is easier for people to contribute and thus more maintanable. - -If the scope allowed for it, every function would be (reasonably) unit tested. +Simple client/server scrabble game implemented in JavaScript with lots of room to add support for stuff like databases in the future. ### Configuration Guide @@ -29,16 +23,40 @@ Make sure your working directory is root/server If you want a custom port create a .env file and use the variable PORT or just use environment variables +You can play in singleplayer or multiplayer, both is accessable at localhost:8080 upon running + +To play singleplayer, simply press the singleplayer button, but note that there is no AI or opponent, you simply get a board to play with. + +To play multiplayer, simply find (or force) a friend to also load the site from your private IP or whatever other networking solution you may have, both enter your name on the home page. One of you needs to create a lobby and the other needs to join it with the buttons labled as such, press ready then bam, you're in a game. Turns are denoted by the colour on the person at the top left, if it's your turn. your name will be green, if it's theirs, their name will be blue. + ### Implementation Rationale +These are some of my thoughts behind why I implemeneted stuff the way I did. To see more, take a look at TODO. + +#### The Server +server/src/game-logic.js is probably the file you're looking for :) + +The server is a node express server that runs on HTTP so that websockets can be routed through the same port. I use socket.io because it's a comfortable abstraction over the already high-level websocket API. + +All of the code on the server seperates domain logic from networking logic (see comments in files) to keep the code clean and the API simple and understandable. + +#### The Client +I chose to support multiple languages in this project to make it more accessable to people if they wish to translate it. + +The solution to locale-ise language in HTML is extremely naive and there are a lot better ways to do it, I did what I could in the time without a full refractor and SSR. +However it is not at much detriment of readability and maintainability of the code so I didn't deem it neccesary. However, it is unfinished. + +The locales are stored in different language files so it is easier for people to contribute and thus more maintanable. + +#### General Code +If the scope allowed for it, every function would be (reasonably) unit tested. ### Bugs and Issues Prettymuch all of the bugs i'm aware of occur when a user reconnects to a game that's taking place. ESPECIALLY if that user is the host of the game ### Contributing - -To contribute a translation there's a few scripts that need to be run +To contribute a translation there's a few scripts that need to be run: To see what needs to be completed code-wise, take a look at `TODO`, there you will find tasks that need to be completed as well as known bugs. There's also a lot of TODOs in the code :) diff --git a/server/report.20210510.203404.29448.0.001.json b/server/report.20210510.203404.29448.0.001.json new file mode 100644 index 0000000..da3f99f --- /dev/null +++ b/server/report.20210510.203404.29448.0.001.json @@ -0,0 +1,154 @@ + +{ + "header": { + "reportVersion": 1, + "event": "Allocation failed - JavaScript heap out of memory", + "trigger": "FatalError", + "filename": "report.20210510.203404.29448.0.001.json", + "dumpEventTime": "2021-05-10T20:34:04Z", + "dumpEventTimeStamp": "1620675244940", + "processId": 29448, + "cwd": "P:\\Uni\\U30221-Application-Programming\\server", + "commandLine": [ + "C:\\Program Files\\nodejs\\node.exe", + "index.js" + ], + "nodejsVersion": "v12.16.1", + "wordSize": 64, + "arch": "x64", + "platform": "win32", + "componentVersions": { + "node": "12.16.1", + "v8": "7.8.279.23-node.31", + "uv": "1.34.0", + "zlib": "1.2.11", + "brotli": "1.0.7", + "ares": "1.15.0", + "modules": "72", + "nghttp2": "1.40.0", + "napi": "5", + "llhttp": "2.0.4", + "http_parser": "2.9.3", + "openssl": "1.1.1d", + "cldr": "35.1", + "icu": "64.2", + "tz": "2019c", + "unicode": "12.1" + }, + "release": { + "name": "node", + "lts": "Erbium", + "headersUrl": "https://nodejs.org/download/release/v12.16.1/node-v12.16.1-headers.tar.gz", + "sourceUrl": "https://nodejs.org/download/release/v12.16.1/node-v12.16.1.tar.gz", + "libUrl": "https://nodejs.org/download/release/v12.16.1/win-x64/node.lib" + }, + "osName": "Windows_NT", + "osRelease": "10.0.19041", + "osVersion": "Windows 10 Pro", + "osMachine": "x86_64", + "cpus": [ + { + "model": "Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz", + "speed": 4200, + "user": 38138500, + "nice": 0, + "sys": 40069531, + "idle": 456379625, + "irq": 5650218 + }, + { + "model": "Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz", + "speed": 4200, + "user": 24136171, + "nice": 0, + "sys": 25101031, + "idle": 485350312, + "irq": 197062 + }, + { + "model": "Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz", + "speed": 4200, + "user": 36622515, + "nice": 0, + "sys": 35968140, + "idle": 461996859, + "irq": 195187 + }, + { + "model": "Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz", + "speed": 4200, + "user": 30077687, + "nice": 0, + "sys": 33668906, + "idle": 470840906, + "irq": 157343 + }, + { + "model": "Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz", + "speed": 4200, + "user": 38422656, + "nice": 0, + "sys": 45939125, + "idle": 450225718, + "irq": 187234 + }, + { + "model": "Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz", + "speed": 4200, + "user": 40894406, + "nice": 0, + "sys": 60784640, + "idle": 432908453, + "irq": 141875 + }, + { + "model": "Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz", + "speed": 4200, + "user": 59644109, + "nice": 0, + "sys": 97409203, + "idle": 377534187, + "irq": 182906 + }, + { + "model": "Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz", + "speed": 4200, + "user": 75688171, + "nice": 0, + "sys": 120512359, + "idle": 338386968, + "irq": 173359 + } + ], + "networkInterfaces": [ + { + "name": "Ethernet", + "internal": false, + "mac": "10:7b:44:15:f2:69", + "address": "fe80::1898:828c:2e38:4e69", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 4 + }, + { + "name": "Ethernet", + "internal": false, + "mac": "10:7b:44:15:f2:69", + "address": "10.210.71.53", + "netmask": "255.255.240.0", + "family": "IPv4" + }, + { + "name": "Loopback Pseudo-Interface 1", + "internal": true, + "mac": "00:00:00:00:00:00", + "address": "::1", + "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + "family": "IPv6", + "scopeid": 0 + }, + { + "name": "Loopback Pseudo-Interface 1", + "internal": true, + "mac": "00:00:00:00:00:00", + "address": " \ No newline at end of file diff --git a/server/src/game-logic.js b/server/src/game-logic.js index d971127..727e596 100644 --- a/server/src/game-logic.js +++ b/server/src/game-logic.js @@ -324,67 +324,158 @@ function PlayTurn(gameuid, playeruid, turn) } } } - console.log(GetGameByUserUID(playeruid)); // process outcome const temptiles = turn.oldboardtiles.concat(turn.boardtiles); // algorithm for getting words - let words = []; + // let words = []; + // for (const newpiece of diff) + // { + // const traverse = (frompiece, direction, word) => { + // // check up, down, left, right for others + // const check = (x, y) => { + // for (const checkpiece of temptiles) + // { + // if (!checkpiece.visited) checkpiece.visited = false; + // // console.log(checkpiece); + // // there's a piece there + // if (checkpiece.pos.x === x && checkpiece.pos.y === y && checkpiece.visited === false) + // { + // console.log(word); + // temptiles[temptiles.indexOf(checkpiece)].visited = true; + // console.log(temptiles); + // return traverse(checkpiece, direction, word + checkpiece.letter); + // } + // return word; + // } + // } + + // if (direction === 0) + // { + // let up = check(frompiece.pos.x , frompiece.pos.y + 1); + // words.push(up); + // } + // if (direction === 0) + // { + // let right = check(frompiece.pos.x + 1, frompiece.pos.y ); + // words.push(right); + // } + // if (direction === 0) + // { + // let down = check(frompiece.pos.x , frompiece.pos.y - 1); + // words.push(down); + // } + // if (direction === 0) + // { + // let left = check(frompiece.pos.x - 1, frompiece.pos.y ); + // words.push(left); + // } + + // return word; + // } + // // traverse from the piece in all directions + // traverse(newpiece, 0, newpiece.letter); + // traverse(newpiece, 1, newpiece.letter); + // traverse(newpiece, 2, newpiece.letter); + // traverse(newpiece, 3, newpiece.letter); + // } + // console.log(words); + + // const traverse = (x, y, direction, acc) => { + + // console.log(temptiles) + // for (const checkpiece of temptiles) + // { + // if (direction === 0) + // { + // if (x === checkpiece.pos.x && y + 1 === checkpiece.pos.y) + // { + // return traverse(x, y + 1, direction, acc + checkpiece.letter); + // } + // } + // if (direction === 1) + // { + // if (x + 1 === checkpiece.pos.x && y === checkpiece.pos.y) + // { + // return traverse(x + 1, y, direction, acc + checkpiece.letter); + // } + // } + // if (direction === 2) + // { + // if (x === checkpiece.pos.x && y - 1 === checkpiece.pos.y) + // { + // return traverse(x, y - 1, direction, acc + checkpiece.letter); + // } + // } + // if (direction === 3) + // { + // if (x - 1 === checkpiece.pos.x && y === checkpiece.pos.y) + // { + // return traverse(x - 1, y, direction, acc + checkpiece.letter); + // } + // } + // return acc; + // } + + // } + + // for (const newpiece of diff) + // { + // let wordsFromPiece = []; + // wordsFromPiece.push(traverse(newpiece.pos.x, newpiece.pos.y, 0, newpiece.letter)); + // wordsFromPiece.push(traverse(newpiece.pos.x, newpiece.pos.y, 1, newpiece.letter)); + // wordsFromPiece.push(traverse(newpiece.pos.x, newpiece.pos.y, 2, newpiece.letter)); + // wordsFromPiece.push(traverse(newpiece.pos.x, newpiece.pos.y, 3, newpiece.letter)); + // console.log(wordsFromPiece); + // } + + // Attempt #3 with 3 hours before the deadline + + // no recursion this time + for (const newpiece of diff) { - const traverse = (frompiece, direction, word) => { - // check up, down, left, right for others - const check = (x, y) => { - for (const checkpiece of temptiles) - { - if (!checkpiece.visited) checkpiece.visited = false; - // console.log(checkpiece); - // there's a piece there - if (checkpiece.pos.x === x && checkpiece.pos.y === y && checkpiece.visited === false) - { - console.log(word); - temptiles[temptiles.indexOf(checkpiece)].visited = true; - console.log(temptiles); - return traverse(checkpiece, direction, word + checkpiece.letter); - } - return word; - } - } - - if (direction === 0) - { - let up = check(frompiece.pos.x , frompiece.pos.y + 1); - words.push(up); - } - if (direction === 0) - { - let right = check(frompiece.pos.x + 1, frompiece.pos.y ); - words.push(right); - } - if (direction === 0) - { - let down = check(frompiece.pos.x , frompiece.pos.y - 1); - words.push(down); - } - if (direction === 0) - { - let left = check(frompiece.pos.x - 1, frompiece.pos.y ); - words.push(left); - } + let wordsFromPiece = []; - return word; + const check = (x, y) => { + for (const checkpiece of temptiles) + { + if (checkpiece.pos.x === x && checkpiece.pos.y === y) + return checkpiece; + } + return false; + }; + + const directions = [ + {x: -1, y: 0}, + {x: 1, y: 0}, + {x: 0, y: -1}, + {x: 0, y: 1} + ]; + + for (let i = 0; i < 4; i++) + { + let word = ''; + const direction = directions[i]; + + let coords = {x: newpiece.pos.x, y: newpiece.pos.y}; + while(true) + { + const ret = check(coords.x, coords.y); + // console.log(ret); + if (ret === false) + break; + word += ret.letter; + coords.x += direction.x; + coords.y += direction.y; + } + if (word.length === 1) continue; + wordsFromPiece.push(word); + console.log(word); } - // traverse from the piece in all directions - traverse(newpiece, 0, newpiece.letter); - traverse(newpiece, 1, newpiece.letter); - traverse(newpiece, 2, newpiece.letter); - traverse(newpiece, 3, newpiece.letter); + } - console.log(words); - // process turn and allocate scores - - // give user new tiles // update tiles with scores turn.boardtiles = turn.oldboardtiles.concat(turn.boardtiles); @@ -402,6 +493,16 @@ function PlayTurn(gameuid, playeruid, turn) turn.boardtiles[tile].score = score; } + // process turn and allocate scores + + // for every new word + // calculate based on TL/DL/DW/TW and tile score the score + // send to client + + + // give user new tiles + + ActiveGames[gameuid].gamestates.push(turn); ActiveGames[gameuid].turn = turninfo.newTurn; ActiveGames[gameuid].turntotal = turninfo.newTotalTurn;