CONTRACTS: Clean up helpers, expand documentation, Hamming wording again (#2296)

This commit is contained in:
gmcew
2025-08-26 01:16:26 +01:00
committed by GitHub
parent ef8cbc3e0c
commit 15776e36e3
7 changed files with 368 additions and 365 deletions

View File

@@ -4,7 +4,7 @@
## Gang.getOtherGangInformation() method
Get information about the other gangs.
Get information about all gangs.
**Signature:**
@@ -15,7 +15,7 @@ getOtherGangInformation(): Record<string, GangOtherInfoObject>;
Record&lt;string, [GangOtherInfoObject](./bitburner.gangotherinfoobject.md)<!-- -->&gt;
Object containing territory and power information about all gangs.
Object containing territory and power information about all gangs, including the player's gang, if any.
## Remarks

View File

@@ -34,7 +34,7 @@ If you are not in BitNode-2, then you must have Source-File 2 in order to use th
| [getInstallResult(memberName)](./bitburner.gang.getinstallresult.md) | Get the effect of an install on ascension multipliers without installing. |
| [getMemberInformation(name)](./bitburner.gang.getmemberinformation.md) | Get information about a specific gang member. |
| [getMemberNames()](./bitburner.gang.getmembernames.md) | List all gang members. |
| [getOtherGangInformation()](./bitburner.gang.getotherganginformation.md) | Get information about the other gangs. |
| [getOtherGangInformation()](./bitburner.gang.getotherganginformation.md) | Get information about all gangs. |
| [getRecruitsAvailable()](./bitburner.gang.getrecruitsavailable.md) | Check how many gang members you can currently recruit. |
| [getTaskNames()](./bitburner.gang.gettasknames.md) | List member task names. |
| [getTaskStats(name)](./bitburner.gang.gettaskstats.md) | Get stats of a task. |

View File

@@ -1,4 +1,3 @@
import { comprGenChar, comprLZDecode, comprLZEncode, comprLZGenerate } from "../../utils/CompressionContracts";
import { CodingContractTypes } from "../ContractTypes";
import { CodingContractName } from "@enums";
@@ -143,3 +142,204 @@ export const compression: Pick<
validateAnswer: (ans): ans is string => typeof ans === "string",
},
};
// choose random characters for generating plaintext to compress
function comprGenChar(): string {
const r = Math.random();
if (r < 0.4) {
return "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[Math.floor(26 * Math.random())];
} else if (r < 0.8) {
return "abcdefghijklmnopqrstuvwxyz"[Math.floor(26 * Math.random())];
} else {
return "01234567689"[Math.floor(10 * Math.random())];
}
}
// generate plaintext which is amenable to LZ encoding
function comprLZGenerate(): string {
const length = 50 + Math.floor(25 * (Math.random() + Math.random()));
let plain = "";
while (plain.length < length) {
if (Math.random() < 0.8) {
plain += comprGenChar();
} else {
const length = 1 + Math.floor(9 * Math.random());
const offset = 1 + Math.floor(9 * Math.random());
if (offset > plain.length) {
continue;
}
for (let i = 0; i < length; ++i) {
plain += plain[plain.length - offset];
}
}
}
return plain.substring(0, length);
}
// compress plaintext string
function comprLZEncode(plain: string): string {
// for state[i][j]:
// if i is 0, we're adding a literal of length j
// else, we're adding a backreference of offset i and length j
let cur_state: (string | null)[][] = Array.from(Array(10), () => Array<string | null>(10).fill(null));
let new_state: (string | null)[][] = Array.from(Array(10), () => Array<string | null>(10));
function set(state: (string | null)[][], i: number, j: number, str: string): void {
const current = state[i][j];
if (current == null || str.length < current.length) {
state[i][j] = str;
} else if (str.length === current.length && Math.random() < 0.5) {
// if two strings are the same length, pick randomly so that
// we generate more possible inputs to Compression II
state[i][j] = str;
}
}
// initial state is a literal of length 1
cur_state[0][1] = "";
for (let i = 1; i < plain.length; ++i) {
for (const row of new_state) {
row.fill(null);
}
const c = plain[i];
// handle literals
for (let length = 1; length <= 9; ++length) {
const string = cur_state[0][length];
if (string == null) {
continue;
}
if (length < 9) {
// extend current literal
set(new_state, 0, length + 1, string);
} else {
// start new literal
set(new_state, 0, 1, string + "9" + plain.substring(i - 9, i) + "0");
}
for (let offset = 1; offset <= Math.min(9, i); ++offset) {
if (plain[i - offset] === c) {
// start new backreference
set(new_state, offset, 1, string + String(length) + plain.substring(i - length, i));
}
}
}
// handle backreferences
for (let offset = 1; offset <= 9; ++offset) {
for (let length = 1; length <= 9; ++length) {
const string = cur_state[offset][length];
if (string == null) {
continue;
}
if (plain[i - offset] === c) {
if (length < 9) {
// extend current backreference
set(new_state, offset, length + 1, string);
} else {
// start new backreference
set(new_state, offset, 1, string + "9" + String(offset) + "0");
}
}
// start new literal
set(new_state, 0, 1, string + String(length) + String(offset));
// end current backreference and start new backreference
for (let new_offset = 1; new_offset <= Math.min(9, i); ++new_offset) {
if (plain[i - new_offset] === c) {
set(new_state, new_offset, 1, string + String(length) + String(offset) + "0");
}
}
}
}
const tmp_state = new_state;
new_state = cur_state;
cur_state = tmp_state;
}
let result = null;
for (let len = 1; len <= 9; ++len) {
let string = cur_state[0][len];
if (string == null) {
continue;
}
string += String(len) + plain.substring(plain.length - len, plain.length);
if (result == null || string.length < result.length) {
result = string;
} else if (string.length == result.length && Math.random() < 0.5) {
result = string;
}
}
for (let offset = 1; offset <= 9; ++offset) {
for (let len = 1; len <= 9; ++len) {
let string = cur_state[offset][len];
if (string == null) {
continue;
}
string += String(len) + "" + String(offset);
if (result == null || string.length < result.length) {
result = string;
} else if (string.length == result.length && Math.random() < 0.5) {
result = string;
}
}
}
return result ?? "";
}
// decompress LZ-compressed string, or return null if input is invalid
function comprLZDecode(compr: string): string | null {
let plain = "";
for (let i = 0; i < compr.length; ) {
const literal_length = compr.charCodeAt(i) - 0x30;
if (literal_length < 0 || literal_length > 9 || i + 1 + literal_length > compr.length) {
return null;
}
plain += compr.substring(i + 1, i + 1 + literal_length);
i += 1 + literal_length;
if (i >= compr.length) {
break;
}
const backref_length = compr.charCodeAt(i) - 0x30;
if (backref_length < 0 || backref_length > 9) {
return null;
} else if (backref_length === 0) {
++i;
} else {
if (i + 1 >= compr.length) {
return null;
}
const backref_offset = compr.charCodeAt(i + 1) - 0x30;
if ((backref_length > 0 && (backref_offset < 1 || backref_offset > 9)) || backref_offset > plain.length) {
return null;
}
for (let j = 0; j < backref_length; ++j) {
plain += plain[plain.length - backref_offset];
}
i += 2;
}
}
return plain;
}

View File

@@ -1,4 +1,3 @@
import { HammingDecode, HammingEncode, HammingEncodeProperly } from "../../utils/HammingCodeTools";
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
import { CodingContractTypes } from "../ContractTypes";
import { CodingContractName } from "@enums";
@@ -18,8 +17,9 @@ export const hammingCode: Pick<
"An 'extended Hamming code' has an additional parity bit to enhance error detection.\n",
"A parity bit is inserted at every position N where N is a power of 2, with the additional parity bit at position 0.\n",
"Parity bits are used to make the total number of '1' bits in a given set of data even.\n",
"Each parity bit at position 2^N alternately considers N bits then ignores N bits, starting at position 2^N.\n",
"Each parity bit at position N alternately considers N bits then ignores N bits, starting at and including position N.\n",
"The additional parity bit at position 0 considers all bits including parity bits.\n",
"For example, the parity bit at position 2 considers bits 2 to 3 and 6 to 7. The parity bit at position 1 considers bits 1, 3, 5 and 7.\n",
"The endianness of the parity bits is reversed compared to the endianness of the data bits:\n",
"Data bits are encoded most significant bit first and the parity bits encoded least significant bit first.\n",
"The additional parity bit at position 0 is set last.\n\n",
@@ -53,8 +53,9 @@ export const hammingCode: Pick<
"An 'extended Hamming code' has an additional parity bit to enhance error detection.\n",
"A parity bit is inserted at every position N where N is a power of 2, with the additional parity bit at position 0.\n",
"Parity bits are used to make the total number of '1' bits in a given set of data even.\n",
"Each parity bit at position 2^N alternately considers 2^N bits then ignores 2^N bits, starting at position 2^N.\n",
"Each parity bit at position N alternately considers N bits then ignores N bits, starting at and including position N.\n",
"The additional parity bit at position 0 considers all bits including parity bits.\n",
"For example, the parity bit at position 2 considers bits 2 to 3 and 6 to 7. The parity bit at position 1 considers bits 1, 3, 5 and 7.\n",
"The endianness of the parity bits is reversed compared to the endianness of the data bits:\n",
"Data bits are encoded most significant bit first and the parity bits encoded least significant bit first.\n",
"The additional parity bit at position 0 is set last.\n",
@@ -89,3 +90,162 @@ export const hammingCode: Pick<
validateAnswer: (ans): ans is number => typeof ans === "number",
},
};
function HammingEncode(data: number): string {
const enc: number[] = [0];
const data_bits: number[] = data
.toString(2)
.split("")
.reverse()
.map((value) => parseInt(value));
let k = data_bits.length;
/* NOTE: writing the data like this flips the endianness, this is what the
* original implementation by Hedrauta did so I'm keeping it like it was. */
for (let i = 1; k > 0; i++) {
if ((i & (i - 1)) != 0) {
enc[i] = data_bits[--k];
} else {
enc[i] = 0;
}
}
let parityNumber = 0;
/* Figure out the subsection parities */
for (let i = 0; i < enc.length; i++) {
if (enc[i]) {
parityNumber ^= i;
}
}
const parityArray = parityNumber
.toString(2)
.split("")
.reverse()
.map((value) => parseInt(value));
/* Set the parity bits accordingly */
for (let i = 0; i < parityArray.length; i++) {
enc[2 ** i] = parityArray[i] ? 1 : 0;
}
parityNumber = 0;
/* Figure out the overall parity for the entire block */
for (let i = 0; i < enc.length; i++) {
if (enc[i]) {
parityNumber++;
}
}
/* Finally set the overall parity bit */
enc[0] = parityNumber % 2 == 0 ? 0 : 1;
return enc.join("");
}
function HammingEncodeProperly(data: number): string {
/* How many bits do we need?
* n = 2^m
* k = 2^m - m - 1
* where k is the number of data bits, m the number
* of parity bits and n the number of total bits. */
let m = 1;
while (2 ** (2 ** m - m - 1) - 1 < data) {
m++;
}
const n: number = 2 ** m;
const k: number = 2 ** m - m - 1;
const enc: number[] = [0];
const data_bits: number[] = data
.toString(2)
.split("")
.reverse()
.map((value) => parseInt(value));
/* Flip endianness as in the original implementation by Hedrauta
* and write the data back to front
* XXX why do we do this? */
for (let i = 1, j = k; i < n; i++) {
if ((i & (i - 1)) != 0) {
enc[i] = data_bits[--j] ? data_bits[j] : 0;
}
}
let parityNumber = 0;
/* Figure out the subsection parities */
for (let i = 0; i < n; i++) {
if (enc[i]) {
parityNumber ^= i;
}
}
const parityArray = parityNumber
.toString(2)
.split("")
.reverse()
.map((value) => parseInt(value));
/* Set the parity bits accordingly */
for (let i = 0; i < m; i++) {
enc[2 ** i] = parityArray[i] ? 1 : 0;
}
parityNumber = 0;
/* Figure out the overall parity for the entire block */
for (let i = 0; i < n; i++) {
if (enc[i]) {
parityNumber++;
}
}
/* Finally set the overall parity bit */
enc[0] = parityNumber % 2 == 0 ? 0 : 1;
return enc.join("");
}
function HammingDecode(data: string): number {
let err = 0;
const bits: number[] = [];
/* TODO why not just work with an array of digits from the start? */
const bitStringArray = data.split("");
for (let i = 0; i < bitStringArray.length; ++i) {
const bit = parseInt(bitStringArray[i]);
bits[i] = bit;
if (bit) {
err ^= +i;
}
}
/* If err != 0 then it spells out the index of the bit that was flipped */
if (err) {
/* Flip to correct */
bits[err] = bits[err] ? 0 : 1;
}
/* Now we have to read the message, bit 0 is unused (it's the overall parity bit
* which we don't care about). Each bit at an index that is a power of 2 is
* a parity bit and not part of the actual message. */
let ans = "";
for (let i = 1; i < bits.length; i++) {
/* i is not a power of two so it's not a parity bit */
if ((i & (i - 1)) != 0) {
ans += bits[i];
}
}
/* TODO to avoid ambiguity about endianness why not let the player return the extracted (and corrected)
* data bits, rather than guessing at how to convert it to a decimal string? */
return parseInt(ans, 2);
}

View File

@@ -28,6 +28,7 @@ Consider using the [API](https://github.com/bitburner-official/bitburner-src/blo
For example, some contracts have long solutions while others have even longer solutions.
You might want to use the [API](https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.codingcontract.md) to automate the process of submitting your solution rather than copy and paste a long solution into an answer box.
The [Coding Contract API](https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.codingcontract.md) can also be used to find out useful information about a contract including the number of attempts you have left, the type of contract and its difficulty.
It can also be used to test your algorithm for a specific contract type by [spawning dummy contracts](https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.codingcontract.createdummycontract.md).
However, using the [API](https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.codingcontract.md) comes at a cost.
Like most functions in other APIs, almost all of the functions in the [Coding Contract API](https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.codingcontract.md) have a RAM cost.

View File

@@ -1,200 +0,0 @@
// choose random characters for generating plaintext to compress
export function comprGenChar(): string {
const r = Math.random();
if (r < 0.4) {
return "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[Math.floor(26 * Math.random())];
} else if (r < 0.8) {
return "abcdefghijklmnopqrstuvwxyz"[Math.floor(26 * Math.random())];
} else {
return "01234567689"[Math.floor(10 * Math.random())];
}
}
// generate plaintext which is amenable to LZ encoding
export function comprLZGenerate(): string {
const length = 50 + Math.floor(25 * (Math.random() + Math.random()));
let plain = "";
while (plain.length < length) {
if (Math.random() < 0.8) {
plain += comprGenChar();
} else {
const length = 1 + Math.floor(9 * Math.random());
const offset = 1 + Math.floor(9 * Math.random());
if (offset > plain.length) {
continue;
}
for (let i = 0; i < length; ++i) {
plain += plain[plain.length - offset];
}
}
}
return plain.substring(0, length);
}
// compress plaintext string
export function comprLZEncode(plain: string): string {
// for state[i][j]:
// if i is 0, we're adding a literal of length j
// else, we're adding a backreference of offset i and length j
let cur_state: (string | null)[][] = Array.from(Array(10), () => Array<string | null>(10).fill(null));
let new_state: (string | null)[][] = Array.from(Array(10), () => Array<string | null>(10));
function set(state: (string | null)[][], i: number, j: number, str: string): void {
const current = state[i][j];
if (current == null || str.length < current.length) {
state[i][j] = str;
} else if (str.length === current.length && Math.random() < 0.5) {
// if two strings are the same length, pick randomly so that
// we generate more possible inputs to Compression II
state[i][j] = str;
}
}
// initial state is a literal of length 1
cur_state[0][1] = "";
for (let i = 1; i < plain.length; ++i) {
for (const row of new_state) {
row.fill(null);
}
const c = plain[i];
// handle literals
for (let length = 1; length <= 9; ++length) {
const string = cur_state[0][length];
if (string == null) {
continue;
}
if (length < 9) {
// extend current literal
set(new_state, 0, length + 1, string);
} else {
// start new literal
set(new_state, 0, 1, string + "9" + plain.substring(i - 9, i) + "0");
}
for (let offset = 1; offset <= Math.min(9, i); ++offset) {
if (plain[i - offset] === c) {
// start new backreference
set(new_state, offset, 1, string + String(length) + plain.substring(i - length, i));
}
}
}
// handle backreferences
for (let offset = 1; offset <= 9; ++offset) {
for (let length = 1; length <= 9; ++length) {
const string = cur_state[offset][length];
if (string == null) {
continue;
}
if (plain[i - offset] === c) {
if (length < 9) {
// extend current backreference
set(new_state, offset, length + 1, string);
} else {
// start new backreference
set(new_state, offset, 1, string + "9" + String(offset) + "0");
}
}
// start new literal
set(new_state, 0, 1, string + String(length) + String(offset));
// end current backreference and start new backreference
for (let new_offset = 1; new_offset <= Math.min(9, i); ++new_offset) {
if (plain[i - new_offset] === c) {
set(new_state, new_offset, 1, string + String(length) + String(offset) + "0");
}
}
}
}
const tmp_state = new_state;
new_state = cur_state;
cur_state = tmp_state;
}
let result = null;
for (let len = 1; len <= 9; ++len) {
let string = cur_state[0][len];
if (string == null) {
continue;
}
string += String(len) + plain.substring(plain.length - len, plain.length);
if (result == null || string.length < result.length) {
result = string;
} else if (string.length == result.length && Math.random() < 0.5) {
result = string;
}
}
for (let offset = 1; offset <= 9; ++offset) {
for (let len = 1; len <= 9; ++len) {
let string = cur_state[offset][len];
if (string == null) {
continue;
}
string += String(len) + "" + String(offset);
if (result == null || string.length < result.length) {
result = string;
} else if (string.length == result.length && Math.random() < 0.5) {
result = string;
}
}
}
return result ?? "";
}
// decompress LZ-compressed string, or return null if input is invalid
export function comprLZDecode(compr: string): string | null {
let plain = "";
for (let i = 0; i < compr.length; ) {
const literal_length = compr.charCodeAt(i) - 0x30;
if (literal_length < 0 || literal_length > 9 || i + 1 + literal_length > compr.length) {
return null;
}
plain += compr.substring(i + 1, i + 1 + literal_length);
i += 1 + literal_length;
if (i >= compr.length) {
break;
}
const backref_length = compr.charCodeAt(i) - 0x30;
if (backref_length < 0 || backref_length > 9) {
return null;
} else if (backref_length === 0) {
++i;
} else {
if (i + 1 >= compr.length) {
return null;
}
const backref_offset = compr.charCodeAt(i + 1) - 0x30;
if ((backref_length > 0 && (backref_offset < 1 || backref_offset > 9)) || backref_offset > plain.length) {
return null;
}
for (let j = 0; j < backref_length; ++j) {
plain += plain[plain.length - backref_offset];
}
i += 2;
}
}
return plain;
}

View File

@@ -1,158 +0,0 @@
export function HammingEncode(data: number): string {
const enc: number[] = [0];
const data_bits: number[] = data
.toString(2)
.split("")
.reverse()
.map((value) => parseInt(value));
let k = data_bits.length;
/* NOTE: writing the data like this flips the endianness, this is what the
* original implementation by Hedrauta did so I'm keeping it like it was. */
for (let i = 1; k > 0; i++) {
if ((i & (i - 1)) != 0) {
enc[i] = data_bits[--k];
} else {
enc[i] = 0;
}
}
let parityNumber = 0;
/* Figure out the subsection parities */
for (let i = 0; i < enc.length; i++) {
if (enc[i]) {
parityNumber ^= i;
}
}
const parityArray = parityNumber
.toString(2)
.split("")
.reverse()
.map((value) => parseInt(value));
/* Set the parity bits accordingly */
for (let i = 0; i < parityArray.length; i++) {
enc[2 ** i] = parityArray[i] ? 1 : 0;
}
parityNumber = 0;
/* Figure out the overall parity for the entire block */
for (let i = 0; i < enc.length; i++) {
if (enc[i]) {
parityNumber++;
}
}
/* Finally set the overall parity bit */
enc[0] = parityNumber % 2 == 0 ? 0 : 1;
return enc.join("");
}
export function HammingEncodeProperly(data: number): string {
/* How many bits do we need?
* n = 2^m
* k = 2^m - m - 1
* where k is the number of data bits, m the number
* of parity bits and n the number of total bits. */
let m = 1;
while (2 ** (2 ** m - m - 1) - 1 < data) {
m++;
}
const n: number = 2 ** m;
const k: number = 2 ** m - m - 1;
const enc: number[] = [0];
const data_bits: number[] = data
.toString(2)
.split("")
.reverse()
.map((value) => parseInt(value));
/* Flip endianness as in the original implementation by Hedrauta
* and write the data back to front
* XXX why do we do this? */
for (let i = 1, j = k; i < n; i++) {
if ((i & (i - 1)) != 0) {
enc[i] = data_bits[--j] ? data_bits[j] : 0;
}
}
let parityNumber = 0;
/* Figure out the subsection parities */
for (let i = 0; i < n; i++) {
if (enc[i]) {
parityNumber ^= i;
}
}
const parityArray = parityNumber
.toString(2)
.split("")
.reverse()
.map((value) => parseInt(value));
/* Set the parity bits accordingly */
for (let i = 0; i < m; i++) {
enc[2 ** i] = parityArray[i] ? 1 : 0;
}
parityNumber = 0;
/* Figure out the overall parity for the entire block */
for (let i = 0; i < n; i++) {
if (enc[i]) {
parityNumber++;
}
}
/* Finally set the overall parity bit */
enc[0] = parityNumber % 2 == 0 ? 0 : 1;
return enc.join("");
}
export function HammingDecode(data: string): number {
let err = 0;
const bits: number[] = [];
/* TODO why not just work with an array of digits from the start? */
const bitStringArray = data.split("");
for (let i = 0; i < bitStringArray.length; ++i) {
const bit = parseInt(bitStringArray[i]);
bits[i] = bit;
if (bit) {
err ^= +i;
}
}
/* If err != 0 then it spells out the index of the bit that was flipped */
if (err) {
/* Flip to correct */
bits[err] = bits[err] ? 0 : 1;
}
/* Now we have to read the message, bit 0 is unused (it's the overall parity bit
* which we don't care about). Each bit at an index that is a power of 2 is
* a parity bit and not part of the actual message. */
let ans = "";
for (let i = 1; i < bits.length; i++) {
/* i is not a power of two so it's not a parity bit */
if ((i & (i - 1)) != 0) {
ans += bits[i];
}
}
/* TODO to avoid ambiguity about endianness why not let the player return the extracted (and corrected)
* data bits, rather than guessing at how to convert it to a decimal string? */
return parseInt(ans, 2);
}