From e7983f65d687c6efc04774ca4dda2dad0278f731 Mon Sep 17 00:00:00 2001
From: Griefed <griefed@griefed.de>
Date: Tue, 28 Sep 2021 20:01:46 +0200
Subject: [PATCH] feat: Generate random maps for a given number-seed.

---
 package-lock.json   |  22 ++++++
 package.json        |   2 +
 src/pages/Index.vue |  69 ++++++++++++------
 src/store/index.js  | 170 +++++++++++++++++++++++++++++++++++---------
 4 files changed, 209 insertions(+), 54 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 3901ee9..b00ff74 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,8 @@
       "dependencies": {
         "@quasar/extras": "^1.11.1",
         "core-js": "^3.18.1",
+        "mersenne-twister": "^1.1.0",
+        "noise-map": "^1.1.0",
         "quasar": "^2.1.0"
       },
       "devDependencies": {
@@ -8480,6 +8482,11 @@
         "node": ">= 8"
       }
     },
+    "node_modules/mersenne-twister": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/mersenne-twister/-/mersenne-twister-1.1.0.tgz",
+      "integrity": "sha1-+RZhjuQ9cXnvz2Qb7EUx65Zwl4o="
+    },
     "node_modules/methods": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -8763,6 +8770,11 @@
       "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==",
       "dev": true
     },
+    "node_modules/noise-map": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/noise-map/-/noise-map-1.1.0.tgz",
+      "integrity": "sha512-eF6DGJuJF2Hj56u7Tjkeh6z/JtQQ76tBPjt8/N2uoL0gJzUMfEWavzW9v4lJTlAxNOnYsTGCkT7CoXNe/TcWHQ=="
+    },
     "node_modules/normalize-package-data": {
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@@ -19870,6 +19882,11 @@
       "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
       "dev": true
     },
+    "mersenne-twister": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/mersenne-twister/-/mersenne-twister-1.1.0.tgz",
+      "integrity": "sha1-+RZhjuQ9cXnvz2Qb7EUx65Zwl4o="
+    },
     "methods": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -20076,6 +20093,11 @@
       "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==",
       "dev": true
     },
+    "noise-map": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/noise-map/-/noise-map-1.1.0.tgz",
+      "integrity": "sha512-eF6DGJuJF2Hj56u7Tjkeh6z/JtQQ76tBPjt8/N2uoL0gJzUMfEWavzW9v4lJTlAxNOnYsTGCkT7CoXNe/TcWHQ=="
+    },
     "normalize-package-data": {
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
diff --git a/package.json b/package.json
index f76aaf6..76513fa 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,8 @@
   "dependencies": {
     "@quasar/extras": "^1.11.1",
     "core-js": "^3.18.1",
+    "mersenne-twister": "^1.1.0",
+    "noise-map": "^1.1.0",
     "quasar": "^2.1.0"
   },
   "devDependencies": {
diff --git a/src/pages/Index.vue b/src/pages/Index.vue
index 21e72f6..48f5d67 100644
--- a/src/pages/Index.vue
+++ b/src/pages/Index.vue
@@ -1,4 +1,5 @@
 <template>
+
   <span v-if="store.state.lttMap">
     <span v-for="(xRow, index) in store.state.lttMap" :key="index">
       <div class="row no-wrap"
@@ -19,11 +20,12 @@
       </div>
     </span>
   </span>
+
   <span v-else>
     <div class="row no-wrap q-pa-md absolute-center">
       <q-card>
         <q-card-section>
-            <div class="column" style="width: 600px;">
+          <div class="column" style="width: 600px;">
               <div class="text-h6 q-mb-md text-black">New Map Dimensions</div>
               <q-item>
                 <q-item-section avatar>
@@ -42,31 +44,49 @@
                   <q-slider v-model="store.state.mapSizeY" :min="1" :max="101" label color="secondary" :step="1" label-always/>
                 </q-item-section>
               </q-item>
-              <q-btn class="q-mr-xs" color="secondary" label="Generate New Map" @click='createMap()'>
+
+              <q-btn class="q-mr-xs" color="secondary" label="Generate Empty Map" @click='createMap()'>
                 <q-tooltip :disable="$q.platform.is.mobile">
-                  Generate new map with size set above
+                  Generate empty map with size set above
                 </q-tooltip>
               </q-btn>
-            </div>
-          </q-card-section>
-
-          <q-card-section>
-            <div class="column">
-              <div class="text-h6 q-mb-md text-black">Load Existing Map</div>
-              <q-input
-                v-model="mapString"
-                filled
-                placeholder="Paste Map Data"
-                type="textarea"
-                input-class="pastCodeArea"
-              ></q-input>
-              <q-btn class="q-mr-xs" color="secondary" label="Load Map From Filedata" @click='loadMapData()'>
+
+          </div>
+        </q-card-section>
+
+        <q-card-section>
+          <div class="column" style="width: 600px;">
+            <q-input color="black" filled v-model="store.state.seed" label="Seed" type="number" maxlength="15">
+              <template v-if="store.state.seed" v-slot:append>
+                <q-icon name="cancel" @click.stop="seed = null" class="cursor-pointer" />
+              </template>
+            </q-input>
+            <q-btn class="q-mr-xs" color="secondary" label="Generate Random Map" @click='createRandomMap()'>
                 <q-tooltip :disable="$q.platform.is.mobile">
-                  Load Map From Data
+                  Generate random map with size set above
                 </q-tooltip>
-              </q-btn>
-            </div>
+            </q-btn>
+          </div>
+        </q-card-section>
+
+        <q-card-section>
+          <div class="column">
+            <div class="text-h6 q-mb-md text-black">Load Existing Map</div>
+            <q-input
+              v-model="mapString"
+              filled
+              placeholder="Paste Map Data"
+              type="textarea"
+              input-class="pastCodeArea"
+            ></q-input>
+            <q-btn class="q-mr-xs" color="secondary" label="Load Map From Filedata" @click='loadMapData()'>
+              <q-tooltip :disable="$q.platform.is.mobile">
+                Load Map From Data
+              </q-tooltip>
+            </q-btn>
+          </div>
         </q-card-section>
+
       </q-card>
     </div>
   </span>
@@ -82,20 +102,25 @@ export default defineComponent({
 
       const store = inject('store');
 
-      var mapString = ref('')
+      var mapString = ref('');
 
       const loadMapData = function() {
         store.methods.loadMap(JSON.parse(mapString.value));
       };
 
-      const createMap= function(){
+      const createMap = function() {
         store.methods.generateMap();
       };
 
+      const createRandomMap = function() {
+        store.methods.generateRandomMap();
+      };
+
       return {
         mapString,
         store,
         createMap,
+        createRandomMap,
         loadMapData
       }
     },
diff --git a/src/store/index.js b/src/store/index.js
index 28758a8..1fd3f5b 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,4 +1,8 @@
-import {reactive} from 'vue';
+import {reactive, ref} from 'vue';
+import NoiseMap from 'noise-map';
+import MersenneTwister from 'mersenne-twister';
+
+const seedGenerator = new MersenneTwister();
 
 const state = reactive({
   maxX: 0,
@@ -14,10 +18,43 @@ const state = reactive({
   mapID: 'e1-m.lttmm',
   mapName: '',
   lttMap: null,
-})
+  seed: ref(seedGenerator.random_int31())
+});
+
+const reticulating = ["Adding","Hidden","Agendas","Adjusting","Bell","Curves","Aesthesizing","Industrial","Areas","Aligning","Covariance","Matrices",
+  "Applying","Feng","Shui","Shaders","Applying","Theatre","Soda","Layer","Asserting","Packed","Exemplars","Attempting","to","Lock","Back-Buffer",
+  "Binding","Sapling","Root","System","Breeding","Fauna","Building","Data","Trees","Bureacritizing","Bureaucracies","Calculating","Inverse","Probability","Matrices",
+  "Calculating","Llama","Expectoration","Trajectory","Calibrating","Blue","Skies","Charging","Ozone","Layer","Coalescing","Cloud","Formations",
+  "Cohorting","Exemplars","Collecting","Meteor","Particles","Compounding","Inert","Tessellations","Compressing","Fish","Files",
+  "Computing","Optimal","Bin","Packing","Concatenating","Sub-Contractors","Containing","Existential","Buffer","Debarking","Ark","Ramp",
+  "Debunching","Unionized","Commercial","Services","Deciding","What","Message","to","Display","Next","Decomposing","Singular","Values",
+  "Decrementing","Tectonic","Plates","Deleting","Ferry","Routes","Depixelating","Inner","Mountain","Surface","Back","Faces","Depositing","Slush","Funds",
+  "Destabilizing","Economic","Indicators","Determining","Width","of","Blast","Fronts","Deunionizing","Bulldozers","Dicing","Models",
+  "Diluting","Livestock","Nutrition","Variables","Downloading","Satellite","Terrain","Data","Exposing","Flash","Variables","to","Streak","System",
+  "Extracting","Resources","Factoring","Pay","Scale","Fixing","Election","Outcome","Matrix","Flood-Filling","Ground","Water","Flushing","Pipe","Network",
+  "Gathering","Particle","Sources","Generating","Jobs","Gesticulating","Mimes","Graphing","Whale","Migration","Hiding","Willio","Webnet","Mask",
+  "Implementing","Impeachment","Routine","Increasing","Accuracy","of","RCI","Simulators","Increasing","Magmafacation","Initializing","My","Sim","Tracking","Mechanism",
+  "Initializing","Rhinoceros","Breeding","Timetable","Initializing","Robotic","Click-Path","AI","Inserting","Sublimated","Messages",
+  "Integrating","Curves","Integrating","Illumination","Form","Factors","Integrating","Population","Graphs","Iterating","Cellular","Automata",
+  "Lecturing","Errant","Subsystems","Mixing","Genetic","Pool","Modeling","Object","Components","Mopping","Occupant","Leaks","Normalizing","Power",
+  "Obfuscating","Quigley","Matrix","Overconstraining","Dirty","Industry","Calculations","Partitioning","City","Grid","Singularities",
+  "Perturbing","Matrices","Pixalating","Nude","Patch","Polishing","Water","Highlights","Populating","Lot","Templates","Preparing","Sprites","for","Random","Walks",
+  "Prioritizing","Landmarks","Projecting","Law","Enforcement","Pastry","Intake","Realigning","Alternate","Time","Frames","Reconfiguring","User","Mental","Processes",
+  "Relaxing","Splines","Removing","Road","Network","Speed","Bumps","Removing","Texture","Gradients","Removing","Vehicle","Avoidance","Behavior",
+  "Resolving","GUID","Conflict","Retracting","Phong","Shader","Retrieving","from","Back","Store","Reverse","Engineering","Image","Consultant",
+  "Routing","Neural","Network","Infanstructure","Scattering","Rhino","Food","Sources","Scrubbing","Terrain","Searching","for","Llamas",
+  "Seeding","Architecture","Simulation","Parameters","Sequencing","Particles","Setting","Advisor","Moods","Setting","Inner","Deity","Indicators",
+  "Setting","Universal","Physical","Constants","Sonically","Enhancing","Occupant-Free","Timber","Speculating","Stock","Market","Indices",
+  "Splatting","Transforms","Stratifying","Ground","Layers","Sub-Sampling","Water","Data","Synthesizing","Gravity","Synthesizing","Wavelets",
+  "Time-Compressing","Simulator","Clock","Unable","to","Reveal","Current","Activity","Weathering","Buildings","Zeroing","Crime","Network",
+  "Reticulating","Splines"];
 
 const methods = {
 
+  /**
+   * Set values back to default for the generation of a new map.
+   * @author Trungel
+   */
   reset() {
     state.mapSizeX = 1;
     state.mapSizeY = 1;
@@ -28,8 +65,14 @@ const methods = {
     state.mapID = 'e1-m.lttmm';
     state.mapName = '';
     state.lttMap = null;
+    state.seed = ref(seedGenerator.random_int31());
   },
 
+  /**
+   * Get all information for the map and export it as JSON.
+   * @author Trungel
+   * @returns {{initialViewRadiusX: (number|*), tileSet, initialVieCenterX: (number|*), initialViewRadiusY: (number|*), mapID: (string|*), mapName: (string|*), initialViewCenterY: (number|*)}} The JSON containing the map.
+   */
   exportMap() {
     return{
       mapID: state.mapID,
@@ -41,6 +84,11 @@ const methods = {
       tileSet: state.lttMap.flat()}
   },
 
+  /**
+   * Load an existing map from JSON entered by a user.
+   * @author Trungel
+   * @param mapData The JSON containing the map to load.
+   */
   loadMap(mapData) {
     let tileSetData = this.getMultDimArrayFromFlattendArray(mapData.tileSet);
 
@@ -59,6 +107,12 @@ const methods = {
     state.centerY = mapData.initialViewCenterY;
   },
 
+  /**
+   * Parse the tileSet of the passed map and retrieve information about said map.
+   * @author Trungel
+   * @param arr The tileSet of the map to load.
+   * @returns {{mapSizeY: number, minY, mapSizeX: number, minX, maxY, maxX, map: *[]}} JSON containing information about our map.
+   */
   getMultDimArrayFromFlattendArray(arr){
     let maxX,minX,maxY,minY, mapSizeX, mapSizeY;
     let sortArr = arr.sort((a,b)=>{
@@ -87,44 +141,31 @@ const methods = {
     return {mapSizeX, mapSizeY,maxX, minX,maxY, minY, map};
   },
 
-
+  /**
+   * Generate a random name for the new map.
+   * @author Griefed
+   * @returns {string} Returns a combination of three random words from {@link reticulating}.
+   */
   getMapName() {
-    const reticulating = ["Adding","Hidden","Agendas","Adjusting","Bell","Curves","Aesthesizing","Industrial","Areas","Aligning","Covariance","Matrices",
-      "Applying","Feng","Shui","Shaders","Applying","Theatre","Soda","Layer","Asserting","Packed","Exemplars","Attempting","to","Lock","Back-Buffer",
-      "Binding","Sapling","Root","System","Breeding","Fauna","Building","Data","Trees","Bureacritizing","Bureaucracies","Calculating","Inverse","Probability","Matrices",
-      "Calculating","Llama","Expectoration","Trajectory","Calibrating","Blue","Skies","Charging","Ozone","Layer","Coalescing","Cloud","Formations",
-      "Cohorting","Exemplars","Collecting","Meteor","Particles","Compounding","Inert","Tessellations","Compressing","Fish","Files",
-      "Computing","Optimal","Bin","Packing","Concatenating","Sub-Contractors","Containing","Existential","Buffer","Debarking","Ark","Ramp",
-      "Debunching","Unionized","Commercial","Services","Deciding","What","Message","to","Display","Next","Decomposing","Singular","Values",
-      "Decrementing","Tectonic","Plates","Deleting","Ferry","Routes","Depixelating","Inner","Mountain","Surface","Back","Faces","Depositing","Slush","Funds",
-      "Destabilizing","Economic","Indicators","Determining","Width","of","Blast","Fronts","Deunionizing","Bulldozers","Dicing","Models",
-      "Diluting","Livestock","Nutrition","Variables","Downloading","Satellite","Terrain","Data","Exposing","Flash","Variables","to","Streak","System",
-      "Extracting","Resources","Factoring","Pay","Scale","Fixing","Election","Outcome","Matrix","Flood-Filling","Ground","Water","Flushing","Pipe","Network",
-      "Gathering","Particle","Sources","Generating","Jobs","Gesticulating","Mimes","Graphing","Whale","Migration","Hiding","Willio","Webnet","Mask",
-      "Implementing","Impeachment","Routine","Increasing","Accuracy","of","RCI","Simulators","Increasing","Magmafacation","Initializing","My","Sim","Tracking","Mechanism",
-      "Initializing","Rhinoceros","Breeding","Timetable","Initializing","Robotic","Click-Path","AI","Inserting","Sublimated","Messages",
-      "Integrating","Curves","Integrating","Illumination","Form","Factors","Integrating","Population","Graphs","Iterating","Cellular","Automata",
-      "Lecturing","Errant","Subsystems","Mixing","Genetic","Pool","Modeling","Object","Components","Mopping","Occupant","Leaks","Normalizing","Power",
-      "Obfuscating","Quigley","Matrix","Overconstraining","Dirty","Industry","Calculations","Partitioning","City","Grid","Singularities",
-      "Perturbing","Matrices","Pixalating","Nude","Patch","Polishing","Water","Highlights","Populating","Lot","Templates","Preparing","Sprites","for","Random","Walks",
-      "Prioritizing","Landmarks","Projecting","Law","Enforcement","Pastry","Intake","Realigning","Alternate","Time","Frames","Reconfiguring","User","Mental","Processes",
-      "Relaxing","Splines","Removing","Road","Network","Speed","Bumps","Removing","Texture","Gradients","Removing","Vehicle","Avoidance","Behavior",
-      "Resolving","GUID","Conflict","Retracting","Phong","Shader","Retrieving","from","Back","Store","Reverse","Engineering","Image","Consultant",
-      "Routing","Neural","Network","Infanstructure","Scattering","Rhino","Food","Sources","Scrubbing","Terrain","Searching","for","Llamas",
-      "Seeding","Architecture","Simulation","Parameters","Sequencing","Particles","Setting","Advisor","Moods","Setting","Inner","Deity","Indicators",
-      "Setting","Universal","Physical","Constants","Sonically","Enhancing","Occupant-Free","Timber","Speculating","Stock","Market","Indices",
-      "Splatting","Transforms","Stratifying","Ground","Layers","Sub-Sampling","Water","Data","Synthesizing","Gravity","Synthesizing","Wavelets",
-      "Time-Compressing","Simulator","Clock","Unable","to","Reveal","Current","Activity","Weathering","Buildings","Zeroing","Crime","Network",
-      "Reticulating","Splines"];
     return reticulating[ Math.floor( Math.random() * reticulating.length) - 1] + " "
             + reticulating[Math.floor(Math.random() * reticulating.length) - 1] + " "
             + reticulating[Math.floor(Math.random() * reticulating.length) - 1];
   },
 
+  /**
+   * Return the mapID for the new map.
+   * @author Griefed
+   * @returns {string}
+   */
   getMapId() {
     return 'e1-m.lttmm';
   },
 
+  /**
+   * Generate a new map with a given size, mapID and mapName.
+   * @author Trungel
+   * @author Griefed
+   */
   generateMap() {
     let defaultTile = "grass";
     let map = [];
@@ -152,11 +193,76 @@ const methods = {
     }
     state.lttMap = map;
     state.mapName = this.getMapName();
+  },
+
+  /**
+   * Generate a random map with the given size, mapID and mapName based on a seed.
+   * @author Griefed
+   */
+  generateRandomMap() {
+    let defaultTile = "grass";
+    let map = [];
+
+    const mapGenerator = new NoiseMap.MapGenerator(state.seed);
+
+    state.minX = -Math.floor(state.mapSizeX/2);
+    if(state.mapSizeX%2){
+      state.maxX = Math.floor(state.mapSizeX/2);
+    }else{
+      state.maxX = Math.floor(state.mapSizeX/2)+1;
+    }
+
+    state.minY = -Math.floor(state.mapSizeY/2);
+    if(state.mapSizeY%2){
+      state.maxY = Math.floor(state.mapSizeY/2);
+    }else{
+      state.maxY = Math.floor(state.mapSizeY/2)+1;
+    }
+    const heightmap = mapGenerator.createMap(state.mapSizeX+2, state.mapSizeY+2, {type: 'perlit'});
+    heightmap.stepValues(5);
+
+    for(let m = state.maxY; m >= state.minY; m--){
+      let xArr = [];
+      for(let i = state.minX; i <= state.maxX; i++){
+        xArr.push({ x: i, y: m, z: 0, typeId: this.getTileTypeFromHeightmap(heightmap, i+(state.mapSizeX/2), m+(state.mapSizeY/2)) });
+      }
+      map.push(xArr);
+    }
+    state.lttMap = map;
+    state.mapName = this.getMapName();
+  },
+
+  /**
+   *
+   * @author Griefed
+   * @param heightmap A heightmap object generated by {@link NoiseMap}
+   * @param x The x coordinate of which to get the value
+   * @param y The y coordinate of which to get the value
+   * @returns {string} The value at position x and y.
+   */
+  getTileTypeFromHeightmap(heightmap, x, y) {
+    let value = Math.floor(heightmap.get(x, y)*100);
+    switch(value) {
+      case 100:
+        return "mountain";
+      case 80:
+        return "ice";
+      case 60:
+        return "barren";
+      case 40:
+        return "desert";
+      case 20:
+        return "grass";
+      case 0:
+        return "water";
+      default:
+        return "grass";
+    }
   }
 
-}
+};
 
 export default {
   state,
   methods
-}
+};
-- 
GitLab