From 4c30636af447c32002a1b3a842a4d9bbe4ddfd69 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Wed, 27 Mar 2024 16:56:38 -0400 Subject: [PATCH] Deterministic tiling algorythm based on file clustering --- action.yml | 9 +- package-lock.json | 6 + package.json | 1 + src/clusterize.js | 22 ++ src/generateMap.js | 12 +- src/map.js | 27 ++- templates/gitterra.yml | 4 +- test/gitterra.json | 455 +++++++++++++++++++++++++++++++++++++---- 8 files changed, 491 insertions(+), 45 deletions(-) create mode 100644 src/clusterize.js diff --git a/action.yml b/action.yml index f1f9495..41b492e 100644 --- a/action.yml +++ b/action.yml @@ -24,15 +24,19 @@ runs: shell: "bash" - name: Extracting the inner knowledge hidden inside the scrolls - run: time wizard/scc scrolls/ --format json --output gitterra.json + run: time wizard/scc scrolls/ --by-file --format json --output gitterra.json shell: "bash" - - name: Transporting the builders to the construction site + - name: Transporting builders to the construction site uses: actions/checkout@v4 with: repository: "GitTerraGame/Play-GitTerra-Action" path: "builders" + - name: Equipping builders with the tools + run: (cd builders && npm install) + shell: "bash" + - name: Building the city of GitTerra run: node builders/src/generateMap.js scrolls/.gitterra.config.js shell: "bash" @@ -43,4 +47,3 @@ runs: name: gitterra path: | index.html - gitterra.json diff --git a/package-lock.json b/package-lock.json index 7cc83bb..be640d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.2", "license": "MIT", "dependencies": { + "kmeansjs": "^0.0.3", "open-cli": "^8.0.0" } }, @@ -200,6 +201,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/kmeansjs": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/kmeansjs/-/kmeansjs-0.0.3.tgz", + "integrity": "sha512-rZQ/xf2v6Mocngyu8flXXX90KAWS6aYtc0ZAVdpUFq5EHzKpkDMQqQqvQF98hGdCAH+ZUE6p58RkTwLRJElIYw==" + }, "node_modules/meow": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", diff --git a/package.json b/package.json index 59b77c3..9b026fd 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "license": "MIT", "type": "module", "dependencies": { + "kmeansjs": "^0.0.3", "open-cli": "^8.0.0" } } diff --git a/src/clusterize.js b/src/clusterize.js new file mode 100644 index 0000000..323f700 --- /dev/null +++ b/src/clusterize.js @@ -0,0 +1,22 @@ +import kmeans from "kmeansjs"; + +async function clusterize(files, number_of_blocks) { + const vector = files.map((file) => [file, file.Bytes, file.Lines]); + + const clusters = await new Promise((resolve, reject) => { + const k = + vector.length > number_of_blocks ? number_of_blocks : vector.length; + + kmeans(vector, k, function (err, res) { + if (err) { + reject(err); + } else { + resolve(res); + } + }); + }); + + return await clusters; +} + +export default clusterize; diff --git a/src/generateMap.js b/src/generateMap.js index e2193d7..6e60e87 100644 --- a/src/generateMap.js +++ b/src/generateMap.js @@ -2,6 +2,7 @@ import { pathToFileURL } from "url"; import { generateMapHTML } from "./map.js"; import { defaultGameConfig } from "./gameConfig.js"; +import clusterize from "./clusterize.js"; import fs from "fs"; import path from "path"; @@ -48,6 +49,9 @@ const repoStats = { }, }; +/* + * Calculating global statistics to determine the number of city blocks + */ const repo = JSON.parse(fs.readFileSync(input, "utf8")); repo.forEach((elem) => { repoStats.total.bytes += elem.Bytes; @@ -75,5 +79,11 @@ const number_of_blocks = Math.round( gameConfig.minTiles ); -const mapHTML = generateMapHTML(gameConfig, number_of_blocks); +/** + * Deterministicly group files into clusters for each city block + */ +const files = repo.map((elem) => elem.Files).flat(); +const clusters = await clusterize(files, number_of_blocks); + +const mapHTML = generateMapHTML(gameConfig, clusters); fs.writeFileSync(output, mapHTML); diff --git a/src/map.js b/src/map.js index a20dca4..1fbd7ac 100644 --- a/src/map.js +++ b/src/map.js @@ -23,7 +23,7 @@ function getMapTileCoordinates(n) { // boolean representing the side of the diamond, e.g. left (false) or right (true) const direction = Math.ceil((n - Math.pow(Math.floor(Math.sqrt(n)), 2)) / 2) - - Math.floor((n - Math.pow(Math.floor(Math.sqrt(n)), 2)) / 2) === + Math.floor((n - Math.pow(Math.floor(Math.sqrt(n)), 2)) / 2) === 0; if (direction) { @@ -35,7 +35,26 @@ function getMapTileCoordinates(n) { } } } -export const generateMapHTML = function (gameConfig, total) { + +/** + * Returns a tile number from a tileset based on the cluster of files + * It currently uses total number of lines of code as a seed for the tile number + * + * @param {*} cluster an array of file objects tile represents + * @param {*} numberOfTileVariations the number of tile variations in the tileset + * + * @returns {int} a 1-based number of the tile + */ +function getTileNumber(cluster, numberOfTileVariations) { + const totalLinesInCluster = cluster.reduce( + (acc, [file]) => acc + file.Lines, + 0 + ); + + return (totalLinesInCluster % numberOfTileVariations) + 1; +} + +export const generateMapHTML = function (gameConfig, clusters) { // scale the image if total is too high const tileScale = 1; @@ -57,7 +76,7 @@ export const generateMapHTML = function (gameConfig, total) { const tiles = []; - for (let i = total; i >= 1; i--) { + for (let i = clusters.length; i >= 1; i--) { const blockCoordinates = getMapTileCoordinates(i); const isoX = @@ -76,7 +95,7 @@ export const generateMapHTML = function (gameConfig, total) { highestIsoY = isoY; } - const tileNumber = Math.floor(Math.random() * numberOfTileVariations) + 1; + const tileNumber = getTileNumber(clusters[i - 1], numberOfTileVariations); tiles.push({ tileNumber, isoX, isoY }); } diff --git a/templates/gitterra.yml b/templates/gitterra.yml index 1789878..d94b92c 100644 --- a/templates/gitterra.yml +++ b/templates/gitterra.yml @@ -11,12 +11,12 @@ GitTerra: # - find . -not -path './.git' -not -path './.git/*' - mkdir -p /tmp/gitterra/scc - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/gitterra/GitTerra.git /tmp/gitterra/gitterra + - (cd /tmp/gitterra/gitterra && npm install) - wget https://github.com/boyter/scc/releases/download/v3.2.0/scc_Linux_x86_64.tar.gz -O /tmp/gitterra/scc/scc.gz - tar -C /tmp/gitterra/scc -xvf /tmp/gitterra/scc/scc.gz - chmod +x /tmp/gitterra/scc/scc - - time /tmp/gitterra/scc/scc . --format json --output gitterra.json + - time /tmp/gitterra/scc/scc . --by-file --format json --output gitterra.json - node /tmp/gitterra/gitterra/src/generateMap.js .gitterra.config.js artifacts: paths: - index.html - - gitterra.json diff --git a/test/gitterra.json b/test/gitterra.json index b05f30d..f50e32d 100644 --- a/test/gitterra.json +++ b/test/gitterra.json @@ -1,67 +1,452 @@ [ { "Name": "JavaScript", - "Bytes": 7187, + "Bytes": 9244, "CodeBytes": 0, - "Lines": 259, - "Code": 201, - "Comment": 27, - "Blank": 31, - "Complexity": 12, - "Count": 5, + "Lines": 332, + "Code": 240, + "Comment": 48, + "Blank": 44, + "Complexity": 14, + "Count": 6, "WeightedComplexity": 0, - "Files": [] + "Files": [ + { + "Language": "JavaScript", + "PossibleLanguages": ["JavaScript"], + "Filename": "clusterize.js", + "Extension": "js", + "Location": "src/clusterize.js", + "Symlocation": "", + "Bytes": 503, + "Lines": 22, + "Code": 17, + "Comment": 0, + "Blank": 5, + "Complexity": 2, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "JavaScript", + "PossibleLanguages": ["JavaScript"], + "Filename": "terraPrime.js", + "Extension": "js", + "Location": "src/tiles/terraPrime.js", + "Symlocation": "", + "Bytes": 318, + "Lines": 12, + "Code": 11, + "Comment": 0, + "Blank": 1, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "JavaScript", + "PossibleLanguages": ["JavaScript"], + "Filename": "gameConfig.js", + "Extension": "js", + "Location": "src/gameConfig.js", + "Symlocation": "", + "Bytes": 126, + "Lines": 6, + "Code": 5, + "Comment": 0, + "Blank": 1, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "JavaScript", + "PossibleLanguages": ["JavaScript"], + "Filename": "generateMap.js", + "Extension": "js", + "Location": "src/generateMap.js", + "Symlocation": "", + "Bytes": 2471, + "Lines": 96, + "Code": 69, + "Comment": 12, + "Blank": 15, + "Complexity": 2, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "JavaScript", + "PossibleLanguages": ["JavaScript"], + "Filename": ".gitterra.config.js", + "Extension": "js", + "Location": "test/.gitterra.config.js", + "Symlocation": "", + "Bytes": 412, + "Lines": 16, + "Code": 4, + "Comment": 10, + "Blank": 2, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "JavaScript", + "PossibleLanguages": ["JavaScript"], + "Filename": "map.js", + "Extension": "js", + "Location": "src/map.js", + "Symlocation": "", + "Bytes": 5414, + "Lines": 180, + "Code": 134, + "Comment": 26, + "Blank": 20, + "Complexity": 10, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + } + ] }, { - "Name": "Markdown", - "Bytes": 4259, + "Name": "YAML", + "Bytes": 4312, "CodeBytes": 0, - "Lines": 77, - "Code": 49, - "Comment": 0, - "Blank": 28, + "Lines": 151, + "Code": 126, + "Comment": 10, + "Blank": 15, "Complexity": 0, - "Count": 3, + "Count": 5, "WeightedComplexity": 0, - "Files": [] + "Files": [ + { + "Language": "YAML", + "PossibleLanguages": ["YAML"], + "Filename": "action.yml", + "Extension": "yml", + "Location": "action.yml", + "Symlocation": "", + "Bytes": 1317, + "Lines": 46, + "Code": 39, + "Comment": 0, + "Blank": 7, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "YAML", + "PossibleLanguages": ["YAML"], + "Filename": "gitterra.yml", + "Extension": "yml", + "Location": ".github/workflows/gitterra.yml", + "Symlocation": "", + "Bytes": 1225, + "Lines": 45, + "Code": 38, + "Comment": 3, + "Blank": 4, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "YAML", + "PossibleLanguages": ["YAML"], + "Filename": "gitterra.yml", + "Extension": "yml", + "Location": "templates/gitterra.yml", + "Symlocation": "", + "Bytes": 777, + "Lines": 22, + "Code": 19, + "Comment": 2, + "Blank": 1, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "YAML", + "PossibleLanguages": ["YAML"], + "Filename": ".gitlab-ci.yml", + "Extension": "yml", + "Location": ".gitlab-ci.yml", + "Symlocation": "", + "Bytes": 541, + "Lines": 19, + "Code": 15, + "Comment": 2, + "Blank": 2, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "YAML", + "PossibleLanguages": ["YAML"], + "Filename": "pages.yml", + "Extension": "yml", + "Location": "templates/pages.yml", + "Symlocation": "", + "Bytes": 452, + "Lines": 19, + "Code": 15, + "Comment": 3, + "Blank": 1, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + } + ] }, { - "Name": "YAML", - "Bytes": 2663, + "Name": "JSON", + "Bytes": 1638719, "CodeBytes": 0, - "Lines": 88, - "Code": 73, - "Comment": 4, - "Blank": 11, + "Lines": 58709, + "Code": 58709, + "Comment": 0, + "Blank": 0, "Complexity": 0, - "Count": 3, + "Count": 4, "WeightedComplexity": 0, - "Files": [] + "Files": [ + { + "Language": "JSON", + "PossibleLanguages": ["JSON"], + "Filename": "package.json", + "Extension": "json", + "Location": "package.json", + "Symlocation": "", + "Bytes": 516, + "Lines": 24, + "Code": 24, + "Comment": 0, + "Blank": 0, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "JSON", + "PossibleLanguages": ["JSON"], + "Filename": "package-lock.json", + "Extension": "json", + "Location": "package-lock.json", + "Symlocation": "", + "Bytes": 15738, + "Lines": 447, + "Code": 447, + "Comment": 0, + "Blank": 0, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "JSON", + "PossibleLanguages": ["JSON"], + "Filename": "gitterra.json", + "Extension": "json", + "Location": "test/gitterra.json", + "Symlocation": "", + "Bytes": 0, + "Lines": 0, + "Code": 0, + "Comment": 0, + "Blank": 0, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "JSON", + "PossibleLanguages": ["JSON"], + "Filename": "gitterra_large.json", + "Extension": "json", + "Location": "test/gitterra_large.json", + "Symlocation": "", + "Bytes": 1622465, + "Lines": 58238, + "Code": 58238, + "Comment": 0, + "Blank": 0, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + } + ] }, { - "Name": "JSON", - "Bytes": 415, + "Name": "Markdown", + "Bytes": 8164, "CodeBytes": 0, - "Lines": 19, - "Code": 19, + "Lines": 181, + "Code": 127, "Comment": 0, - "Blank": 0, + "Blank": 54, "Complexity": 0, - "Count": 2, + "Count": 3, "WeightedComplexity": 0, - "Files": [] + "Files": [ + { + "Language": "Markdown", + "PossibleLanguages": ["Markdown"], + "Filename": "README.md", + "Extension": "md", + "Location": "README.md", + "Symlocation": "", + "Bytes": 6004, + "Lines": 165, + "Code": 118, + "Comment": 0, + "Blank": 47, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "Markdown", + "PossibleLanguages": ["Markdown"], + "Filename": "LICENSE.md", + "Extension": "md", + "Location": "LICENSE.md", + "Symlocation": "", + "Bytes": 1062, + "Lines": 7, + "Code": 4, + "Comment": 0, + "Blank": 3, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + }, + { + "Language": "Markdown", + "PossibleLanguages": ["Markdown"], + "Filename": "LICENSE.scc.md", + "Extension": "md", + "Location": "LICENSE.scc.md", + "Symlocation": "", + "Bytes": 1098, + "Lines": 9, + "Code": 5, + "Comment": 0, + "Blank": 4, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + } + ] }, { "Name": "gitignore", - "Bytes": 18, + "Bytes": 47, "CodeBytes": 0, - "Lines": 1, - "Code": 1, + "Lines": 3, + "Code": 3, "Comment": 0, "Blank": 0, "Complexity": 0, "Count": 1, "WeightedComplexity": 0, - "Files": [] + "Files": [ + { + "Language": "gitignore", + "PossibleLanguages": ["gitignore"], + "Filename": ".gitignore", + "Extension": ".gitignore", + "Location": ".gitignore", + "Symlocation": "", + "Bytes": 47, + "Lines": 3, + "Code": 3, + "Comment": 0, + "Blank": 0, + "Complexity": 0, + "WeightedComplexity": 0, + "Hash": null, + "Callback": null, + "Binary": false, + "Minified": false, + "Generated": false + } + ] } ]