Merge branch 'main' of https://github.com/Kugelschieber/breach into ui-gamestate

This commit is contained in:
2020-12-21 15:52:39 +01:00
4 changed files with 358 additions and 66 deletions

146
package-lock.json generated
View File

@@ -1233,6 +1233,11 @@
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
"dev": true "dev": true
}, },
"@types/seedrandom": {
"version": "2.4.28",
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.28.tgz",
"integrity": "sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA=="
},
"@types/serve-static": { "@types/serve-static": {
"version": "1.13.8", "version": "1.13.8",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.8.tgz", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.8.tgz",
@@ -1767,26 +1772,6 @@
} }
} }
}, },
"fork-ts-checker-webpack-plugin-v5": {
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
"dev": true,
"optional": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
}
},
"glob-parent": { "glob-parent": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
@@ -1888,18 +1873,6 @@
} }
} }
}, },
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"dev": true,
"optional": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
"slash": { "slash": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
@@ -2205,16 +2178,6 @@
} }
} }
}, },
"json5": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
},
"jsonfile": { "jsonfile": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
@@ -2224,18 +2187,6 @@
"graceful-fs": "^4.1.6" "graceful-fs": "^4.1.6"
} }
}, },
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"locate-path": { "locate-path": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -2391,18 +2342,6 @@
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true "dev": true
}, },
"vue-loader-v16": {
"version": "npm:vue-loader@16.1.1",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.1.tgz",
"integrity": "sha512-wz/+HFg/3SBayHWAlZXARcnDTl3VOChrfW9YnxvAweiuyKX/7IGx1ad/4yJHmwhgWlOVYMAbTiI7GV8G33PfGQ==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
}
},
"yallist": { "yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@@ -6935,6 +6874,40 @@
} }
} }
}, },
"fork-ts-checker-webpack-plugin-v5": {
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
"dev": true,
"optional": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
},
"dependencies": {
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"dev": true,
"optional": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
}
}
},
"form-data": { "form-data": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
@@ -12925,6 +12898,11 @@
} }
} }
}, },
"seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
},
"select-hose": { "select-hose": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@@ -15089,6 +15067,42 @@
} }
} }
}, },
"vue-loader-v16": {
"version": "npm:vue-loader@16.1.2",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz",
"integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
},
"dependencies": {
"json5": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
}
}
},
"vue-style-loader": { "vue-style-loader": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",

View File

@@ -22,6 +22,8 @@
"testURL": "http://localhost/" "testURL": "http://localhost/"
}, },
"dependencies": { "dependencies": {
"@types/seedrandom": "^2.4.28",
"seedrandom": "^3.0.5",
"vue": "^3.0.0" "vue": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -0,0 +1,24 @@
import "jest"
import seedrandom from "seedrandom";
import {generateGameConfig} from "./MatrixGeneration";
import GameConfiguration from "./GameConfiguration";
describe("MatrixGen", () => {
test("random generator works", () => {
const seededRNG = seedrandom("Testseed");
expect(seededRNG()).toEqual(0.026130760816951273);
expect(seededRNG()).toEqual(0.24232428305919648);
});
test("basic game config creation", () =>{
const gameConfig : GameConfiguration = generateGameConfig(1, "Testseed");
expect(gameConfig).not.toBeNull;
expect(gameConfig.matrix).not.toBeNull;
console.log(gameConfig.matrix);
expect(gameConfig.maxBufferLength).not.toBeNull;
expect(gameConfig.sequences).not.toBeNull;
console.log(gameConfig.sequences);
expect(gameConfig.timeout).not.toBeNull;
});
});

View File

@@ -0,0 +1,252 @@
import GameConfiguration from "./GameConfiguration";
import seedrandom from "seedrandom";
const minMatrixSize = 3;
const maxMatrixSize = 8;
const minNumberSequences = 2;
const maxNumberSequences = 7;
const minSequenceLength = 2;
const minBufferSize = 4;
const maxBufferSize = 8;
const stepsUntilMatrixSizeIncreased = 10;
const stepsUntilSequenceNumberIncreased = 3;
const defaultTimeout = 60_000;
//TODO see how many different values are there
//TODO see how the possible values are defined
const numberDifferentMatrixValues = 5;
const matrixValues = ["A0", "E9", "4C", "8B", "6F"];
const emptyMatrixValue = " ";
/**
* Generates a game configuration for the current level given the seed which is used
* for the PRNG.
*
* The generated game configuration, see {@link GameConfiguration}, contains
* a matrix with random values with a size which corresponds to the current level,
* the sequences which are generated randomly and have to be found in the matrix,
* the length of the buffer which contains the chosen values from the matrix,
* the timeout which is the time a user has to solve the current level.
*
* @param level The current game level (corresponds directly to the difficulty)
* @param seed The seed used to compute the random values of the marix and sequences
*/
export function generateGameConfig(level: number, seed: string): GameConfiguration {
if (level < 1) {
throw new RangeError("The level has to be at least 1.");
}
//the levels probably start at 1, however starting with 0 makes things smoother here
level--;
const seededRNG = seedrandom(seed);
const matrixSize: number = getMatrixSize(level);
const numberOfSequences: number = getNumberOfSequences(level);
const matrix: string[] = generateRandomMatrix(matrixSize, seededRNG);
const maxBufferLength: number = computeMaxBufferLength(level);
const sequences: string[][] = generateSequences(matrix, numberOfSequences, maxBufferLength, seededRNG);
const result: GameConfiguration = {
matrix: matrix,
sequences: sequences,
maxBufferLength: maxBufferLength,
timeout: defaultTimeout
};
return result;
}
function computeMaxBufferLength(level: number): number {
//TODO not sure what might be a good idea
//see getNumberOfSequences: every time the number of sequences is increased,
//the buffer size is set to the minimum
//if the number of sequences is not increased, the buffer length is increased
let result: number = minBufferSize;
if (matrixSizeWasIncreased(level)) {
//currently it was still possible to increase the matrix size every x level
const intervalLevel: number = level % stepsUntilMatrixSizeIncreased;
result += intervalLevel % stepsUntilSequenceNumberIncreased;
result = Math.min(result, maxBufferSize);
}
else {
//the matrix size could not be increased anymore, therefore the number of
//the maximum buffer length has to be changed differently
result = maxBufferSize;
}
return result;
}
function generateSequences(matrix: string[], numberOfSequences: number, maxBufferLength: number, seededRNG: seedrandom.prng): string[][] {
const result: string[][] = new Array(numberOfSequences);
for (let i = 0; i < numberOfSequences; i++) {
const currentSequenceLength: number = getRandomInt(minSequenceLength, maxBufferLength, seededRNG);
result[i] = new Array(currentSequenceLength);
fillSequence(result[i], matrix, seededRNG);
}
return result;
}
function fillSequence(sequence: string[], matrix: string[], seededRNG: seedrandom.prng): void {
const matrixSize = Math.sqrt(matrix.length);
const matrixCopy: string[] = Object.assign([], matrix);
const sequenceLength = sequence.length;
let currentLineNumber = 0;
let currentColumnNumber = 0;
let searchInLine = true;
//always start in the first line
for (let i = 0; i < sequenceLength; i++) {
let matrixIndex = -1;
if (searchInLine) {
while (matrixIndex == -1) {
currentColumnNumber = getRandomInt(0, matrixSize - 1, seededRNG);
matrixIndex = getMatrixIndex(matrixSize, currentLineNumber, currentColumnNumber);
if (matrixCopy[matrixIndex] == emptyMatrixValue) {
matrixIndex = -1;
}
if (i < sequenceLength - 1 && numberValuesLeftColumn(matrixCopy, currentColumnNumber) < 2) {
//there needs to be 2 values left in the chosen column
//one for the current sequence value and one for the next
matrixIndex = -1;
}
}
searchInLine = false;
}
else {
while (matrixIndex == -1) {
currentLineNumber = getRandomInt(0, matrixSize - 1, seededRNG);
matrixIndex = getMatrixIndex(matrixSize, currentLineNumber, currentColumnNumber);
if (matrixCopy[matrixIndex] == emptyMatrixValue) {
matrixIndex = -1;
}
//ensure the chosen column has values left which can be selected
if (i < sequenceLength - 1 && numberValuesLeftLine(matrixCopy, currentLineNumber) < 2) {
//there needs to be 2 values left in the chosen line
//one for the current sequence value and one for the next
matrixIndex = -1;
}
}
searchInLine = true;
}
matrixIndex = getMatrixIndex(matrixSize, currentLineNumber, currentColumnNumber);
sequence[i] = matrix[matrixIndex];
matrixCopy[matrixIndex] = emptyMatrixValue;
}
}
function numberValuesLeftColumn(matrixCopy: string[], columnNumber: number): number {
const matrixSize = Math.sqrt(matrixCopy.length);
let valuesLeft = 0;
for (let i = 0; i < matrixSize; i++) {
const matrixIndex = getMatrixIndex(matrixSize, i, columnNumber);
if (matrixCopy[matrixIndex] != emptyMatrixValue) {
valuesLeft++;
}
}
return valuesLeft;
}
function numberValuesLeftLine(matrixCopy: string[], lineNumber: number): number {
const matrixSize = Math.sqrt(matrixCopy.length);
let valuesLeft = 0;
for (let i = 0; i < matrixSize; i++) {
const matrixIndex = getMatrixIndex(matrixSize, lineNumber, i);
if (matrixCopy[matrixIndex] != emptyMatrixValue) {
valuesLeft++;
}
}
return valuesLeft;
}
function getMatrixIndex(matrixSize: number, lineNumber: number, columnNumber: number): number {
return (lineNumber * matrixSize) + columnNumber;
}
function generateRandomMatrix(matrixSize: number, seededRNG: seedrandom.prng): string[] {
const result: string[] = new Array(matrixSize * matrixSize);
for (let i = 0; i < (matrixSize * matrixSize); i++) {
result[i] = randomValue(seededRNG);
}
return result;
}
/**
* Returns a random integer in the given interval. Both interval limits are inclusive
*
* @param min The minimum number to be returned (inclusive)
* @param max The maximum number to be returned (inclusive)
* @param seededRNG The PRNG given by seedrandom
*/
function getRandomInt(min: number, max: number, seededRNG: seedrandom.prng): number {
//taken from https://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(seededRNG() * (max - min + 1)) + min;
}
function randomValue(seededRNG: seedrandom.prng): string {
return matrixValues[getRandomInt(0, numberDifferentMatrixValues - 1, seededRNG)];
}
/**
* Increase the matrix size every x levels up to a maximum size
*
* x is defined in StepsUntilMatrixSizeIncreased
*
* @param level The current game level (corresponds directly to the difficulty)
*/
//TODO see if this is a clever way to increase the matrix size
function getMatrixSize(level: number): number {
const size: number = minMatrixSize + Math.floor(level / stepsUntilMatrixSizeIncreased);
return Math.min(size, maxMatrixSize);
}
/**
* Computes the number of sequences for the given level.
*
* @param level The current game level (corresponds directly to the difficulty)
*/
//TODO It would suffice to give the level as param, as the matrix size can be computed
//What is the best practice in this context?
function getNumberOfSequences(level: number): number {
//TODO not sure what might be a good idea
let result: number = minNumberSequences;
if (matrixSizeWasIncreased(level)) {
//currently it was still possible to increase the matrix size every x level
const intervalLevel: number = level % stepsUntilMatrixSizeIncreased;
result += Math.floor(intervalLevel / stepsUntilSequenceNumberIncreased);
result = Math.min(result, maxNumberSequences);
}
else {
//the matrix size could not be increased anymore, therefore the number of
//sequences has to be changed differently
result = maxNumberSequences;
}
return result;
}
/**
* Currently it was still possible to increase the matrix size every x level
*
* @param level The current game level (corresponds directly to the difficulty)
*/
function matrixSizeWasIncreased(level: number): boolean {
return level < maxMatrixSize * (stepsUntilMatrixSizeIncreased + 1);
}