prettify, sorry for the big ass commit

This commit is contained in:
Olivier Gagnon
2021-09-04 19:09:30 -04:00
parent 3d7cdb4ef9
commit a18bdd6afc
554 changed files with 91615 additions and 66138 deletions
+289 -85
View File
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
@@ -8,100 +8,304 @@ import { interpolate } from "./Difficulty";
import { BlinkingCursor } from "./BlinkingCursor";
interface Difficulty {
[key: string]: number;
timer: number;
min: number;
max: number;
[key: string]: number;
timer: number;
min: number;
max: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer: 16000, min: 3, max: 4},
Normal: {timer: 12500, min: 2, max: 3},
Hard: {timer: 15000, min: 3, max: 4},
Impossible: {timer: 8000, min: 4, max: 4},
}
Trivial: { timer: 16000, min: 3, max: 4 },
Normal: { timer: 12500, min: 2, max: 3 },
Hard: { timer: 15000, min: 3, max: 4 },
Impossible: { timer: 8000, min: 4, max: 4 },
};
export function BackwardGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer: 0, min: 0, max: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [answer] = useState(makeAnswer(difficulty));
const [guess, setGuess] = useState("");
const difficulty: Difficulty = { timer: 0, min: 0, max: 0 };
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [answer] = useState(makeAnswer(difficulty));
const [guess, setGuess] = useState("");
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
if(event.keyCode === 16) return;
const nextGuess = guess + event.key.toUpperCase();
if(!answer.startsWith(nextGuess)) props.onFailure();
else if (answer === nextGuess) props.onSuccess();
else setGuess(nextGuess);
}
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
if (event.keyCode === 16) return;
const nextGuess = guess + event.key.toUpperCase();
if (!answer.startsWith(nextGuess)) props.onFailure();
else if (answer === nextGuess) props.onSuccess();
else setGuess(nextGuess);
}
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Type it backward</h1>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
<Grid item xs={6}>
<p style={{transform: 'scaleX(-1)'}}>{answer}</p>
</Grid>
<Grid item xs={6}>
<p>{guess}<BlinkingCursor /></p>
</Grid>
</Grid>)
return (
<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Type it backward</h1>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
<Grid item xs={6}>
<p style={{ transform: "scaleX(-1)" }}>{answer}</p>
</Grid>
<Grid item xs={6}>
<p>
{guess}
<BlinkingCursor />
</p>
</Grid>
</Grid>
);
}
function makeAnswer(difficulty: Difficulty): string {
const length = random(difficulty.min, difficulty.max);
let answer = "";
for(let i = 0; i < length; i++) {
if(i > 0) answer += " "
answer += words[Math.floor(Math.random() * words.length)];
}
const length = random(difficulty.min, difficulty.max);
let answer = "";
for (let i = 0; i < length; i++) {
if (i > 0) answer += " ";
answer += words[Math.floor(Math.random() * words.length)];
}
return answer;
return answer;
}
const words = ["ALGORITHM", "ANALOG", "APP", "APPLICATION", "ARRAY", "BACKUP",
"BANDWIDTH", "BINARY", "BIT", "BITE", "BITMAP", "BLOG", "BLOGGER",
"BOOKMARK", "BOOT", "BROADBAND", "BROWSER", "BUFFER", "BUG", "BUS", "BYTE",
"CACHE", "CAPS LOCK", "CAPTCHA", "CD", "CD-ROM", "CLIENT",
"CLIPBOARD", "CLOUD", "COMPUTING", "COMMAND", "COMPILE", "COMPRESS",
"COMPUTER", "CONFIGURE", "COOKIE", "COPY", "CPU",
"CYBERCRIME", "CYBERSPACE", "DASHBOARD", "DATA", "MINING", "DATABASE",
"DEBUG", "DECOMPRESS", "DELETE", "DESKTOP", "DEVELOPMENT", "DIGITAL",
"DISK", "DNS", "DOCUMENT", "DOMAIN", "DOMAIN NAME", "DOT", "DOT MATRIX",
"DOWNLOAD", "DRAG", "DVD", "DYNAMIC", "EMAIL", "EMOTICON", "ENCRYPT",
"ENCRYPTION", "ENTER", "EXABYTE", "FAQ", "FILE", "FINDER", "FIREWALL",
"FIRMWARE", "FLAMING", "FLASH", "FLASH DRIVE", "FLOPPY DISK", "FLOWCHART",
"FOLDER", "FONT", "FORMAT", "FRAME", "FREEWARE", "GIGABYTE", "GRAPHICS",
"HACK", "HACKER", "HARDWARE", "HOME PAGE", "HOST", "HTML", "HYPERLINK",
"HYPERTEXT", "ICON", "INBOX", "INTEGER", "INTERFACE", "INTERNET",
"IP ADDRESS", "ITERATION", "JAVA", "JOYSTICK", "JUNKMAIL", "KERNEL",
"KEY", "KEYBOARD", "KEYWORD", "LAPTOP", "LASER PRINTER", "LINK", "LINUX",
"LOG OUT", "LOGIC", "LOGIN", "LURKING", "MACINTOSH", "MACRO", "MAINFRAME",
"MALWARE", "MEDIA", "MEMORY", "MIRROR", "MODEM", "MONITOR", "MOTHERBOARD",
"MOUSE", "MULTIMEDIA", "NET", "NETWORK", "NODE", "NOTEBOOK", "COMPUTER",
"OFFLINE", "ONLINE", "OPENSOURCE", "OPERATING", "SYSTEM", "OPTION", "OUTPUT",
"PAGE", "PASSWORD", "PASTE", "PATH", "PHISHING", "PIRACY", "PIRATE",
"PLATFORM", "PLUGIN", "PODCAST", "POPUP", "PORTAL", "PRINT", "PRINTER",
"PRIVACY", "PROCESS", "PROGRAM", "PROGRAMMER", "PROTOCOL", "QUEUE",
"QWERTY", "RAM", "REALTIME", "REBOOT", "RESOLUTION", "RESTORE", "ROM",
"ROOT", "ROUTER", "RUNTIME", "SAVE", "SCAN", "SCANNER", "SCREEN",
"SCREENSHOT", "SCRIPT", "SCROLL", "SCROLL", "SEARCH", "ENGINE",
"SECURITY", "SERVER", "SHAREWARE", "SHELL", "SHIFT", "SHIFT KEY",
"SNAPSHOT", "SOCIAL NETWORKING", "SOFTWARE", "SPAM", "SPAMMER",
"SPREADSHEET", "SPYWARE", "STATUS", "STORAGE", "SUPERCOMPUTER", "SURF",
"SYNTAX", "TABLE", "TAG", "TERMINAL", "TEMPLATE", "TERABYTE", "TEXT EDITOR",
"THREAD", "TOOLBAR", "TRASH", "TROJAN HORSE", "TYPEFACE", "UNDO", "UNIX",
"UPLOAD", "URL", "USER", "USER INTERFACE", "USERNAME", "UTILITY", "VERSION",
"VIRTUAL", "VIRTUAL MEMORY", "VIRUS", "WEB", "WEBMASTER",
"WEBSITE", "WIDGET", "WIKI", "WINDOW", "WINDOWS", "WIRELESS",
"PROCESSOR", "WORKSTATION", "WEB", "WORM", "WWW", "XML",
"ZIP"];
const words = [
"ALGORITHM",
"ANALOG",
"APP",
"APPLICATION",
"ARRAY",
"BACKUP",
"BANDWIDTH",
"BINARY",
"BIT",
"BITE",
"BITMAP",
"BLOG",
"BLOGGER",
"BOOKMARK",
"BOOT",
"BROADBAND",
"BROWSER",
"BUFFER",
"BUG",
"BUS",
"BYTE",
"CACHE",
"CAPS LOCK",
"CAPTCHA",
"CD",
"CD-ROM",
"CLIENT",
"CLIPBOARD",
"CLOUD",
"COMPUTING",
"COMMAND",
"COMPILE",
"COMPRESS",
"COMPUTER",
"CONFIGURE",
"COOKIE",
"COPY",
"CPU",
"CYBERCRIME",
"CYBERSPACE",
"DASHBOARD",
"DATA",
"MINING",
"DATABASE",
"DEBUG",
"DECOMPRESS",
"DELETE",
"DESKTOP",
"DEVELOPMENT",
"DIGITAL",
"DISK",
"DNS",
"DOCUMENT",
"DOMAIN",
"DOMAIN NAME",
"DOT",
"DOT MATRIX",
"DOWNLOAD",
"DRAG",
"DVD",
"DYNAMIC",
"EMAIL",
"EMOTICON",
"ENCRYPT",
"ENCRYPTION",
"ENTER",
"EXABYTE",
"FAQ",
"FILE",
"FINDER",
"FIREWALL",
"FIRMWARE",
"FLAMING",
"FLASH",
"FLASH DRIVE",
"FLOPPY DISK",
"FLOWCHART",
"FOLDER",
"FONT",
"FORMAT",
"FRAME",
"FREEWARE",
"GIGABYTE",
"GRAPHICS",
"HACK",
"HACKER",
"HARDWARE",
"HOME PAGE",
"HOST",
"HTML",
"HYPERLINK",
"HYPERTEXT",
"ICON",
"INBOX",
"INTEGER",
"INTERFACE",
"INTERNET",
"IP ADDRESS",
"ITERATION",
"JAVA",
"JOYSTICK",
"JUNKMAIL",
"KERNEL",
"KEY",
"KEYBOARD",
"KEYWORD",
"LAPTOP",
"LASER PRINTER",
"LINK",
"LINUX",
"LOG OUT",
"LOGIC",
"LOGIN",
"LURKING",
"MACINTOSH",
"MACRO",
"MAINFRAME",
"MALWARE",
"MEDIA",
"MEMORY",
"MIRROR",
"MODEM",
"MONITOR",
"MOTHERBOARD",
"MOUSE",
"MULTIMEDIA",
"NET",
"NETWORK",
"NODE",
"NOTEBOOK",
"COMPUTER",
"OFFLINE",
"ONLINE",
"OPENSOURCE",
"OPERATING",
"SYSTEM",
"OPTION",
"OUTPUT",
"PAGE",
"PASSWORD",
"PASTE",
"PATH",
"PHISHING",
"PIRACY",
"PIRATE",
"PLATFORM",
"PLUGIN",
"PODCAST",
"POPUP",
"PORTAL",
"PRINT",
"PRINTER",
"PRIVACY",
"PROCESS",
"PROGRAM",
"PROGRAMMER",
"PROTOCOL",
"QUEUE",
"QWERTY",
"RAM",
"REALTIME",
"REBOOT",
"RESOLUTION",
"RESTORE",
"ROM",
"ROOT",
"ROUTER",
"RUNTIME",
"SAVE",
"SCAN",
"SCANNER",
"SCREEN",
"SCREENSHOT",
"SCRIPT",
"SCROLL",
"SCROLL",
"SEARCH",
"ENGINE",
"SECURITY",
"SERVER",
"SHAREWARE",
"SHELL",
"SHIFT",
"SHIFT KEY",
"SNAPSHOT",
"SOCIAL NETWORKING",
"SOFTWARE",
"SPAM",
"SPAMMER",
"SPREADSHEET",
"SPYWARE",
"STATUS",
"STORAGE",
"SUPERCOMPUTER",
"SURF",
"SYNTAX",
"TABLE",
"TAG",
"TERMINAL",
"TEMPLATE",
"TERABYTE",
"TEXT EDITOR",
"THREAD",
"TOOLBAR",
"TRASH",
"TROJAN HORSE",
"TYPEFACE",
"UNDO",
"UNIX",
"UPLOAD",
"URL",
"USER",
"USER INTERFACE",
"USERNAME",
"UTILITY",
"VERSION",
"VIRTUAL",
"VIRTUAL MEMORY",
"VIRUS",
"WEB",
"WEBMASTER",
"WEBSITE",
"WIDGET",
"WIKI",
"WINDOW",
"WINDOWS",
"WIRELESS",
"PROCESSOR",
"WORKSTATION",
"WEB",
"WORM",
"WWW",
"XML",
"ZIP",
];
+7 -3
View File
@@ -1,5 +1,9 @@
import React from 'react';
import React from "react";
export function BlinkingCursor(): React.ReactElement {
return (<span style={{fontSize: "1em"}} className={"blinking-cursor"}>|</span>);
}
return (
<span style={{ fontSize: "1em" }} className={"blinking-cursor"}>
|
</span>
);
}
+64 -57
View File
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
@@ -8,77 +8,84 @@ import { interpolate } from "./Difficulty";
import { BlinkingCursor } from "./BlinkingCursor";
interface Difficulty {
[key: string]: number;
timer: number;
min: number;
max: number;
[key: string]: number;
timer: number;
min: number;
max: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer:8000, min: 2, max: 3},
Normal: {timer:6000, min: 4, max: 5},
Hard: {timer:4000, min: 4, max: 6},
Impossible: {timer: 2500, min: 7, max: 7},
}
Trivial: { timer: 8000, min: 2, max: 3 },
Normal: { timer: 6000, min: 4, max: 5 },
Hard: { timer: 4000, min: 4, max: 6 },
Impossible: { timer: 2500, min: 7, max: 7 },
};
function generateLeftSide(difficulty: Difficulty): string {
let str = "";
const length = random(difficulty.min, difficulty.max);
for(let i = 0; i < length; i++) {
str += ["[", '<', '(', '{'][Math.floor(Math.random()*4)];
}
let str = "";
const length = random(difficulty.min, difficulty.max);
for (let i = 0; i < length; i++) {
str += ["[", "<", "(", "{"][Math.floor(Math.random() * 4)];
}
return str;
return str;
}
function getChar(event: React.KeyboardEvent<HTMLElement>): string {
if(event.keyCode == 48 && event.shiftKey) return ")";
if(event.keyCode == 221 && !event.shiftKey) return "]";
if(event.keyCode == 221 && event.shiftKey) return "}";
if(event.keyCode == 190 && event.shiftKey) return ">";
return "";
if (event.keyCode == 48 && event.shiftKey) return ")";
if (event.keyCode == 221 && !event.shiftKey) return "]";
if (event.keyCode == 221 && event.shiftKey) return "}";
if (event.keyCode == 190 && event.shiftKey) return ">";
return "";
}
function match(left: string, right: string): boolean {
return (left === '[' && right === ']') ||
(left === '<' && right === '>') ||
(left === '(' && right === ')') ||
(left === '{' && right === '}');
return (
(left === "[" && right === "]") ||
(left === "<" && right === ">") ||
(left === "(" && right === ")") ||
(left === "{" && right === "}")
);
}
export function BracketGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer:0, min: 0, max: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [right, setRight] = useState("");
const [left] = useState(generateLeftSide(difficulty));
const difficulty: Difficulty = { timer: 0, min: 0, max: 0 };
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [right, setRight] = useState("");
const [left] = useState(generateLeftSide(difficulty));
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
const char = getChar(event);
if(!char) return;
if(!match(left[left.length-right.length-1], char)) {
props.onFailure();
return;
}
if(left.length === right.length+1) {
props.onSuccess();
return;
}
setRight(right+char);
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
const char = getChar(event);
if (!char) return;
if (!match(left[left.length - right.length - 1], char)) {
props.onFailure();
return;
}
if (left.length === right.length + 1) {
props.onSuccess();
return;
}
setRight(right + char);
}
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Close the brackets</h1>
<p style={{fontSize: '5em'}}>{`${left}${right}`}<BlinkingCursor /></p>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>)
}
return (
<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Close the brackets</h1>
<p style={{ fontSize: "5em" }}>
{`${left}${right}`}
<BlinkingCursor />
</p>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>
);
}
+109 -71
View File
@@ -1,96 +1,134 @@
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty";
interface Difficulty {
[key: string]: number;
timer: number;
size: number;
[key: string]: number;
timer: number;
size: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer: 12000, size: 6},
Normal: {timer: 9000, size: 8},
Hard: {timer: 5000, size: 9},
Impossible: {timer: 2500, size: 12},
}
Trivial: { timer: 12000, size: 6 },
Normal: { timer: 9000, size: 8 },
Hard: { timer: 5000, size: 9 },
Impossible: { timer: 2500, size: 12 },
};
export function BribeGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer: 0, size: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [choices] = useState(makeChoices(difficulty));
const [index, setIndex] = useState(0);
const difficulty: Difficulty = { timer: 0, size: 0 };
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [choices] = useState(makeChoices(difficulty));
const [index, setIndex] = useState(0);
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
const k = event.keyCode;
if(k === 32) {
if(positive.includes(choices[index])) props.onSuccess();
else props.onFailure();
return;
}
let newIndex = index;
if([38, 87, 68, 39].includes(k)) newIndex++;
if([65, 37, 83, 40].includes(k)) newIndex--;
while(newIndex < 0) newIndex += choices.length;
while(newIndex > choices.length-1) newIndex -= choices.length;
setIndex(newIndex);
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
const k = event.keyCode;
if (k === 32) {
if (positive.includes(choices[index])) props.onSuccess();
else props.onFailure();
return;
}
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1>Say something nice about the guard.</h1>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
<Grid item xs={6}>
<h2 style={{fontSize: "2em"}}></h2>
<h2 style={{fontSize: "2em"}}>{choices[index]}</h2>
<h2 style={{fontSize: "2em"}}></h2>
</Grid>
</Grid>)
let newIndex = index;
if ([38, 87, 68, 39].includes(k)) newIndex++;
if ([65, 37, 83, 40].includes(k)) newIndex--;
while (newIndex < 0) newIndex += choices.length;
while (newIndex > choices.length - 1) newIndex -= choices.length;
setIndex(newIndex);
}
return (
<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1>Say something nice about the guard.</h1>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
<Grid item xs={6}>
<h2 style={{ fontSize: "2em" }}></h2>
<h2 style={{ fontSize: "2em" }}>{choices[index]}</h2>
<h2 style={{ fontSize: "2em" }}></h2>
</Grid>
</Grid>
);
}
function shuffleArray(array: string[]): void {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
function makeChoices(difficulty: Difficulty): string[] {
const choices = [];
choices.push(positive[Math.floor(Math.random()*positive.length)]);
for(let i = 0; i < difficulty.size; i++) {
const option = negative[Math.floor(Math.random()*negative.length)];
if(choices.includes(option)) {
i--;
continue;
}
choices.push(option);
const choices = [];
choices.push(positive[Math.floor(Math.random() * positive.length)]);
for (let i = 0; i < difficulty.size; i++) {
const option = negative[Math.floor(Math.random() * negative.length)];
if (choices.includes(option)) {
i--;
continue;
}
shuffleArray(choices);
return choices;
choices.push(option);
}
shuffleArray(choices);
return choices;
}
const positive = ["affectionate","agreeable","bright","charming","creative",
"determined","energetic","friendly","funny","generous","polite","likable",
"diplomatic","helpful","giving","kind","hardworking","patient","dynamic",
"loyal"];
const positive = [
"affectionate",
"agreeable",
"bright",
"charming",
"creative",
"determined",
"energetic",
"friendly",
"funny",
"generous",
"polite",
"likable",
"diplomatic",
"helpful",
"giving",
"kind",
"hardworking",
"patient",
"dynamic",
"loyal",
];
const negative = ["aggressive","aloof","arrogant","big-headed","boastful",
"boring","bossy","careless","clingy","couch potato","cruel","cynical",
"grumpy","hot air","know it all","obnoxious","pain in the neck","picky",
"tactless","thoughtless"];
const negative = [
"aggressive",
"aloof",
"arrogant",
"big-headed",
"boastful",
"boring",
"bossy",
"careless",
"clingy",
"couch potato",
"cruel",
"cynical",
"grumpy",
"hot air",
"know it all",
"obnoxious",
"pain in the neck",
"picky",
"tactless",
"thoughtless",
];
+47 -44
View File
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
@@ -7,59 +7,62 @@ import { random, getArrow } from "../utils";
import { interpolate } from "./Difficulty";
interface Difficulty {
[key: string]: number;
timer: number;
min: number;
max: number;
[key: string]: number;
timer: number;
min: number;
max: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer: 13000, min: 6, max: 8},
Normal: {timer: 7000, min: 7, max: 8},
Hard: {timer: 5000, min: 8, max: 9},
Impossible: {timer: 3000, min: 9, max: 9},
}
Trivial: { timer: 13000, min: 6, max: 8 },
Normal: { timer: 7000, min: 7, max: 8 },
Hard: { timer: 5000, min: 8, max: 9 },
Impossible: { timer: 3000, min: 9, max: 9 },
};
export function CheatCodeGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer: 0, min: 0, max: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [code] = useState(generateCode(difficulty));
const [index, setIndex] = useState(0);
const difficulty: Difficulty = { timer: 0, min: 0, max: 0 };
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [code] = useState(generateCode(difficulty));
const [index, setIndex] = useState(0);
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
if(code[index] !== getArrow(event)) {
props.onFailure();
return;
}
setIndex(index+1);
if(index+1 >= code.length) props.onSuccess();
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
if (code[index] !== getArrow(event)) {
props.onFailure();
return;
}
setIndex(index + 1);
if (index + 1 >= code.length) props.onSuccess();
}
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Enter the Code!</h1>
<p style={{fontSize: '5em'}}>{code[index]}</p>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>)
return (
<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Enter the Code!</h1>
<p style={{ fontSize: "5em" }}>{code[index]}</p>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>
);
}
function generateCode(difficulty: Difficulty): string {
const arrows = ['←', '→', '↑', '↓'];
let code = '';
for(let i = 0; i < random(difficulty.min, difficulty.max); i++) {
let arrow = arrows[Math.floor(4*Math.random())];
while(arrow === code[code.length-1]) arrow = arrows[Math.floor(4*Math.random())];
code += arrow;
}
const arrows = ["←", "→", "↑", "↓"];
let code = "";
for (let i = 0; i < random(difficulty.min, difficulty.max); i++) {
let arrow = arrows[Math.floor(4 * Math.random())];
while (arrow === code[code.length - 1])
arrow = arrows[Math.floor(4 * Math.random())];
code += arrow;
}
return code;
return code;
}
+21 -19
View File
@@ -1,26 +1,28 @@
import React, { useState, useEffect } from 'react';
import Grid from '@material-ui/core/Grid';
import React, { useState, useEffect } from "react";
import Grid from "@material-ui/core/Grid";
interface IProps {
onFinish: () => void;
onFinish: () => void;
}
export function Countdown(props: IProps): React.ReactElement {
const [x, setX] = useState(3);
useEffect(() => {
if(x === 0) {
props.onFinish();
return;
}
setTimeout(()=>setX(x-1), 200);
});
const [x, setX] = useState(3);
useEffect(() => {
if (x === 0) {
props.onFinish();
return;
}
setTimeout(() => setX(x - 1), 200);
});
return (<>
<Grid container spacing={3}>
<Grid item xs={12}>
<h1>Get Ready!</h1>
<h1>{x}</h1>
</Grid>
return (
<>
<Grid container spacing={3}>
<Grid item xs={12}>
<h1>Get Ready!</h1>
<h1>{x}</h1>
</Grid>
</>)
}
</Grid>
</>
);
}
+123 -88
View File
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
@@ -7,110 +7,145 @@ import { interpolate } from "./Difficulty";
import { getArrow } from "../utils";
interface Difficulty {
[key: string]: number;
timer: number;
width: number;
height: number;
symbols: number;
[key: string]: number;
timer: number;
width: number;
height: number;
symbols: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer: 12500, width: 3, height: 3, symbols: 6},
Normal: {timer: 15000, width: 4, height: 4, symbols: 7},
Hard: {timer: 12500, width: 5, height: 5, symbols: 8},
Impossible: {timer: 10000, width: 6, height: 6, symbols: 9},
}
Trivial: { timer: 12500, width: 3, height: 3, symbols: 6 },
Normal: { timer: 15000, width: 4, height: 4, symbols: 7 },
Hard: { timer: 12500, width: 5, height: 5, symbols: 8 },
Impossible: { timer: 10000, width: 6, height: 6, symbols: 9 },
};
export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer: 0, width: 0, height: 0, symbols: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [grid] = useState(generatePuzzle(difficulty));
const [answer] = useState(generateAnswer(grid, difficulty));
const [index, setIndex] = useState(0);
const [pos, setPos] = useState([0, 0]);
const difficulty: Difficulty = { timer: 0, width: 0, height: 0, symbols: 0 };
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [grid] = useState(generatePuzzle(difficulty));
const [answer] = useState(generateAnswer(grid, difficulty));
const [index, setIndex] = useState(0);
const [pos, setPos] = useState([0, 0]);
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
const move = [0, 0];
const arrow = getArrow(event);
switch(arrow) {
case "↑":
move[1]--;
break;
case "←":
move[0]--;
break;
case "↓":
move[1]++;
break;
case "→":
move[0]++;
break;
}
const next = [pos[0]+move[0], pos[1]+move[1]];
next[0] = (next[0]+grid[0].length)%grid[0].length;
next[1] = (next[1]+grid.length)%grid.length;
setPos(next);
if(event.keyCode == 32) {
const selected = grid[pos[1]][pos[0]];
const expected = answer[index];
if(selected !== expected) {
props.onFailure();
return;
}
setIndex(index+1);
if(answer.length === index+1) props.onSuccess();
}
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
const move = [0, 0];
const arrow = getArrow(event);
switch (arrow) {
case "↑":
move[1]--;
break;
case "←":
move[0]--;
break;
case "↓":
move[1]++;
break;
case "→":
move[0]++;
break;
}
const next = [pos[0] + move[0], pos[1] + move[1]];
next[0] = (next[0] + grid[0].length) % grid[0].length;
next[1] = (next[1] + grid.length) % grid.length;
setPos(next);
const fontSize = "2em";
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Match the symbols!</h1>
<h2 style={{fontSize: fontSize}}>Targets: {answer.map((a, i) => {
if(i == index)
return <span key={`${i}`} style={{fontSize: "1em", color: 'blue'}}>{a}&nbsp;</span>
return <span key={`${i}`} style={{fontSize: "1em"}}>{a}&nbsp;</span>
})}</h2>
if (event.keyCode == 32) {
const selected = grid[pos[1]][pos[0]];
const expected = answer[index];
if (selected !== expected) {
props.onFailure();
return;
}
setIndex(index + 1);
if (answer.length === index + 1) props.onSuccess();
}
}
const fontSize = "2em";
return (
<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Match the symbols!</h1>
<h2 style={{ fontSize: fontSize }}>
Targets:{" "}
{answer.map((a, i) => {
if (i == index)
return (
<span key={`${i}`} style={{ fontSize: "1em", color: "blue" }}>
{a}&nbsp;
</span>
);
return (
<span key={`${i}`} style={{ fontSize: "1em" }}>
{a}&nbsp;
</span>
);
})}
</h2>
<br />
{grid.map((line, y) => (
<div key={y}>
<pre>
{line.map((cell, x) => {
if (x == pos[0] && y == pos[1])
return (
<span
key={`${x}${y}`}
style={{ fontSize: fontSize, color: "blue" }}
>
{cell}&nbsp;
</span>
);
return (
<span key={`${x}${y}`} style={{ fontSize: fontSize }}>
{cell}&nbsp;
</span>
);
})}
</pre>
<br />
{grid.map((line, y) => <div key={y}><pre>{line.map((cell, x) => {
if(x == pos[0] && y == pos[1])
return <span key={`${x}${y}`} style={{fontSize: fontSize, color: 'blue'}}>{cell}&nbsp;</span>
return <span key={`${x}${y}`} style={{fontSize: fontSize}}>{cell}&nbsp;</span>
})}</pre><br /></div>)}
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>)
</div>
))}
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>
);
}
function generateAnswer(grid: string[][], difficulty: Difficulty): string[] {
const answer = [];
for(let i = 0; i < Math.round(difficulty.symbols); i++) {
answer.push(grid[Math.floor(Math.random()*grid.length)][Math.floor(Math.random()*grid[0].length)]);
}
return answer;
const answer = [];
for (let i = 0; i < Math.round(difficulty.symbols); i++) {
answer.push(
grid[Math.floor(Math.random() * grid.length)][
Math.floor(Math.random() * grid[0].length)
],
);
}
return answer;
}
function randChar(): string {
return "ABCDEF0123456789"[Math.floor(Math.random()*16)];
return "ABCDEF0123456789"[Math.floor(Math.random() * 16)];
}
function generatePuzzle(difficulty: Difficulty): string[][] {
const puzzle = [];
for(let i = 0; i < Math.round(difficulty.height); i++) {
const line = [];
for(let j = 0; j < Math.round(difficulty.width); j++) {
line.push(randChar()+randChar());
}
puzzle.push(line);
const puzzle = [];
for (let i = 0; i < Math.round(difficulty.height); i++) {
const line = [];
for (let j = 0; j < Math.round(difficulty.width); j++) {
line.push(randChar() + randChar());
}
return puzzle;
puzzle.push(line);
}
return puzzle;
}
+30 -22
View File
@@ -1,32 +1,40 @@
interface DifficultySetting {
[key: string]: number;
[key: string]: number;
}
interface DifficultySettings {
Trivial: DifficultySetting;
Normal: DifficultySetting;
Hard: DifficultySetting;
Impossible: DifficultySetting;
Trivial: DifficultySetting;
Normal: DifficultySetting;
Hard: DifficultySetting;
Impossible: DifficultySetting;
}
// I could use `any` to simply some of this but I also want to take advantage
// of the type safety that typescript provides. I'm just not sure how in this
// case.
export function interpolate(settings: DifficultySettings, n: number, out: DifficultySetting): DifficultySetting {
// interpolates between 2 difficulties.
function lerpD(a: DifficultySetting, b: DifficultySetting, t: number): DifficultySetting {
// interpolates between 2 numbers.
function lerp(x: number, y: number, t: number): number {
return (1 - t) * x + t * y;
}
for(const key of Object.keys(a)) {
out[key] = lerp(a[key], b[key], t);
}
return a;
export function interpolate(
settings: DifficultySettings,
n: number,
out: DifficultySetting,
): DifficultySetting {
// interpolates between 2 difficulties.
function lerpD(
a: DifficultySetting,
b: DifficultySetting,
t: number,
): DifficultySetting {
// interpolates between 2 numbers.
function lerp(x: number, y: number, t: number): number {
return (1 - t) * x + t * y;
}
if(n < 0) return lerpD(settings.Trivial, settings.Trivial, 0);
if(n >= 0 && n < 1) return lerpD(settings.Trivial, settings.Normal, n);
if(n >= 1 && n < 2) return lerpD(settings.Normal, settings.Hard, n-1);
if(n >= 2 && n < 3) return lerpD(settings.Hard, settings.Impossible, n-2);
return lerpD(settings.Impossible, settings.Impossible, 0);
}
for (const key of Object.keys(a)) {
out[key] = lerp(a[key], b[key], t);
}
return a;
}
if (n < 0) return lerpD(settings.Trivial, settings.Trivial, 0);
if (n >= 0 && n < 1) return lerpD(settings.Trivial, settings.Normal, n);
if (n >= 1 && n < 2) return lerpD(settings.Normal, settings.Hard, n - 1);
if (n >= 2 && n < 3) return lerpD(settings.Hard, settings.Impossible, n - 2);
return lerpD(settings.Impossible, settings.Impossible, 0);
}
+124 -99
View File
@@ -1,7 +1,7 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import { Countdown } from "./Countdown";
import { BracketGame } from "./BracketGame";
import { SlashGame } from "./SlashGame";
@@ -14,122 +14,147 @@ import { WireCuttingGame } from "./WireCuttingGame";
import { Victory } from "./Victory";
interface IProps {
Player: IPlayer;
Engine: IEngine;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
Player: IPlayer;
Engine: IEngine;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
}
enum Stage {
Countdown = 0,
Minigame,
Result,
Sell,
Countdown = 0,
Minigame,
Result,
Sell,
}
const minigames = [
SlashGame,
BracketGame,
BackwardGame,
BribeGame,
CheatCodeGame,
Cyberpunk2077Game,
MinesweeperGame,
WireCuttingGame,
]
SlashGame,
BracketGame,
BackwardGame,
BribeGame,
CheatCodeGame,
Cyberpunk2077Game,
MinesweeperGame,
WireCuttingGame,
];
export function Game(props: IProps): React.ReactElement {
const [level, setLevel] = useState(1);
const [stage, setStage] = useState(Stage.Countdown);
const [results, setResults] = useState('');
const [gameIds, setGameIds] = useState({
lastGames: [-1, -1],
id: Math.floor(Math.random()*minigames.length),
const [level, setLevel] = useState(1);
const [stage, setStage] = useState(Stage.Countdown);
const [results, setResults] = useState("");
const [gameIds, setGameIds] = useState({
lastGames: [-1, -1],
id: Math.floor(Math.random() * minigames.length),
});
function nextGameId(): number {
let id = gameIds.lastGames[0];
const ids = [gameIds.lastGames[0], gameIds.lastGames[1], gameIds.id];
while (ids.includes(id)) {
id = Math.floor(Math.random() * minigames.length);
}
return id;
}
function setupNextGame(): void {
setGameIds({
lastGames: [gameIds.lastGames[1], gameIds.id],
id: nextGameId(),
});
}
function nextGameId(): number {
let id = gameIds.lastGames[0];
const ids = [gameIds.lastGames[0], gameIds.lastGames[1], gameIds.id];
while(ids.includes(id)) {
id = Math.floor(Math.random()*minigames.length);
}
return id;
function success(): void {
pushResult(true);
if (level === props.MaxLevel) {
setStage(Stage.Sell);
} else {
setStage(Stage.Countdown);
setLevel(level + 1);
}
setupNextGame();
}
function pushResult(win: boolean): void {
setResults((old) => {
let next = old;
next += win ? "✓" : "✗";
if (next.length > 15) next = next.slice(1);
return next;
});
}
function setupNextGame(): void {
setGameIds({
lastGames: [gameIds.lastGames[1], gameIds.id],
id: nextGameId(),
})
function failure(options?: { automated: boolean }): void {
setStage(Stage.Countdown);
pushResult(false);
// Kill the player immediately if they use automation, so
// it's clear they're not meant to
const damage = options?.automated
? props.Player.hp
: props.StartingDifficulty * 3;
if (props.Player.takeDamage(damage)) {
const menu = document.getElementById("mainmenu-container");
if (menu === null) throw new Error("mainmenu-container not found");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
}
setupNextGame();
}
function success(): void {
pushResult(true);
if(level === props.MaxLevel) {
setStage(Stage.Sell);
} else {
setStage(Stage.Countdown);
setLevel(level+1);
}
setupNextGame();
}
function pushResult(win: boolean): void {
setResults(old => {
let next = old;
next += win ? "✓" : "✗";
if(next.length > 15) next = next.slice(1);
return next;
})
}
function failure(options?: { automated: boolean }): void {
setStage(Stage.Countdown);
pushResult(false);
// Kill the player immediately if they use automation, so
// it's clear they're not meant to
const damage = options?.automated ? props.Player.hp : props.StartingDifficulty*3;
if(props.Player.takeDamage(damage)) {
const menu = document.getElementById("mainmenu-container");
if(menu === null) throw new Error("mainmenu-container not found");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
}
setupNextGame();
}
let stageComponent: React.ReactNode;
switch(stage) {
let stageComponent: React.ReactNode;
switch (stage) {
case Stage.Countdown:
stageComponent = (<Countdown onFinish={() =>setStage(Stage.Minigame)} />);
break;
stageComponent = <Countdown onFinish={() => setStage(Stage.Minigame)} />;
break;
case Stage.Minigame: {
const MiniGame = minigames[gameIds.id];
stageComponent = (<MiniGame onSuccess={success} onFailure={failure} difficulty={props.Difficulty+level/50} />);
break;
const MiniGame = minigames[gameIds.id];
stageComponent = (
<MiniGame
onSuccess={success}
onFailure={failure}
difficulty={props.Difficulty + level / 50}
/>
);
break;
}
case Stage.Sell:
stageComponent = (<Victory Player={props.Player} Engine={props.Engine} StartingDifficulty={props.StartingDifficulty} Difficulty={props.Difficulty} MaxLevel={props.MaxLevel} />);
break;
}
stageComponent = (
<Victory
Player={props.Player}
Engine={props.Engine}
StartingDifficulty={props.StartingDifficulty}
Difficulty={props.Difficulty}
MaxLevel={props.MaxLevel}
/>
);
break;
}
function Progress(): React.ReactElement {
return (
<h4>
<span style={{ color: "gray" }}>
{results.slice(0, results.length - 1)}
</span>
{results[results.length - 1]}
</h4>
);
}
function Progress(): React.ReactElement {
return <h4><span style={{color: "gray"}}>{results.slice(0, results.length-1)}</span>{results[results.length-1]}</h4>
}
return (<>
<Grid container spacing={3}>
<Grid item xs={3}>
<h3>Level: {level}&nbsp;/&nbsp;{props.MaxLevel}</h3>
<Progress />
</Grid>
<Grid item xs={12}>
{stageComponent}
</Grid>
return (
<>
<Grid container spacing={3}>
<Grid item xs={3}>
<h3>
Level: {level}&nbsp;/&nbsp;{props.MaxLevel}
</h3>
<Progress />
</Grid>
</>)
}
<Grid item xs={12}>
{stageComponent}
</Grid>
</Grid>
</>
);
}
+26 -25
View File
@@ -1,7 +1,7 @@
import LinearProgress from '@material-ui/core/LinearProgress';
import React, { useState, useEffect } from 'react';
import LinearProgress from "@material-ui/core/LinearProgress";
import React, { useState, useEffect } from "react";
import { withStyles } from "@material-ui/core/styles";
import Grid from '@material-ui/core/Grid';
import Grid from "@material-ui/core/Grid";
const TimerProgress = withStyles(() => ({
bar: {
@@ -11,31 +11,32 @@ const TimerProgress = withStyles(() => ({
}))(LinearProgress);
interface IProps {
millis: number;
onExpire: () => void;
millis: number;
onExpire: () => void;
}
export function GameTimer(props: IProps): React.ReactElement {
const [v, setV] = useState(100);
const [v, setV] = useState(100);
const tick = 200;
useEffect(() => {
const intervalId = setInterval(() => {
setV(old => {
if(old <= 0) props.onExpire();
return old - tick / props.millis * 100;
});
}, tick);
return () => {
clearInterval(intervalId);
}
}, []);
const tick = 200;
useEffect(() => {
const intervalId = setInterval(() => {
setV((old) => {
if (old <= 0) props.onExpire();
return old - (tick / props.millis) * 100;
});
}, tick);
return () => {
clearInterval(intervalId);
};
}, []);
// https://stackoverflow.com/questions/55593367/disable-material-uis-linearprogress-animation
// TODO(hydroflame): there's like a bug where it triggers the end before the
// bar physically reaches the end
return (<Grid item xs={12}>
<TimerProgress variant="determinate" value={v} />
</Grid>);
// https://stackoverflow.com/questions/55593367/disable-material-uis-linearprogress-animation
// TODO(hydroflame): there's like a bug where it triggers the end before the
// bar physically reaches the end
return (
<Grid item xs={12}>
<TimerProgress variant="determinate" value={v} />
</Grid>
);
}
+7 -7
View File
@@ -1,8 +1,8 @@
export interface IMinigameProps {
onSuccess: () => void;
onFailure: (options?: {
/** Failed due to using untrusted events (automation) */
automated: boolean;
}) => void;
difficulty: number;
}
onSuccess: () => void;
onFailure: (options?: {
/** Failed due to using untrusted events (automation) */
automated: boolean;
}) => void;
difficulty: number;
}
+82 -61
View File
@@ -1,76 +1,97 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import React from 'react';
import React from "react";
import { StdButton } from "../../ui/React/StdButton";
import Grid from '@material-ui/core/Grid';
import Grid from "@material-ui/core/Grid";
interface IProps {
Player: IPlayer;
Engine: IEngine;
Location: string;
Difficulty: number;
MaxLevel: number;
start: () => void;
cancel: () => void;
Player: IPlayer;
Engine: IEngine;
Location: string;
Difficulty: number;
MaxLevel: number;
start: () => void;
cancel: () => void;
}
function arrowPart(color: string, length: number): JSX.Element {
let arrow = "";
if(length <= 0) length = 0;
else if(length > 13) length = 13;
else {
length--;
arrow = ">";
}
return <span style={{color: color}}>{"=".repeat(length)}{arrow}{" ".repeat(13-arrow.length-length)}</span>
let arrow = "";
if (length <= 0) length = 0;
else if (length > 13) length = 13;
else {
length--;
arrow = ">";
}
return (
<span style={{ color: color }}>
{"=".repeat(length)}
{arrow}
{" ".repeat(13 - arrow.length - length)}
</span>
);
}
function coloredArrow(difficulty: number): JSX.Element {
if(difficulty === 0) {
return <span style={{color: 'white'}}>{'>'}{" ".repeat(38)}</span>
} else {
return <>{arrowPart('white', difficulty*13)}{arrowPart('orange', (difficulty-1)*13)}{arrowPart('red', (difficulty-2)*13)}</>
}
if (difficulty === 0) {
return (
<span style={{ color: "white" }}>
{">"}
{" ".repeat(38)}
</span>
);
} else {
return (
<>
{arrowPart("white", difficulty * 13)}
{arrowPart("orange", (difficulty - 1) * 13)}
{arrowPart("red", (difficulty - 2) * 13)}
</>
);
}
}
export function Intro(props: IProps): React.ReactElement {
return (<>
<Grid container spacing={3}>
<Grid item xs={10}>
<h1>Infiltrating {props.Location}</h1>
</Grid>
<Grid item xs={10}>
<h2>Maximum level: {props.MaxLevel}</h2>
</Grid>
<Grid item xs={10}>
<pre>[{coloredArrow(props.Difficulty)}]</pre>
<pre> ^ ^ ^ ^</pre>
<pre> Trivial Normal Hard Impossible</pre>
</Grid>
<Grid item xs={10}>
<p>Infiltration is a series of short minigames that get
progressively harder. You take damage for failing them. Reaching
the maximum level rewards you with intel you can trade for money
or reputation.</p>
<br />
<p>The minigames you play are randomly selected. It might take you
few tries to get used to them.</p>
<br />
<p>No game require use of the mouse.</p>
<br />
<p>Spacebar is the default action/confirm button.</p>
<br />
<p>Everything that uses arrow can also use WASD</p>
<br />
<p>Sometimes the rest of the keyboard is used.</p>
</Grid>
<Grid item xs={3}>
<StdButton onClick={props.start} text={"Start"} />
</Grid>
<Grid item xs={3}>
<StdButton onClick={props.cancel} text={"Cancel"} />
</Grid>
return (
<>
<Grid container spacing={3}>
<Grid item xs={10}>
<h1>Infiltrating {props.Location}</h1>
</Grid>
</>)
}
<Grid item xs={10}>
<h2>Maximum level: {props.MaxLevel}</h2>
</Grid>
<Grid item xs={10}>
<pre>[{coloredArrow(props.Difficulty)}]</pre>
<pre> ^ ^ ^ ^</pre>
<pre> Trivial Normal Hard Impossible</pre>
</Grid>
<Grid item xs={10}>
<p>
Infiltration is a series of short minigames that get progressively
harder. You take damage for failing them. Reaching the maximum level
rewards you with intel you can trade for money or reputation.
</p>
<br />
<p>
The minigames you play are randomly selected. It might take you few
tries to get used to them.
</p>
<br />
<p>No game require use of the mouse.</p>
<br />
<p>Spacebar is the default action/confirm button.</p>
<br />
<p>Everything that uses arrow can also use WASD</p>
<br />
<p>Sometimes the rest of the keyboard is used.</p>
</Grid>
<Grid item xs={3}>
<StdButton onClick={props.start} text={"Start"} />
</Grid>
<Grid item xs={3}>
<StdButton onClick={props.cancel} text={"Cancel"} />
</Grid>
</Grid>
</>
);
}
+16 -16
View File
@@ -1,24 +1,24 @@
import React, { useEffect } from 'react';
import React, { useEffect } from "react";
interface IProps {
onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;
onFailure: (options?: { automated: boolean }) => void;
onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;
onFailure: (options?: { automated: boolean }) => void;
}
export function KeyHandler(props: IProps): React.ReactElement {
let elem: any;
useEffect(() => elem.focus());
let elem: any;
useEffect(() => elem.focus());
function onKeyDown(event: React.KeyboardEvent<HTMLElement>): void {
console.log("isTrusted?", event.isTrusted)
if(!event.isTrusted) {
console.log("untrusted event!")
props.onFailure({ automated: true });
return;
}
props.onKeyDown(event);
function onKeyDown(event: React.KeyboardEvent<HTMLElement>): void {
console.log("isTrusted?", event.isTrusted);
if (!event.isTrusted) {
console.log("untrusted event!");
props.onFailure({ automated: true });
return;
}
props.onKeyDown(event);
}
// invisible autofocused element that eats all the keypress for the minigames.
return (<div tabIndex={1} ref={c => elem = c} onKeyDown={onKeyDown} />)
}
// invisible autofocused element that eats all the keypress for the minigames.
return <div tabIndex={1} ref={(c) => (elem = c)} onKeyDown={onKeyDown} />;
}
+108 -99
View File
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import Grid from '@material-ui/core/Grid';
import React, { useState, useEffect } from "react";
import Grid from "@material-ui/core/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
@@ -7,123 +7,132 @@ import { interpolate } from "./Difficulty";
import { getArrow } from "../utils";
interface Difficulty {
[key: string]: number;
timer: number;
width: number;
height: number;
mines: number;
[key: string]: number;
timer: number;
width: number;
height: number;
mines: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer: 15000, width: 3, height: 3, mines: 4},
Normal: {timer: 15000, width: 4, height: 4, mines: 7},
Hard: {timer: 15000, width: 5, height: 5, mines: 11},
Impossible: {timer: 15000, width: 6, height: 6, mines: 15},
}
Trivial: { timer: 15000, width: 3, height: 3, mines: 4 },
Normal: { timer: 15000, width: 4, height: 4, mines: 7 },
Hard: { timer: 15000, width: 5, height: 5, mines: 11 },
Impossible: { timer: 15000, width: 6, height: 6, mines: 15 },
};
export function MinesweeperGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer: 0, width: 0, height: 0, mines: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [minefield] = useState(generateMinefield(difficulty));
const [answer, setAnswer] = useState(generateEmptyField(difficulty));
const [pos, setPos] = useState([0, 0]);
const [memoryPhase, setMemoryPhase] = useState(true);
const difficulty: Difficulty = { timer: 0, width: 0, height: 0, mines: 0 };
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [minefield] = useState(generateMinefield(difficulty));
const [answer, setAnswer] = useState(generateEmptyField(difficulty));
const [pos, setPos] = useState([0, 0]);
const [memoryPhase, setMemoryPhase] = useState(true);
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
if(memoryPhase) return;
const move = [0, 0];
const arrow = getArrow(event);
switch(arrow) {
case "↑":
move[1]--;
break;
case "←":
move[0]--;
break;
case "↓":
move[1]++;
break;
case "→":
move[0]++;
break;
}
const next = [pos[0]+move[0], pos[1]+move[1]];
next[0] = (next[0]+minefield[0].length)%minefield[0].length;
next[1] = (next[1]+minefield.length)%minefield.length;
setPos(next);
if(event.keyCode == 32) {
if(!minefield[pos[1]][pos[0]]) {
props.onFailure();
return;
}
setAnswer(old => {
old[pos[1]][pos[0]] = true;
if(fieldEquals(minefield, old)) props.onSuccess();
return old;
});
}
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
if (memoryPhase) return;
const move = [0, 0];
const arrow = getArrow(event);
switch (arrow) {
case "↑":
move[1]--;
break;
case "←":
move[0]--;
break;
case "↓":
move[1]++;
break;
case "→":
move[0]++;
break;
}
const next = [pos[0] + move[0], pos[1] + move[1]];
next[0] = (next[0] + minefield[0].length) % minefield[0].length;
next[1] = (next[1] + minefield.length) % minefield.length;
setPos(next);
useEffect(() => {
const id = setTimeout(() => setMemoryPhase(false), 2000);
return () => clearInterval(id);
}, []);
if (event.keyCode == 32) {
if (!minefield[pos[1]][pos[0]]) {
props.onFailure();
return;
}
setAnswer((old) => {
old[pos[1]][pos[0]] = true;
if (fieldEquals(minefield, old)) props.onSuccess();
return old;
});
}
}
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>{memoryPhase?"Remember all the mines!": "Mark all the mines!"}</h1>
{minefield.map((line, y) => <div key={y}><pre>{line.map((cell, x) => {
if(memoryPhase) {
if(minefield[y][x])
return <span key={x}>[?]&nbsp;</span>
return <span key={x}>[&nbsp;]&nbsp;</span>
useEffect(() => {
const id = setTimeout(() => setMemoryPhase(false), 2000);
return () => clearInterval(id);
}, []);
return (
<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>
{memoryPhase ? "Remember all the mines!" : "Mark all the mines!"}
</h1>
{minefield.map((line, y) => (
<div key={y}>
<pre>
{line.map((cell, x) => {
if (memoryPhase) {
if (minefield[y][x]) return <span key={x}>[?]&nbsp;</span>;
return <span key={x}>[&nbsp;]&nbsp;</span>;
} else {
if(x == pos[0] && y == pos[1])
return <span key={x}>[X]&nbsp;</span>
if(answer[y][x])
return <span key={x}>[.]&nbsp;</span>
return <span key={x}>[&nbsp;]&nbsp;</span>
if (x == pos[0] && y == pos[1])
return <span key={x}>[X]&nbsp;</span>;
if (answer[y][x]) return <span key={x}>[.]&nbsp;</span>;
return <span key={x}>[&nbsp;]&nbsp;</span>;
}
})}</pre><br /></div>)}
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>)
})}
</pre>
<br />
</div>
))}
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>
);
}
function fieldEquals(a: boolean[][], b: boolean[][]): boolean {
function count(field: boolean[][]): number {
return field.flat().reduce((a, b) => a + (b?1:0), 0);
}
return count(a) === count(b);
function count(field: boolean[][]): number {
return field.flat().reduce((a, b) => a + (b ? 1 : 0), 0);
}
return count(a) === count(b);
}
function generateEmptyField(difficulty: Difficulty): boolean[][] {
const field = [];
for(let i = 0; i < difficulty.height; i++) {
field.push((new Array(Math.round(difficulty.width))).fill(false));
}
return field;
const field = [];
for (let i = 0; i < difficulty.height; i++) {
field.push(new Array(Math.round(difficulty.width)).fill(false));
}
return field;
}
function generateMinefield(difficulty: Difficulty): boolean[][] {
const field = generateEmptyField(difficulty);
for(let i = 0; i < difficulty.mines; i++) {
const x = Math.floor(Math.random()*field.length);
const y = Math.floor(Math.random()*field[0].length);
if (field[x][y]) {
i--;
continue;
}
field[x][y] = true;
const field = generateEmptyField(difficulty);
for (let i = 0; i < difficulty.mines; i++) {
const x = Math.floor(Math.random() * field.length);
const y = Math.floor(Math.random() * field[0].length);
if (field[x][y]) {
i--;
continue;
}
return field;
field[x][y] = true;
}
return field;
}
+34 -30
View File
@@ -1,45 +1,49 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import React, { useState } from 'react';
import React, { useState } from "react";
import { Intro } from "./Intro";
import { Game } from "./Game";
interface IProps {
Player: IPlayer;
Engine: IEngine;
Location: string;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
Player: IPlayer;
Engine: IEngine;
Location: string;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
}
export function Root(props: IProps): React.ReactElement {
const [start, setStart] = useState(false);
const [start, setStart] = useState(false);
function cancel(): void {
const menu = document.getElementById("mainmenu-container");
if(menu === null) throw new Error("mainmenu-container not found");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
}
function cancel(): void {
const menu = document.getElementById("mainmenu-container");
if (menu === null) throw new Error("mainmenu-container not found");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
}
if(!start) {
return (<Intro
Player={props.Player}
Engine={props.Engine}
Location={props.Location}
Difficulty={props.Difficulty}
MaxLevel={props.MaxLevel}
start={() => setStart(true)}
cancel={cancel}
/>)
}
return (<Game
if (!start) {
return (
<Intro
Player={props.Player}
Engine={props.Engine}
StartingDifficulty={props.StartingDifficulty}
Location={props.Location}
Difficulty={props.Difficulty}
MaxLevel={props.MaxLevel}
/>);
}
start={() => setStart(true)}
cancel={cancel}
/>
);
}
return (
<Game
Player={props.Player}
Engine={props.Engine}
StartingDifficulty={props.StartingDifficulty}
Difficulty={props.Difficulty}
MaxLevel={props.MaxLevel}
/>
);
}
+48 -44
View File
@@ -1,60 +1,64 @@
import React, { useState, useEffect } from 'react';
import Grid from '@material-ui/core/Grid';
import React, { useState, useEffect } from "react";
import Grid from "@material-ui/core/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty";
interface Difficulty {
[key: string]: number;
window: number;
[key: string]: number;
window: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {window: 600},
Normal: {window: 325},
Hard: {window: 250},
Impossible: {window: 150},
}
Trivial: { window: 600 },
Normal: { window: 325 },
Hard: { window: 250 },
Impossible: { window: 150 },
};
export function SlashGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {window: 0};
interpolate(difficulties, props.difficulty, difficulty);
const [guarding, setGuarding] = useState(true);
const difficulty: Difficulty = { window: 0 };
interpolate(difficulties, props.difficulty, difficulty);
const [guarding, setGuarding] = useState(true);
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
if(event.keyCode !== 32) return;
if(guarding) {
props.onFailure();
} else {
props.onSuccess();
}
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
if (event.keyCode !== 32) return;
if (guarding) {
props.onFailure();
} else {
props.onSuccess();
}
}
useEffect(() => {
let id2 = -1;
const id = window.setTimeout(() => {
setGuarding(false);
id2 = window.setTimeout(()=>setGuarding(true), difficulty.window)
}, Math.random()*3250+1500);
return () => {
clearInterval(id);
if(id2 !== -1) clearInterval(id2);
}
}, []);
useEffect(() => {
let id2 = -1;
const id = window.setTimeout(() => {
setGuarding(false);
id2 = window.setTimeout(() => setGuarding(true), difficulty.window);
}, Math.random() * 3250 + 1500);
return () => {
clearInterval(id);
if (id2 !== -1) clearInterval(id2);
};
}, []);
return (<Grid container spacing={3}>
<GameTimer millis={5000} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Slash when his guard is down!</h1>
<p style={{fontSize: '5em'}}>{guarding ? "!Guarding!" : "!ATTACKING!"}</p>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>)
}
return (
<Grid container spacing={3}>
<GameTimer millis={5000} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Slash when his guard is down!</h1>
<p style={{ fontSize: "5em" }}>
{guarding ? "!Guarding!" : "!ATTACKING!"}
</p>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>
);
}
+91 -56
View File
@@ -1,77 +1,112 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { Factions } from "../../Faction/Factions";
import React, { useState } from 'react';
import React, { useState } from "react";
import { StdButton } from "../../ui/React/StdButton";
import Grid from '@material-ui/core/Grid';
import Grid from "@material-ui/core/Grid";
import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
interface IProps {
Player: IPlayer;
Engine: IEngine;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
Player: IPlayer;
Engine: IEngine;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
}
export function Victory(props: IProps): React.ReactElement {
const [faction, setFaction] = useState('none');
const [faction, setFaction] = useState("none");
function quitInfiltration(): void {
const menu = document.getElementById("mainmenu-container");
if(!menu) throw new Error('mainmenu-container somehow null');
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
}
function quitInfiltration(): void {
const menu = document.getElementById("mainmenu-container");
if (!menu) throw new Error("mainmenu-container somehow null");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
}
const levelBonus = props.MaxLevel*Math.pow(1.01, props.MaxLevel);
const levelBonus = props.MaxLevel * Math.pow(1.01, props.MaxLevel);
const repGain = Math.pow(props.Difficulty+1, 1.1)*
Math.pow(props.StartingDifficulty, 1.2)*
30*levelBonus*BitNodeMultipliers.InfiltrationRep;
const repGain =
Math.pow(props.Difficulty + 1, 1.1) *
Math.pow(props.StartingDifficulty, 1.2) *
30 *
levelBonus *
BitNodeMultipliers.InfiltrationRep;
const moneyGain = Math.pow(props.Difficulty+1, 2)*
Math.pow(props.StartingDifficulty, 3)*
3e3*levelBonus*BitNodeMultipliers.InfiltrationMoney;
const moneyGain =
Math.pow(props.Difficulty + 1, 2) *
Math.pow(props.StartingDifficulty, 3) *
3e3 *
levelBonus *
BitNodeMultipliers.InfiltrationMoney;
function sell(): void {
props.Player.gainMoney(moneyGain);
props.Player.recordMoneySource(moneyGain, 'infiltration');
quitInfiltration();
}
function sell(): void {
props.Player.gainMoney(moneyGain);
props.Player.recordMoneySource(moneyGain, "infiltration");
quitInfiltration();
}
function trade(): void {
if(faction === 'none') return;
Factions[faction].playerReputation += repGain;
quitInfiltration();
}
function trade(): void {
if (faction === "none") return;
Factions[faction].playerReputation += repGain;
quitInfiltration();
}
function changeDropdown(event: React.ChangeEvent<HTMLSelectElement>): void {
setFaction(event.target.value);
}
function changeDropdown(event: React.ChangeEvent<HTMLSelectElement>): void {
setFaction(event.target.value);
}
return (<>
<Grid container spacing={3}>
<Grid item xs={10}>
<h1>Infiltration successful!</h1>
</Grid>
<Grid item xs={10}>
<h2>You can trade the confidential information you found for money or reputation.</h2>
<select className={"dropdown"} onChange={changeDropdown}>
<option key={'none'} value={'none'}>{'none'}</option>
{props.Player.factions.filter(f => Factions[f].getInfo().offersWork()).map(f => <option key={f} value={f}>{f}</option>)}
</select>
<StdButton onClick={trade} text={<>{"Trade for "}{Reputation(repGain)}{" reputation"}</>} />
</Grid>
<Grid item xs={3}>
<StdButton onClick={sell} text={<>{"Sell for "}<Money money={moneyGain} /></>} />
</Grid>
<Grid item xs={3}>
<StdButton onClick={quitInfiltration} text={"Quit"} />
</Grid>
return (
<>
<Grid container spacing={3}>
<Grid item xs={10}>
<h1>Infiltration successful!</h1>
</Grid>
</>)
}
<Grid item xs={10}>
<h2>
You can trade the confidential information you found for money or
reputation.
</h2>
<select className={"dropdown"} onChange={changeDropdown}>
<option key={"none"} value={"none"}>
{"none"}
</option>
{props.Player.factions
.filter((f) => Factions[f].getInfo().offersWork())
.map((f) => (
<option key={f} value={f}>
{f}
</option>
))}
</select>
<StdButton
onClick={trade}
text={
<>
{"Trade for "}
{Reputation(repGain)}
{" reputation"}
</>
}
/>
</Grid>
<Grid item xs={3}>
<StdButton
onClick={sell}
text={
<>
{"Sell for "}
<Money money={moneyGain} />
</>
}
/>
</Grid>
<Grid item xs={3}>
<StdButton onClick={quitInfiltration} text={"Quit"} />
</Grid>
</Grid>
</>
);
}
+144 -131
View File
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import React, { useState } from "react";
import Grid from "@material-ui/core/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
@@ -7,165 +7,178 @@ import { random } from "../utils";
import { interpolate } from "./Difficulty";
interface Difficulty {
[key: string]: number;
timer: number;
wiresmin: number;
wiresmax: number;
rules: number;
[key: string]: number;
timer: number;
wiresmin: number;
wiresmax: number;
rules: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer: 9000, wiresmin: 4, wiresmax: 4, rules: 2},
Normal: {timer: 7000, wiresmin: 6, wiresmax: 6, rules: 2},
Hard: {timer: 5000, wiresmin: 8, wiresmax: 8, rules: 3},
Impossible: {timer: 4000, wiresmin: 9, wiresmax: 9, rules: 4},
}
Trivial: { timer: 9000, wiresmin: 4, wiresmax: 4, rules: 2 },
Normal: { timer: 7000, wiresmin: 6, wiresmax: 6, rules: 2 },
Hard: { timer: 5000, wiresmin: 8, wiresmax: 8, rules: 3 },
Impossible: { timer: 4000, wiresmin: 9, wiresmax: 9, rules: 4 },
};
const types = ["|", ".", "/", "-", "█", "#"];
const types = [
"|",
".",
"/",
"-",
"█",
"#",
]
const colors = [
"red",
"#FFC107",
"blue",
"white",
]
const colors = ["red", "#FFC107", "blue", "white"];
const colorNames: any = {
"red": "red",
"#FFC107": "yellow",
"blue": "blue",
"white": "white",
}
red: "red",
"#FFC107": "yellow",
blue: "blue",
white: "white",
};
interface Wire {
tpe: string;
colors: string[];
tpe: string;
colors: string[];
}
interface Question {
toString: () => string;
shouldCut: (wire: Wire, index: number) => boolean;
toString: () => string;
shouldCut: (wire: Wire, index: number) => boolean;
}
export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer: 0, wiresmin: 0, wiresmax: 0, rules: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [wires] = useState(generateWires(difficulty));
const [cutWires, setCutWires] = useState((new Array(wires.length)).fill(false));
const [questions] = useState(generateQuestion(wires, difficulty));
const difficulty: Difficulty = {
timer: 0,
wiresmin: 0,
wiresmax: 0,
rules: 0,
};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [wires] = useState(generateWires(difficulty));
const [cutWires, setCutWires] = useState(new Array(wires.length).fill(false));
const [questions] = useState(generateQuestion(wires, difficulty));
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
const wireNum = parseInt(event.key);
if(wireNum < 1 || wireNum > wires.length || isNaN(wireNum)) return;
setCutWires(old => {
const next = [...old];
next[wireNum-1] = true;
if(!questions.some((q => q.shouldCut(wires[wireNum-1], wireNum-1)))) {
props.onFailure();
}
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
const wireNum = parseInt(event.key);
// check if we won
const wiresToBeCut = [];
for(let j = 0; j < wires.length; j++) {
let shouldBeCut = false;
for(let i = 0; i < questions.length; i++) {
shouldBeCut = shouldBeCut || questions[i].shouldCut(wires[j], j)
}
wiresToBeCut.push(shouldBeCut);
}
if(wiresToBeCut.every((b, i) => b === next[i])) {
props.onSuccess();
}
if (wireNum < 1 || wireNum > wires.length || isNaN(wireNum)) return;
setCutWires((old) => {
const next = [...old];
next[wireNum - 1] = true;
if (
!questions.some((q) => q.shouldCut(wires[wireNum - 1], wireNum - 1))
) {
props.onFailure();
}
return next;
});
}
// check if we won
const wiresToBeCut = [];
for (let j = 0; j < wires.length; j++) {
let shouldBeCut = false;
for (let i = 0; i < questions.length; i++) {
shouldBeCut = shouldBeCut || questions[i].shouldCut(wires[j], j);
}
wiresToBeCut.push(shouldBeCut);
}
if (wiresToBeCut.every((b, i) => b === next[i])) {
props.onSuccess();
}
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Cut the wires with the following properties! (keyboard 1 to 9)</h1>
{questions.map((question, i) => <h3 key={i}>{question.toString()}</h3>)}
<pre>{(new Array(wires.length)).fill(0).map((_, i) => <span key={i}>&nbsp;{i+1}&nbsp;&nbsp;&nbsp;&nbsp;</span>)}</pre>
{(new Array(8)).fill(0).map((_, i) => <div key={i}>
<pre>
{wires.map((wire, j) => {
if((i === 3 || i === 4) && cutWires[j]) return <span key={j}>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>;
return <span key={j} style={{color: wire.colors[i%wire.colors.length]}}>|{wire.tpe}|&nbsp;&nbsp;&nbsp;</span>
})}
</pre>
</div>)}
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>)
return next;
});
}
return (
<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>
Cut the wires with the following properties! (keyboard 1 to 9)
</h1>
{questions.map((question, i) => (
<h3 key={i}>{question.toString()}</h3>
))}
<pre>
{new Array(wires.length).fill(0).map((_, i) => (
<span key={i}>&nbsp;{i + 1}&nbsp;&nbsp;&nbsp;&nbsp;</span>
))}
</pre>
{new Array(8).fill(0).map((_, i) => (
<div key={i}>
<pre>
{wires.map((wire, j) => {
if ((i === 3 || i === 4) && cutWires[j])
return (
<span key={j}>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
);
return (
<span
key={j}
style={{ color: wire.colors[i % wire.colors.length] }}
>
|{wire.tpe}|&nbsp;&nbsp;&nbsp;
</span>
);
})}
</pre>
</div>
))}
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>
);
}
function randomPositionQuestion(wires: Wire[]): Question {
const index = Math.floor(Math.random() * wires.length);
return {
toString: (): string => {
return `Cut wires number ${index+1}.`;
},
shouldCut: (wire: Wire, i: number): boolean => {
return index === i;
},
}
const index = Math.floor(Math.random() * wires.length);
return {
toString: (): string => {
return `Cut wires number ${index + 1}.`;
},
shouldCut: (wire: Wire, i: number): boolean => {
return index === i;
},
};
}
function randomColorQuestion(wires: Wire[]): Question {
const index = Math.floor(Math.random() * wires.length);
const cutColor = wires[index].colors[0];
return {
toString: (): string => {
return `Cut all wires colored ${colorNames[cutColor]}.`;
},
shouldCut: (wire: Wire): boolean => {
return wire.colors.includes(cutColor);
},
}
const index = Math.floor(Math.random() * wires.length);
const cutColor = wires[index].colors[0];
return {
toString: (): string => {
return `Cut all wires colored ${colorNames[cutColor]}.`;
},
shouldCut: (wire: Wire): boolean => {
return wire.colors.includes(cutColor);
},
};
}
function generateQuestion(wires: Wire[], difficulty: Difficulty): Question[] {
const numQuestions = difficulty.rules;
const questionGenerators = [
randomPositionQuestion,
randomColorQuestion,
]
const questions = [];
for(let i = 0; i < numQuestions; i++) {
questions.push(questionGenerators[i%2](wires));
}
return questions;
const numQuestions = difficulty.rules;
const questionGenerators = [randomPositionQuestion, randomColorQuestion];
const questions = [];
for (let i = 0; i < numQuestions; i++) {
questions.push(questionGenerators[i % 2](wires));
}
return questions;
}
function generateWires(difficulty: Difficulty): Wire[] {
const wires = [];
const numWires = random(difficulty.wiresmin, difficulty.wiresmax);
for(let i = 0; i < numWires; i++) {
const wireColors = [colors[Math.floor(Math.random()*colors.length)]];
if(Math.random() < 0.15) {
wireColors.push(colors[Math.floor(Math.random()*colors.length)]);
}
wires.push({
tpe: types[Math.floor(Math.random()*types.length)],
colors: wireColors,
});
const wires = [];
const numWires = random(difficulty.wiresmin, difficulty.wiresmax);
for (let i = 0; i < numWires; i++) {
const wireColors = [colors[Math.floor(Math.random() * colors.length)]];
if (Math.random() < 0.15) {
wireColors.push(colors[Math.floor(Math.random() * colors.length)]);
}
return wires;
}
wires.push({
tpe: types[Math.floor(Math.random() * types.length)],
colors: wireColors,
});
}
return wires;
}