Merge branch 'dev' of github.com:danielyxie/bitburner into improvement/stats-augs-ui

This commit is contained in:
nickofolas 2022-04-07 23:55:46 -05:00
commit fbf9bc521f
14 changed files with 477 additions and 34 deletions

28
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -196,6 +196,23 @@ The list contains the name of (i.e. the value returned by
| | | |
| | | Determine how many unique paths there are from start to finish. |
+------------------------------------+------------------------------------------------------------------------------------------+
| Shortest Path in a Grid | | You are given a 2D array of numbers (array of array of numbers) representing |
| | | a grid. The 2D array contains 1's and 0's, where 1 represents an obstacle and |
| | | 0 represents a free space. |
| | | |
| | | Assume you are initially positioned in top-left corner of that grid and that you |
| | | are trying to reach the bottom-right corner. In each step, you may move to the up, |
| | | down, left or right. Furthermore, you cannot move onto spaces which have obstacles. |
| | | |
| | | Determine if paths exist from start to destination, and find the shortest one. |
| | | |
| | | Examples: |
| | | [[0,1,0,0,0], |
| | | [0,0,0,1,0]] -> "DRRURRD" |
| | | [[0,1], |
| | | [1,0]] -> "" |
| | | |
+------------------------------------+------------------------------------------------------------------------------------------+
| Sanitize Parentheses in Expression | | Given a string with parentheses and letters, remove the minimum number of invalid |
| | | parentheses in order to validate the string. If there are multiple minimal ways |
| | | to validate the string, provide all of the possible results. |

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -2,7 +2,6 @@
import * as React from "react";
import { IMap } from "../types";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { Faction } from "../Faction/Faction";
import { Factions } from "../Faction/Factions";
import { numeralWrapper } from "../ui/numeralFormat";

@ -154,8 +154,8 @@ function applyAugmentation(aug: IPlayerOwnedAugmentation, reapply = false): void
}
}
function installAugmentations(): boolean {
if (Player.queuedAugmentations.length == 0) {
function installAugmentations(force?: boolean): boolean {
if (Player.queuedAugmentations.length == 0 && !force) {
dialogBoxCreate("You have not purchased any Augmentations to install!");
return false;
}
@ -185,12 +185,14 @@ function installAugmentations(): boolean {
augmentationList += aug.name + level + "<br>";
}
Player.queuedAugmentations = [];
dialogBoxCreate(
"You slowly drift to sleep as scientists put you under in order " +
"to install the following Augmentations:<br>" +
augmentationList +
"<br>You wake up in your home...you feel different...",
);
if (!force) {
dialogBoxCreate(
"You slowly drift to sleep as scientists put you under in order " +
"to install the following Augmentations:<br>" +
augmentationList +
"<br>You wake up in your home...you feel different...",
);
}
prestigeAugmentation();
return true;
}

@ -203,7 +203,7 @@ export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Factio
// Remove special augs
augs = augs.filter((a) => !a.isSpecial);
const blacklist: string[] = [AugmentationNames.NeuroFluxGovernor];
const blacklist: string[] = [AugmentationNames.NeuroFluxGovernor, AugmentationNames.CongruityImplant];
if (player.bitNodeN !== 2) {
// TRP is not available outside of BN2 for Gangs

@ -225,7 +225,6 @@ export class Gang implements IGang {
if (AllGangs[otherGang].territory <= 0) return;
const territoryGain = calculateTerritoryGain(thisGang, otherGang);
AllGangs[thisGang].territory += territoryGain;
if (AllGangs[thisGang].territory > 0.999) AllGangs[thisGang].territory = 1;
AllGangs[otherGang].territory -= territoryGain;
if (thisGang === gangName) {
this.clash(true); // Player won
@ -239,9 +238,7 @@ export class Gang implements IGang {
if (AllGangs[thisGang].territory <= 0) return;
const territoryGain = calculateTerritoryGain(otherGang, thisGang);
AllGangs[thisGang].territory -= territoryGain;
if (AllGangs[otherGang].territory < 0.001) AllGangs[otherGang].territory = 0;
AllGangs[otherGang].territory += territoryGain;
if (AllGangs[otherGang].territory > 0.999) AllGangs[otherGang].territory = 1;
if (thisGang === gangName) {
this.clash(false); // Player lost
} else if (otherGang === gangName) {
@ -251,6 +248,11 @@ export class Gang implements IGang {
AllGangs[thisGang].power *= 1 / 1.01;
}
}
const total = Object.values(AllGangs)
.map((g) => g.territory)
.reduce((p, c) => p + c, 0);
Object.values(AllGangs).forEach((g) => (g.territory /= total));
}
}
}

@ -6,7 +6,6 @@ import { startWorkerScript } from "../NetscriptWorker";
import { Augmentation } from "../Augmentation/Augmentation";
import { Augmentations } from "../Augmentation/Augmentations";
import { augmentationExists, installAugmentations } from "../Augmentation/AugmentationHelpers";
import { prestigeAugmentation } from "../Prestige";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { killWorkerScript } from "../Netscript/killWorkerScript";
import { CONSTANTS } from "../Constants";
@ -212,7 +211,7 @@ export function NetscriptSingularity(
workerScript.log("softReset", () => "Soft resetting. This will cause this script to be killed");
setTimeout(() => {
prestigeAugmentation();
installAugmentations(true);
runAfterReset(cbScript);
}, 0);

@ -1,5 +1,7 @@
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { MinHeap } from "../utils/Heap";
import { HammingEncode, HammingDecode } from "../utils/HammingCodeTools";
/* tslint:disable:completed-docs no-magic-numbers arrow-return-shorthand */
/* Function that generates a valid 'data' for a contract type */
@ -794,6 +796,140 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return obstacleGrid[obstacleGrid.length - 1][obstacleGrid[0].length - 1] === parseInt(ans);
},
},
{
name: "Shortest Path in a Grid",
desc: (data: number[][]): string => {
return [
"You are located in the top-left corner of the following grid:\n\n",
`&nbsp;&nbsp;[${data.map((line) => "[" + line + "]").join(",\n&nbsp;&nbsp;&nbsp;")}]\n\n`,
"You are trying to find the shortest path to the bottom-right corner of the grid,",
"but there are obstacles on the grid that you cannot move onto.",
"These obstacles are denoted by '1', while empty spaces are denoted by 0.\n\n",
"Determine the shortest path from start to finish, if one exists.",
"The answer should be given as a string of UDLR characters, indicating the moves along the path\n\n",
"NOTE: If there are multiple equally short paths, any of them is accepted as answer.",
"If there is no path, the answer should be an empty string.\n",
"NOTE: The data returned for this contract is an 2D array of numbers representing the grid.\n\n",
"Examples:\n\n",
"&nbsp;&nbsp;&nbsp;&nbsp;[[0,1,0,0,0],\n",
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[0,0,0,1,0]]\n",
"\n",
"Answer: 'DRRURRD'\n\n",
"&nbsp;&nbsp;&nbsp;&nbsp;[[0,1],\n",
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[1,0]]\n",
"\n",
"Answer: ''\n\n",
].join(" ");
},
difficulty: 7,
numTries: 10,
gen: (): number[][] => {
const height = getRandomInt(6, 12);
const width = getRandomInt(6, 12);
const dstY = height - 1;
const dstX = width - 1;
const minPathLength = dstY + dstX; // Math.abs(dstY - srcY) + Math.abs(dstX - srcX)
const grid: number[][] = new Array(height);
for (let y = 0; y < height; y++) grid[y] = new Array(width).fill(0);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
if (y == 0 && x == 0) continue; // Don't block start
if (y == dstY && x == dstX) continue; // Don't block destination
// Generate more obstacles the farther a position is from start and destination.
// Raw distance factor peaks at 50% at half-way mark. Rescale to 40% max.
// Obstacle chance range of [15%, 40%] produces ~78% solvable puzzles
const distanceFactor = (Math.min(y + x, dstY - y + dstX - x) / minPathLength) * 0.8;
if (Math.random() < Math.max(0.15, distanceFactor)) grid[y][x] = 1;
}
}
return grid;
},
solver: (data: number[][], ans: string): boolean => {
const width = data[0].length;
const height = data.length;
const dstY = height - 1;
const dstX = width - 1;
const distance: [number][] = new Array(height);
//const prev: [[number, number] | undefined][] = new Array(height);
const queue = new MinHeap<[number, number]>();
for (let y = 0; y < height; y++) {
distance[y] = new Array(width).fill(Infinity) as [number];
//prev[y] = new Array(width).fill(undefined) as [undefined];
}
function validPosition(y: number, x: number): boolean {
return y >= 0 && y < height && x >= 0 && x < width && data[y][x] == 0;
}
// List in-bounds and passable neighbors
function* neighbors(y: number, x: number): Generator<[number, number]> {
if (validPosition(y - 1, x)) yield [y - 1, x]; // Up
if (validPosition(y + 1, x)) yield [y + 1, x]; // Down
if (validPosition(y, x - 1)) yield [y, x - 1]; // Left
if (validPosition(y, x + 1)) yield [y, x + 1]; // Right
}
// Prepare starting point
distance[0][0] = 0;
queue.push([0, 0], 0);
// Take next-nearest position and expand potential paths from there
while (queue.size > 0) {
const [y, x] = queue.pop() as [number, number];
for (const [yN, xN] of neighbors(y, x)) {
const d = distance[y][x] + 1;
if (d < distance[yN][xN]) {
if (distance[yN][xN] == Infinity)
// Not reached previously
queue.push([yN, xN], d);
// Found a shorter path
else queue.changeWeight(([yQ, xQ]) => yQ == yN && xQ == xN, d);
//prev[yN][xN] = [y, x];
distance[yN][xN] = d;
}
}
}
// No path at all?
if (distance[dstY][dstX] == Infinity) return ans == "";
// There is a solution, require that the answer path is as short as the shortest
// path we found
if (ans.length > distance[dstY][dstX]) return false;
// Further verify that the answer path is a valid path
let ansX = 0;
let ansY = 0;
for (const direction of ans) {
switch (direction) {
case "U":
ansY -= 1;
break;
case "D":
ansY += 1;
break;
case "L":
ansX -= 1;
break;
case "R":
ansX += 1;
break;
default:
return false; // Invalid character
}
if (!validPosition(ansY, ansX)) return false;
}
// Path was valid, finally verify that the answer path brought us to the end coordinates
return ansY == dstY && ansX == dstX;
},
},
{
desc: (data: string): string => {
return [
@ -1008,4 +1144,62 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return true;
},
},
{
name: "HammingCodes: Integer to encoded Binary",
numTries: 10,
difficulty: 5,
desc: (n: number): string => {
return [
"You are given the following decimal Value: \n",
`${n} \n`,
"Convert it into a binary string and encode it as a 'Hamming-Code'. eg:\n ",
"Value 8 will result into binary '1000', which will be encoded",
"with the pattern 'pppdpddd', where p is a paritybit and d a databit,\n",
"or '10101' (Value 21) will result into (pppdpdddpd) '1111101011'.\n\n",
"NOTE: You need an parity Bit on Index 0 as an 'overall'-paritybit. \n",
"NOTE 2: You should watch the HammingCode-video from 3Blue1Brown, which explains the 'rule' of encoding,",
"including the first Index parity-bit mentioned on the first note.\n\n",
"Now the only one rule for this encoding:\n",
" It's not allowed to add additional leading '0's to the binary value\n",
"That means, the binary value has to be encoded as it is",
].join(" ");
},
gen: (): number => {
return getRandomInt(Math.pow(2, 4), Math.pow(2, getRandomInt(1, 57)));
},
solver: (data: number, ans: string): boolean => {
return ans === HammingEncode(data);
},
},
{
name: "HammingCodes: Encoded Binary to Integer",
difficulty: 8,
numTries: 10,
desc: (n: string): string => {
return [
"You are given the following encoded binary String: \n",
`'${n}' \n`,
"Treat it as a Hammingcode with 1 'possible' error on an random Index.\n",
"Find the 'possible' wrong bit, fix it and extract the decimal value, which is hidden inside the string.\n\n",
"Note: The length of the binary string is dynamic, but it's encoding/decoding is following Hammings 'rule'\n",
"Note 2: Index 0 is an 'overall' parity bit. Watch the Hammingcode-video from 3Blue1Brown for more information\n",
"Note 3: There's a ~55% chance for an altered Bit. So... MAYBE there is an altered Bit 😉\n",
"Extranote for automation: return the decimal value as a string",
].join(" ");
},
gen: (): string => {
const _alteredBit = Math.round(Math.random());
const _buildArray: Array<string> = HammingEncode(
getRandomInt(Math.pow(2, 4), Math.pow(2, getRandomInt(1, 57))),
).split("");
if (_alteredBit) {
const _randomIndex: number = getRandomInt(0, _buildArray.length - 1);
_buildArray[_randomIndex] = _buildArray[_randomIndex] == "0" ? "1" : "0";
}
return _buildArray.join("");
},
solver: (data: string, ans: string): boolean => {
return parseInt(ans, 10) === HammingDecode(data);
},
},
];

@ -310,7 +310,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
function softReset(): void {
dialogBoxCreate("Soft Reset!");
prestigeAugmentation();
installAugmentations(true);
resetErrorBoundary();
Router.toTerminal();
}

@ -0,0 +1,97 @@
// by Discord: H3draut3r#6722, feel free to ask me any questions. i probably don't know the answer 🤣
export function HammingEncode(value: number): string {
// encoding following Hammings rule
function HammingSumOfParity(_lengthOfDBits: number): number {
// will calculate the needed amount of parityBits 'without' the "overall"-Parity (that math took me 4 Days to get it working)
return _lengthOfDBits < 3 || _lengthOfDBits == 0 // oh and of course using ternary operators, it's a pretty neat function
? _lengthOfDBits == 0
? 0
: _lengthOfDBits + 1
: // the following math will only work, if the length is greater equal 3, otherwise it's "kind of" broken :D
Math.ceil(Math.log2(_lengthOfDBits * 2)) <=
Math.ceil(Math.log2(1 + _lengthOfDBits + Math.ceil(Math.log2(_lengthOfDBits))))
? Math.ceil(Math.log2(_lengthOfDBits) + 1)
: Math.ceil(Math.log2(_lengthOfDBits));
}
const _data = value.toString(2).split(""); // first, change into binary string, then create array with 1 bit per index
const _sumParity: number = HammingSumOfParity(_data.length); // get the sum of needed parity bits (for later use in encoding)
const count = (arr: Array<string>, val: string): number =>
arr.reduce((a: number, v: string) => (v === val ? a + 1 : a), 0);
// function count for specific entries in the array, for later use
const _build = ["x", "x", ..._data.splice(0, 1)]; // init the "pre-build"
for (let i = 2; i < _sumParity; i++) {
// add new paritybits and the corresponding data bits (pre-building array)
_build.push("x", ..._data.splice(0, Math.pow(2, i) - 1));
}
// now the "calculation"... get the paritybits ('x') working
for (const index of _build.reduce(function (a: Array<number>, e: string, i: number) {
if (e == "x") a.push(i);
return a;
}, [])) {
// that reduce will result in an array of index numbers where the "x" is placed
const _tempcount = index + 1; // set the "stepsize" for the parityBit
const _temparray = []; // temporary array to store the extracted bits
const _tempdata = [..._build]; // only work with a copy of the _build
while (_tempdata[index] !== undefined) {
// as long as there are bits on the starting index, do "cut"
const _temp: Array<string> = _tempdata.splice(index, _tempcount * 2); // cut stepsize*2 bits, then...
_temparray.push(..._temp.splice(0, _tempcount)); // ... cut the result again and keep the first half
}
_temparray.splice(0, 1); // remove first bit, which is the parity one
_build[index] = (count(_temparray, "1") % 2).toString(); // count with remainder of 2 and"toString" to store the parityBit
} // parity done, now the "overall"-parity is set
_build.unshift((count(_build, "1") % 2).toString()); // has to be done as last element
return _build.join(""); // return the _build as string
}
export function HammingDecode(_data: string): number {
//check for altered bit and decode
const _build = _data.split(""); // ye, an array for working, again
const _testArray = []; //for the "truthtable". if any is false, the data has an altered bit, will check for and fix it
const _sumParity = Math.ceil(Math.log2(_data.length)); // sum of parity for later use
const count = (arr: Array<string>, val: string): number =>
arr.reduce((a: number, v: string) => (v === val ? a + 1 : a), 0);
// the count.... again ;)
let _overallParity = _build.splice(0, 1).join(""); // store first index, for checking in next step and fix the _build properly later on
_testArray.push(_overallParity == (count(_build, "1") % 2).toString() ? true : false); // first check with the overall parity bit
for (let i = 0; i < _sumParity; i++) {
// for the rest of the remaining parity bits we also "check"
const _tempIndex = Math.pow(2, i) - 1; // get the parityBits Index
const _tempStep = _tempIndex + 1; // set the stepsize
const _tempData = [..._build]; // get a "copy" of the build-data for working
const _tempArray = []; // init empty array for "testing"
while (_tempData[_tempIndex] != undefined) {
// extract from the copied data until the "starting" index is undefined
const _temp = [..._tempData.splice(_tempIndex, _tempStep * 2)]; // extract 2*stepsize
_tempArray.push(..._temp.splice(0, _tempStep)); // and cut again for keeping first half
}
const _tempParity = _tempArray.shift(); // and again save the first index separated for checking with the rest of the data
_testArray.push(_tempParity == (count(_tempArray, "1") % 2).toString() ? true : false);
// is the _tempParity the calculated data? push answer into the 'truthtable'
}
let _fixIndex = 0; // init the "fixing" index and start with 0
for (let i = 1; i < _sumParity + 1; i++) {
// simple binary adding for every boolean in the _testArray, starting from 2nd index of it
_fixIndex += _testArray[i] ? 0 : Math.pow(2, i) / 2;
}
_build.unshift(_overallParity); // now we need the "overall" parity back in it's place
// try fix the actual encoded binary string if there is an error
if (_fixIndex > 0 && _testArray[0] == false) {
// if the overall is false and the sum of calculated values is greater equal 0, fix the corresponding hamming-bit
_build[_fixIndex] = _build[_fixIndex] == "0" ? "1" : "0";
} else if (_testArray[0] == false) {
// otherwise, if the the overall_parity is the only wrong, fix that one
_overallParity = _overallParity == "0" ? "1" : "0";
} else if (_testArray[0] == true && _testArray.some((truth) => truth == false)) {
return 0; // uhm, there's some strange going on... 2 bits are altered? How? This should not happen 👀
}
// oof.. halfway through... we fixed an possible altered bit, now "extract" the parity-bits from the _build
for (let i = _sumParity; i >= 0; i--) {
// start from the last parity down the 2nd index one
_build.splice(Math.pow(2, i), 1);
}
_build.splice(0, 1); // remove the overall parity bit and we have our binary value
return parseInt(_build.join(""), 2); // parse the integer with redux 2 and we're done!
}

133
src/utils/Heap.ts Normal file

@ -0,0 +1,133 @@
/** Binary heap. */
abstract class BinHeap<T> {
/**
* Heap data array consisting of [weight, payload] pairs, arranged by weight
* to satisfy heap condition.
*
* Encodes the binary tree by storing tree root at index 0 and
* left child of element i at `i * 2 + 1` and
* right child of element i at `i * 2 + 2`.
*/
protected data: [number, T][];
constructor() {
this.data = [];
}
/** Get number of elements in the heap. */
public get size(): number {
return this.data.length;
}
/** Add a new element to the heap. */
public push(value: T, weight: number): void {
const i = this.data.length;
this.data[i] = [weight, value];
this.heapifyUp(i);
}
/** Get the value of the root-most element of the heap, without changing the heap. */
public peek(): T | undefined {
if (this.data.length == 0) return undefined;
return this.data[0][1];
}
/** Remove the root-most element of the heap and return the removed element's value. */
public pop(): T | undefined {
if (this.data.length == 0) return undefined;
const value = this.data[0][1];
this.data[0] = this.data[this.data.length - 1];
this.data.length = this.data.length - 1;
this.heapifyDown(0);
return value;
}
/** Change the weight of an element in the heap. */
public changeWeight(predicate: (value: T) => boolean, weight: number): void {
// Find first element with matching value, if any
const i = this.data.findIndex((e) => predicate(e[1]));
if (i == -1) return;
// Update that element's weight
this.data[i][0] = weight;
// And re-heapify if needed
const p = Math.floor((i - 1) / 2);
if (!this.heapOrderABeforeB(this.data[p][0], this.data[i][0]))
// Needs to shift root-wards?
this.heapifyUp(i);
// Try shifting deeper
else this.heapifyDown(i);
}
/** Restore heap condition, starting at index i and traveling towards root. */
protected heapifyUp(i: number): void {
// Swap the new element up towards root until it reaches root position or
// settles under under a suitable parent
while (i > 0) {
const p = Math.floor((i - 1) / 2);
// Reached heap-ordered state already?
if (this.heapOrderABeforeB(this.data[p][0], this.data[i][0])) break;
// Swap
const tmp = this.data[p];
this.data[p] = this.data[i];
this.data[i] = tmp;
// And repeat at parent index
i = p;
}
}
/** Restore heap condition, starting at index i and traveling away from root. */
protected heapifyDown(i: number): void {
// Swap the shifted element down in the heap until it either reaches the
// bottom layer or is in correct order relative to it's children
while (i < this.data.length) {
const l = i * 2 + 1;
const r = i * 2 + 2;
let toSwap = i;
// Find which one of element i and it's children should be closest to root
if (l < this.data.length && this.heapOrderABeforeB(this.data[l][0], this.data[toSwap][0])) toSwap = l;
if (r < this.data.length && this.heapOrderABeforeB(this.data[r][0], this.data[toSwap][0])) toSwap = r;
// Already in order?
if (i == toSwap) break;
// Not in order. Swap child that should be closest to root up to 'i' and repeat
const tmp = this.data[toSwap];
this.data[toSwap] = this.data[i];
this.data[i] = tmp;
i = toSwap;
}
}
/**
* Should element with weight `weightA` be closer to root than element with
* weight `weightB`?
*/
protected abstract heapOrderABeforeB(weightA: number, weightB: number): boolean;
}
/** Binary max-heap. */
export class MaxHeap<T> extends BinHeap<T> {
heapOrderABeforeB(weightA: number, weightB: number): boolean {
return weightA > weightB;
}
}
/** Binary min-heap. */
export class MinHeap<T> extends BinHeap<T> {
heapOrderABeforeB(weightA: number, weightB: number): boolean {
return weightA < weightB;
}
}