bitburner-src/src/Infiltration/ui/WireCuttingGame.tsx

219 lines
6.9 KiB
TypeScript
Raw Normal View History

import React, { useEffect, useState } from "react";
2022-04-25 01:51:30 +02:00
import { Box, Paper, Typography } from "@mui/material";
import { AugmentationName } from "@enums";
import { Player } from "@player";
import { Settings } from "../../Settings/Settings";
2022-04-25 01:51:30 +02:00
import { KEY } from "../../utils/helpers/keyCodes";
import { random } from "../utils";
import { interpolate } from "./Difficulty";
import { GameTimer } from "./GameTimer";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { isPositiveInteger } from "../../types";
interface Difficulty {
2021-09-05 01:09:30 +02:00
[key: string]: number;
timer: number;
wiresmin: number;
wiresmax: number;
rules: number;
}
const difficulties: {
2021-09-05 01:09:30 +02:00
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
2021-09-05 01:09:30 +02:00
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 = [KEY.PIPE, KEY.DOT, KEY.FORWARD_SLASH, KEY.HYPHEN, "█", KEY.HASH];
2021-09-05 01:09:30 +02:00
const colors = ["red", "#FFC107", "blue", "white"];
2022-07-15 07:51:30 +02:00
const colorNames: Record<string, string> = {
2021-09-05 01:09:30 +02:00
red: "red",
"#FFC107": "yellow",
blue: "blue",
white: "white",
};
interface Wire {
wireType: string;
2021-09-05 01:09:30 +02:00
colors: string[];
}
interface Question {
2021-09-05 01:09:30 +02:00
toString: () => string;
shouldCut: (wire: Wire, index: number) => boolean;
}
export function WireCuttingGame({ onSuccess, onFailure, difficulty }: IMinigameProps): React.ReactElement {
const [questions, setQuestions] = useState<Question[]>([]);
const [wires, setWires] = useState<Wire[]>([]);
const [timer, setTimer] = useState(0);
const [cutWires, setCutWires] = useState<boolean[]>([]);
const [wiresToCut, setWiresToCut] = useState(new Set<number>());
const [hasAugment, setHasAugment] = useState(false);
2021-09-05 01:09:30 +02:00
useEffect(() => {
// Determine game difficulty
const gameDifficulty: Difficulty = {
timer: 0,
wiresmin: 0,
wiresmax: 0,
rules: 0,
};
interpolate(difficulties, difficulty, gameDifficulty);
// Calculate initial game data
const gameWires = generateWires(gameDifficulty);
const gameQuestions = generateQuestion(gameWires, gameDifficulty);
const gameWiresToCut = new Set<number>();
gameWires.forEach((wire, index) => {
for (const question of gameQuestions) {
if (question.shouldCut(wire, index)) {
gameWiresToCut.add(index);
return; // go to next wire
}
}
});
// Initialize the game state
setTimer(gameDifficulty.timer);
setWires(gameWires);
setCutWires(gameWires.map((__) => false));
setQuestions(gameQuestions);
setWiresToCut(gameWiresToCut);
setHasAugment(Player.hasAugmentation(AugmentationName.KnowledgeOfApollo, true));
}, [difficulty]);
2021-09-25 04:15:19 +02:00
function press(this: Document, event: KeyboardEvent): void {
2021-09-05 01:09:30 +02:00
event.preventDefault();
const wireNum = parseInt(event.key);
if (!isPositiveInteger(wireNum) || wireNum > wires.length) return;
2021-09-05 01:09:30 +02:00
const wireIndex = wireNum - 1;
if (cutWires[wireIndex]) return;
2021-09-05 01:09:30 +02:00
// Check if game has been lost
if (!wiresToCut.has(wireIndex)) return onFailure();
// Check if game has been won
const newWiresToCut = new Set(wiresToCut);
newWiresToCut.delete(wireIndex);
if (newWiresToCut.size === 0) return onSuccess();
// Rerender with new state if game has not been won or lost yet
const newCutWires = cutWires.map((old, i) => (i === wireIndex ? true : old));
setWiresToCut(newWiresToCut);
setCutWires(newCutWires);
2021-09-05 01:09:30 +02:00
}
return (
2022-04-24 23:50:19 +02:00
<>
<GameTimer millis={timer} onExpire={onFailure} />
2022-04-24 23:50:19 +02:00
<Paper sx={{ display: "grid", justifyItems: "center", pb: 1 }}>
<Typography variant="h4" sx={{ width: "75%", textAlign: "center" }}>
Cut the wires with the following properties! (keyboard 1 to 9)
</Typography>
{questions.map((question, i) => (
2021-10-01 19:36:59 +02:00
<Typography key={i}>{question.toString()}</Typography>
2021-09-05 01:09:30 +02:00
))}
2022-04-24 23:50:19 +02:00
<Box
sx={{
display: "grid",
gridTemplateColumns: `repeat(${wires.length}, 1fr)`,
2022-04-24 23:50:19 +02:00
columnGap: 3,
justifyItems: "center",
}}
>
{Array.from({ length: wires.length }).map((_, i) => {
const isCorrectWire = cutWires[i + 1] || wiresToCut.has(i + 1);
2022-04-22 21:30:49 +02:00
const color = hasAugment && !isCorrectWire ? Settings.theme.disabled : Settings.theme.primary;
return (
2022-04-24 23:50:19 +02:00
<Typography key={i} style={{ color: color }}>
{i + 1}
</Typography>
2022-04-22 21:30:49 +02:00
);
})}
2022-04-24 23:50:19 +02:00
{new Array(8).fill(0).map((_, i) => (
<React.Fragment key={i}>
{wires.map((wire, j) => {
if ((i === 3 || i === 4) && cutWires[j]) {
2022-04-24 23:50:19 +02:00
return <Typography key={j}></Typography>;
}
const isCorrectWire = cutWires[j + 1] || wiresToCut.has(j + 1);
const wireColor =
hasAugment && !isCorrectWire ? Settings.theme.disabled : wire.colors[i % wire.colors.length];
2021-09-05 01:09:30 +02:00
return (
2022-04-24 23:50:19 +02:00
<Typography key={j} style={{ color: wireColor }}>
|{wire.wireType}|
2022-04-24 23:50:19 +02:00
</Typography>
2021-09-05 01:09:30 +02:00
);
})}
2022-04-24 23:50:19 +02:00
</React.Fragment>
))}
</Box>
<KeyHandler onKeyDown={press} onFailure={onFailure} />
2022-04-24 23:50:19 +02:00
</Paper>
</>
2021-09-05 01:09:30 +02:00
);
}
function randomPositionQuestion(wires: Wire[]): Question {
2021-09-05 01:09:30 +02:00
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 {
2021-09-05 01:09:30 +02:00
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[] {
2021-09-05 01:09:30 +02:00
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[] {
2021-09-05 01:09:30 +02:00
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)]);
}
2021-09-05 01:09:30 +02:00
wires.push({
wireType: types[Math.floor(Math.random() * types.length)],
2021-09-05 01:09:30 +02:00
colors: wireColors,
});
}
return wires;
}