2023-06-29 19:22:10 +02:00
|
|
|
import React, { useEffect, useState } from "react";
|
2023-06-27 04:29:44 +02:00
|
|
|
|
2022-04-25 01:51:30 +02:00
|
|
|
import { Box, Paper, Typography } from "@mui/material";
|
2023-06-12 06:34:20 +02:00
|
|
|
import { AugmentationName } from "@enums";
|
2022-10-10 00:42:14 +02:00
|
|
|
import { Player } from "@player";
|
2022-03-23 15:31:56 +01:00
|
|
|
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";
|
2023-06-29 19:22:10 +02:00
|
|
|
import { isPositiveInteger } from "../../types";
|
2021-06-13 17:05:40 +02:00
|
|
|
|
|
|
|
interface Difficulty {
|
2021-09-05 01:09:30 +02:00
|
|
|
[key: string]: number;
|
|
|
|
timer: number;
|
|
|
|
wiresmin: number;
|
|
|
|
wiresmax: number;
|
|
|
|
rules: number;
|
2021-06-13 17:05:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const difficulties: {
|
2021-09-05 01:09:30 +02:00
|
|
|
Trivial: Difficulty;
|
|
|
|
Normal: Difficulty;
|
|
|
|
Hard: Difficulty;
|
|
|
|
Impossible: Difficulty;
|
2021-06-13 17:05:40 +02:00
|
|
|
} = {
|
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 },
|
|
|
|
};
|
2021-06-13 17:05:40 +02:00
|
|
|
|
2022-03-23 15:31:56 +01:00
|
|
|
const types = [KEY.PIPE, KEY.DOT, KEY.FORWARD_SLASH, KEY.HYPHEN, "█", KEY.HASH];
|
2021-06-13 17:05:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
const colors = ["red", "#FFC107", "blue", "white"];
|
2021-06-13 17:05:40 +02:00
|
|
|
|
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",
|
|
|
|
};
|
2021-06-13 17:05:40 +02:00
|
|
|
|
|
|
|
interface Wire {
|
2023-06-29 19:22:10 +02:00
|
|
|
wireType: string;
|
2021-09-05 01:09:30 +02:00
|
|
|
colors: string[];
|
2021-06-13 17:05:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
interface Question {
|
2021-09-05 01:09:30 +02:00
|
|
|
toString: () => string;
|
|
|
|
shouldCut: (wire: Wire, index: number) => boolean;
|
2021-06-13 17:05:40 +02:00
|
|
|
}
|
|
|
|
|
2023-06-29 19:22:10 +02:00
|
|
|
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
|
|
|
|
2022-03-26 03:50:28 +01:00
|
|
|
useEffect(() => {
|
2023-06-29 19:22:10 +02:00
|
|
|
// 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
|
|
|
|
}
|
2022-03-26 03:50:28 +01:00
|
|
|
}
|
2023-06-29 19:22:10 +02:00
|
|
|
});
|
2023-06-27 04:29:44 +02:00
|
|
|
|
2023-06-29 19:22:10 +02:00
|
|
|
// Initialize the game state
|
|
|
|
setTimer(gameDifficulty.timer);
|
|
|
|
setWires(gameWires);
|
|
|
|
setCutWires(gameWires.map((__) => false));
|
|
|
|
setQuestions(gameQuestions);
|
|
|
|
setWiresToCut(gameWiresToCut);
|
|
|
|
setHasAugment(Player.hasAugmentation(AugmentationName.KnowledgeOfApollo, true));
|
|
|
|
}, [difficulty]);
|
2022-03-26 03:50:28 +01:00
|
|
|
|
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);
|
2023-06-29 19:22:10 +02:00
|
|
|
if (!isPositiveInteger(wireNum) || wireNum > wires.length) return;
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2023-06-29 19:22:10 +02:00
|
|
|
const wireIndex = wireNum - 1;
|
|
|
|
if (cutWires[wireIndex]) return;
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2023-06-29 19:22:10 +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
|
|
|
<>
|
2023-06-27 04:29:44 +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>
|
2023-06-29 19:22:10 +02:00
|
|
|
{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",
|
2023-06-29 19:22:10 +02:00
|
|
|
gridTemplateColumns: `repeat(${wires.length}, 1fr)`,
|
2022-04-24 23:50:19 +02:00
|
|
|
columnGap: 3,
|
|
|
|
justifyItems: "center",
|
|
|
|
}}
|
|
|
|
>
|
2023-06-29 19:22:10 +02:00
|
|
|
{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}>
|
2023-06-29 19:22:10 +02:00
|
|
|
{wires.map((wire, j) => {
|
2022-03-23 15:31:56 +01:00
|
|
|
if ((i === 3 || i === 4) && cutWires[j]) {
|
2022-04-24 23:50:19 +02:00
|
|
|
return <Typography key={j}></Typography>;
|
2022-03-23 15:31:56 +01:00
|
|
|
}
|
2023-06-29 19:22:10 +02:00
|
|
|
const isCorrectWire = cutWires[j + 1] || wiresToCut.has(j + 1);
|
2022-03-23 15:31:56 +01:00
|
|
|
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 }}>
|
2023-06-29 19:22:10 +02:00
|
|
|
|{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>
|
2023-06-27 04:29:44 +02:00
|
|
|
<KeyHandler onKeyDown={press} onFailure={onFailure} />
|
2022-04-24 23:50:19 +02:00
|
|
|
</Paper>
|
|
|
|
</>
|
2021-09-05 01:09:30 +02:00
|
|
|
);
|
2021-06-13 17:05:40 +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;
|
|
|
|
},
|
|
|
|
};
|
2021-06-13 17:05:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
},
|
|
|
|
};
|
2021-06-13 17:05:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2021-06-13 17:05:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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-06-13 17:05:40 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
wires.push({
|
2023-06-29 19:22:10 +02:00
|
|
|
wireType: types[Math.floor(Math.random() * types.length)],
|
2021-09-05 01:09:30 +02:00
|
|
|
colors: wireColors,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return wires;
|
|
|
|
}
|