2019-04-11 10:37:40 +02:00
|
|
|
import {
|
|
|
|
codingContractTypesMetadata,
|
|
|
|
DescriptionFunc,
|
|
|
|
GeneratorFunc,
|
|
|
|
SolverFunc
|
|
|
|
} from "./data/codingcontracttypes";
|
|
|
|
|
|
|
|
import { IMap } from "./types";
|
|
|
|
|
|
|
|
import {
|
|
|
|
Generic_fromJSON,
|
|
|
|
Generic_toJSON,
|
|
|
|
Reviver
|
|
|
|
} from "../utils/JSONReviver";
|
2019-02-01 09:27:24 +01:00
|
|
|
import { KEY } from "../utils/helpers/keyCodes";
|
2018-09-14 23:03:31 +02:00
|
|
|
import { createElement } from "../utils/uiHelpers/createElement";
|
|
|
|
import { createPopup } from "../utils/uiHelpers/createPopup";
|
|
|
|
import { removeElementById } from "../utils/uiHelpers/removeElementById";
|
2019-02-01 09:27:24 +01:00
|
|
|
|
2018-09-14 23:03:31 +02:00
|
|
|
/* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */
|
|
|
|
|
|
|
|
/* Represents different types of problems that a Coding Contract can have */
|
2018-09-16 10:55:54 +02:00
|
|
|
export class CodingContractType {
|
2018-09-14 23:03:31 +02:00
|
|
|
/**
|
|
|
|
* Function that generates a description of the problem
|
|
|
|
*/
|
|
|
|
desc: DescriptionFunc;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Number that generally represents the problem's difficulty. Bigger numbers = harder
|
|
|
|
*/
|
|
|
|
difficulty: number;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A function that randomly generates a valid 'data' for the problem
|
|
|
|
*/
|
|
|
|
generate: GeneratorFunc;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Name of the type of problem
|
|
|
|
*/
|
|
|
|
name: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The maximum number of tries the player gets on this kind of problem before it self-destructs
|
|
|
|
*/
|
|
|
|
numTries: number;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stores a function that checks if the provided answer is correct
|
|
|
|
*/
|
|
|
|
solver: SolverFunc;
|
|
|
|
|
|
|
|
constructor(name: string,
|
|
|
|
desc: DescriptionFunc,
|
|
|
|
gen: GeneratorFunc,
|
|
|
|
solver: SolverFunc,
|
|
|
|
diff: number,
|
|
|
|
numTries: number) {
|
|
|
|
this.name = name;
|
|
|
|
this.desc = desc;
|
|
|
|
this.generate = gen;
|
|
|
|
this.solver = solver;
|
|
|
|
this.difficulty = diff;
|
|
|
|
this.numTries = numTries;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Contract Types */
|
|
|
|
// tslint:disable-next-line
|
2018-09-16 10:55:54 +02:00
|
|
|
export const CodingContractTypes: IMap<CodingContractType> = {};
|
2018-09-14 23:03:31 +02:00
|
|
|
|
|
|
|
for (const md of codingContractTypesMetadata) {
|
2018-09-28 06:40:40 +02:00
|
|
|
// tslint:disable-next-line
|
2018-09-16 10:55:54 +02:00
|
|
|
CodingContractTypes[md.name] = new CodingContractType(md.name, md.desc, md.gen, md.solver, md.difficulty, md.numTries);
|
2018-09-14 23:03:31 +02:00
|
|
|
}
|
2018-09-16 10:55:54 +02:00
|
|
|
console.info(`${Object.keys(CodingContractTypes).length} Coding Contract Types loaded`);
|
2018-09-14 23:03:31 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Enum representing the different types of rewards a Coding Contract can give
|
|
|
|
*/
|
|
|
|
export enum CodingContractRewardType {
|
|
|
|
FactionReputation,
|
|
|
|
FactionReputationAll,
|
|
|
|
CompanyReputation,
|
2018-09-23 02:25:48 +02:00
|
|
|
Money, // This must always be the last reward type
|
2018-09-14 23:03:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enum representing the result when trying to solve the Contract
|
|
|
|
*/
|
|
|
|
export enum CodingContractResult {
|
|
|
|
Success,
|
|
|
|
Failure,
|
|
|
|
Cancelled,
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A class that represents the type of reward a contract gives
|
|
|
|
*/
|
|
|
|
export interface ICodingContractReward {
|
|
|
|
/* Name of Company/Faction name for reward, if applicable */
|
|
|
|
name?: string;
|
|
|
|
type: CodingContractRewardType;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A Coding Contract is a file that poses a programming-related problem to the Player.
|
|
|
|
* The player receives a reward if the problem is solved correctly
|
|
|
|
*/
|
|
|
|
export class CodingContract {
|
|
|
|
/**
|
|
|
|
* Initiatizes a CodingContract from a JSON save state.
|
|
|
|
*/
|
|
|
|
static fromJSON(value: any): CodingContract {
|
|
|
|
return Generic_fromJSON(CodingContract, value.data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Relevant data for the contract's problem */
|
|
|
|
data: any;
|
|
|
|
|
|
|
|
/* Contract's filename */
|
|
|
|
fn: string;
|
|
|
|
|
|
|
|
/* Describes the reward given if this Contract is solved. The reward is actually
|
|
|
|
processed outside of this file */
|
|
|
|
reward: ICodingContractReward | null;
|
|
|
|
|
|
|
|
/* Number of times the Contract has been attempted */
|
|
|
|
tries: number = 0;
|
|
|
|
|
|
|
|
/* String representing the contract's type. Must match type in ContractTypes */
|
|
|
|
type: string;
|
|
|
|
|
|
|
|
constructor(fn: string = "",
|
|
|
|
type: string = "Find Largest Prime Factor",
|
|
|
|
reward: ICodingContractReward | null = null) {
|
|
|
|
this.fn = fn;
|
|
|
|
if (!this.fn.endsWith(".cct")) {
|
|
|
|
this.fn += ".cct";
|
|
|
|
}
|
|
|
|
|
|
|
|
// tslint:disable-next-line
|
2018-09-16 10:55:54 +02:00
|
|
|
if (CodingContractTypes[type] == null) {
|
2018-09-14 23:03:31 +02:00
|
|
|
throw new Error(`Error: invalid contract type: ${type} please contact developer`);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.type = type;
|
2018-09-16 10:55:54 +02:00
|
|
|
this.data = CodingContractTypes[type].generate();
|
2018-09-14 23:03:31 +02:00
|
|
|
this.reward = reward;
|
|
|
|
}
|
|
|
|
|
2018-09-23 02:25:48 +02:00
|
|
|
getData(): any {
|
|
|
|
return this.data;
|
|
|
|
}
|
|
|
|
|
|
|
|
getDescription(): string {
|
|
|
|
return CodingContractTypes[this.type].desc(this.data);
|
|
|
|
}
|
|
|
|
|
2018-09-14 23:03:31 +02:00
|
|
|
getDifficulty(): number {
|
2018-09-16 10:55:54 +02:00
|
|
|
return CodingContractTypes[this.type].difficulty;
|
2018-09-14 23:03:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
getMaxNumTries(): number {
|
2018-09-16 10:55:54 +02:00
|
|
|
return CodingContractTypes[this.type].numTries;
|
2018-09-14 23:03:31 +02:00
|
|
|
}
|
|
|
|
|
2018-10-09 02:26:24 +02:00
|
|
|
getType(): string {
|
|
|
|
return CodingContractTypes[this.type].name;
|
|
|
|
}
|
|
|
|
|
2018-09-14 23:03:31 +02:00
|
|
|
isSolution(solution: string): boolean {
|
2018-09-16 10:55:54 +02:00
|
|
|
return CodingContractTypes[this.type].solver(this.data, solution);
|
2018-09-14 23:03:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a popup to prompt the player to solve the problem
|
|
|
|
*/
|
|
|
|
async prompt(): Promise<CodingContractResult> {
|
|
|
|
// tslint:disable-next-line
|
|
|
|
return new Promise<CodingContractResult>((resolve: Function, reject: Function) => {
|
2018-09-23 02:25:48 +02:00
|
|
|
const contractType: CodingContractType = CodingContractTypes[this.type];
|
2018-09-14 23:03:31 +02:00
|
|
|
const popupId: string = `coding-contract-prompt-popup-${this.fn}`;
|
|
|
|
const txt: HTMLElement = createElement("p", {
|
2019-02-01 09:27:24 +01:00
|
|
|
innerHTML: ["You are attempting to solve a Coding Contract. You have",
|
2018-09-23 02:25:48 +02:00
|
|
|
`${this.getMaxNumTries() - this.tries} tries remaining,`,
|
2019-02-01 09:27:24 +01:00
|
|
|
"after which the contract will self-destruct.<br><br>",
|
|
|
|
`${contractType.desc(this.data).replace(/\n/g, "<br>")}`].join(" "),
|
2018-09-14 23:03:31 +02:00
|
|
|
});
|
2018-09-28 06:40:40 +02:00
|
|
|
let answerInput: HTMLInputElement;
|
|
|
|
let solveBtn: HTMLElement;
|
2019-02-01 09:27:24 +01:00
|
|
|
let cancelBtn: HTMLElement;
|
2018-09-28 06:40:40 +02:00
|
|
|
answerInput = createElement("input", {
|
|
|
|
onkeydown: (e: any) => {
|
2019-02-01 09:27:24 +01:00
|
|
|
if (e.keyCode === KEY.ENTER && answerInput.value !== "") {
|
2018-09-23 02:25:48 +02:00
|
|
|
e.preventDefault();
|
|
|
|
solveBtn.click();
|
2019-02-01 09:27:24 +01:00
|
|
|
} else if (e.keyCode === KEY.ESC) {
|
|
|
|
e.preventDefault();
|
|
|
|
cancelBtn.click();
|
2018-09-23 02:25:48 +02:00
|
|
|
}
|
|
|
|
},
|
2018-09-14 23:03:31 +02:00
|
|
|
placeholder: "Enter Solution here",
|
2019-05-02 00:20:14 +02:00
|
|
|
width: "50%",
|
2018-09-14 23:03:31 +02:00
|
|
|
}) as HTMLInputElement;
|
2018-09-28 06:40:40 +02:00
|
|
|
solveBtn = createElement("a", {
|
2018-09-14 23:03:31 +02:00
|
|
|
class: "a-link-button",
|
|
|
|
clickListener: () => {
|
|
|
|
const answer: string = answerInput.value;
|
|
|
|
if (this.isSolution(answer)) {
|
|
|
|
resolve(CodingContractResult.Success);
|
|
|
|
} else {
|
|
|
|
resolve(CodingContractResult.Failure);
|
|
|
|
}
|
|
|
|
removeElementById(popupId);
|
|
|
|
},
|
|
|
|
innerText: "Solve",
|
|
|
|
});
|
2019-02-01 09:27:24 +01:00
|
|
|
cancelBtn = createElement("a", {
|
2018-09-14 23:03:31 +02:00
|
|
|
class: "a-link-button",
|
|
|
|
clickListener: () => {
|
|
|
|
resolve(CodingContractResult.Cancelled);
|
|
|
|
removeElementById(popupId);
|
|
|
|
},
|
|
|
|
innerText: "Cancel",
|
|
|
|
});
|
|
|
|
const lineBreak: HTMLElement = createElement("br");
|
|
|
|
createPopup(popupId, [txt, lineBreak, lineBreak, answerInput, solveBtn, cancelBtn]);
|
2018-09-23 02:25:48 +02:00
|
|
|
answerInput.focus();
|
2018-09-14 23:03:31 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Serialize the current file to a JSON save state.
|
|
|
|
*/
|
|
|
|
toJSON(): any {
|
|
|
|
return Generic_toJSON("CodingContract", this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Reviver.constructors.CodingContract = CodingContract;
|