beep
This commit is contained in:
Generated
+15783
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "disic",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/plugin-transform-private-property-in-object": "^7.24.1",
|
||||||
|
"@emotion/react": "^11.11.4",
|
||||||
|
"@emotion/styled": "^11.11.5",
|
||||||
|
"@mui/icons-material": "^5.15.15",
|
||||||
|
"@mui/material": "^5.15.15",
|
||||||
|
"mui-color-input": "^2.0.3",
|
||||||
|
"nanoid": "^5.0.7",
|
||||||
|
"rainbowvis.js": "^1.1.1",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-scripts": "5.0.1",
|
||||||
|
"typescript": "^4.9.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jest": "^27.5.2",
|
||||||
|
"@types/node": "^16.18.96",
|
||||||
|
"@types/react": "^18.2.79",
|
||||||
|
"@types/react-dom": "^18.2.25",
|
||||||
|
"madge": "^7.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject",
|
||||||
|
"dependency-test": "npx madge --circular --extensions ts ./src"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Web site created using create-react-app"
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>Disic</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
import { GameContextProvider, GameScreen } from "./internal";
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<GameContextProvider>
|
||||||
|
<GameScreen />
|
||||||
|
</GameContextProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import { CellSetupConfig } from "../../internal";
|
||||||
|
|
||||||
|
class Cell {
|
||||||
|
constructor({ canvasX, canvasY, x, y }: CellSetupConfig) {
|
||||||
|
this.canvasX = canvasX;
|
||||||
|
this.canvasY = canvasY;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.id = nanoid();
|
||||||
|
}
|
||||||
|
canvasX: number;
|
||||||
|
canvasY: number;
|
||||||
|
config: { [key: string]: any };
|
||||||
|
currentColor: string;
|
||||||
|
id: string;
|
||||||
|
nextColor: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
setCurrentColor(newColor: string) {
|
||||||
|
this.currentColor = newColor;
|
||||||
|
}
|
||||||
|
setNextColor(newColor: string) {
|
||||||
|
this.nextColor = newColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Cell;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import Cell from "./Cell";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export { Cell };
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface CellSetupConfig {
|
||||||
|
canvasX: number;
|
||||||
|
canvasY: number;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import {
|
||||||
|
Cell,
|
||||||
|
clearCanvas,
|
||||||
|
createHexRows,
|
||||||
|
createSquareRows,
|
||||||
|
drawHexagon,
|
||||||
|
drawSquare,
|
||||||
|
getNeighbors,
|
||||||
|
} from "../../internal";
|
||||||
|
import { GridConfig } from "./types";
|
||||||
|
|
||||||
|
class Grid {
|
||||||
|
constructor(config: GridConfig) {
|
||||||
|
this.config = config;
|
||||||
|
const { cellShape, loops } = this.config;
|
||||||
|
this.setupCells();
|
||||||
|
this.getNeighbors = (cell: Cell) =>
|
||||||
|
getNeighbors({ cellShape, loops, rows: this.rows }, cell);
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
config: GridConfig;
|
||||||
|
ctx: CanvasRenderingContext2D = null;
|
||||||
|
rows: Cell[][] = [];
|
||||||
|
getNeighbors: (cell: Cell) => Cell[];
|
||||||
|
init() {
|
||||||
|
this.config.ruleset.init(this);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
iterateCells(fn: (cell: Cell, x?: number, y?: number) => void) {
|
||||||
|
this.rows.forEach((row, x) => row.forEach((cell, y) => fn(cell, x, y)));
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const cells: Cell[] = [];
|
||||||
|
this.iterateCells((cell) => {
|
||||||
|
cells.push(cell);
|
||||||
|
});
|
||||||
|
// sorting by color improves performance by reducing the number of times the fillStyle changes
|
||||||
|
const sortedCells = [...cells].sort((a, b) =>
|
||||||
|
a.currentColor > b.currentColor ? 1 : -1
|
||||||
|
);
|
||||||
|
sortedCells.forEach((cell) => {
|
||||||
|
switch (this.config.cellShape) {
|
||||||
|
case "hex":
|
||||||
|
drawHexagon(cell, this.ctx, this.config.cellSize);
|
||||||
|
break;
|
||||||
|
case "square":
|
||||||
|
drawSquare(cell, this.ctx, this.config.cellSize);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
reset() {
|
||||||
|
clearCanvas();
|
||||||
|
this.setupCells();
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
setupCells() {
|
||||||
|
let rows = [];
|
||||||
|
switch (this.config.cellShape) {
|
||||||
|
case "hex":
|
||||||
|
rows = createHexRows(this.config);
|
||||||
|
break;
|
||||||
|
case "square":
|
||||||
|
rows = createSquareRows(this.config);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.rows = rows;
|
||||||
|
}
|
||||||
|
update() {
|
||||||
|
this.config.ruleset.update(this);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Grid;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import Grid from "./Grid";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export { Grid };
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Ruleset } from "../../internal";
|
||||||
|
|
||||||
|
export type CellShape = "hex" | "square";
|
||||||
|
|
||||||
|
export interface GridConfig {
|
||||||
|
cellShape: CellShape;
|
||||||
|
cellSize: number;
|
||||||
|
height: number;
|
||||||
|
loops: boolean;
|
||||||
|
ruleset: Ruleset;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { GlobalConfig } from "../../context";
|
||||||
|
import { GameSpecificConfig } from "./types";
|
||||||
|
|
||||||
|
abstract class Ruleset {
|
||||||
|
constructor(config?: GameSpecificConfig) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
config?: GameSpecificConfig;
|
||||||
|
defaultGlobalSettings?: GlobalConfig;
|
||||||
|
init: (arg0: any) => void;
|
||||||
|
update: (arg0: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Ruleset;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import Ruleset from "./Ruleset";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export { Ruleset };
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export type GameSpecificConfig = { [key: string]: any };
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./Grid";
|
||||||
|
export * from "./Ruleset";
|
||||||
|
export * from "./Cell";
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { CanvasProps } from "./types";
|
||||||
|
import { useContext, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
GameContext,
|
||||||
|
getCanvasSize,
|
||||||
|
getContext,
|
||||||
|
useCanvas,
|
||||||
|
} from "../../internal";
|
||||||
|
|
||||||
|
const Canvas = (props: CanvasProps) => {
|
||||||
|
const { canvasAttributes } = props;
|
||||||
|
const { currentGame } = useContext(GameContext);
|
||||||
|
const { grid, globalConfigs } = useContext(GameContext);
|
||||||
|
const { throttleAmount } = globalConfigs[currentGame];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (grid && grid.ctx === null) {
|
||||||
|
grid.ctx = getContext();
|
||||||
|
}
|
||||||
|
}, [grid]);
|
||||||
|
|
||||||
|
const draw = (frameCount: number) => {
|
||||||
|
if (frameCount % throttleAmount !== 0) return;
|
||||||
|
grid?.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
const canvasRef = useCanvas(draw);
|
||||||
|
const { height, width } = getCanvasSize();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
{...canvasAttributes}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Canvas;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import Canvas from "./Canvas";
|
||||||
|
export * from "./types";
|
||||||
|
export { Canvas };
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { AllHTMLAttributes } from "react";
|
||||||
|
|
||||||
|
export interface CanvasProps {
|
||||||
|
canvasAttributes: AllHTMLAttributes<HTMLCanvasElement>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Grid,
|
||||||
|
IconButton,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import { GameSpecificControls } from "./GameSpecificControls";
|
||||||
|
import { GlobalControls } from "./GlobalControls";
|
||||||
|
import { ControlsProps } from "./types";
|
||||||
|
import { ControlsContext } from "./context";
|
||||||
|
import { GameContext, GlobalConfig } from "../../context";
|
||||||
|
|
||||||
|
const Controls = ({ closeDrawer }: ControlsProps) => {
|
||||||
|
const { currentGame } = useContext(GameContext);
|
||||||
|
const {
|
||||||
|
globalConfigsTouched,
|
||||||
|
newGlobalConfigs,
|
||||||
|
setGlobalConfigsTouched,
|
||||||
|
setNewGlobalConfigs,
|
||||||
|
start,
|
||||||
|
} = useContext(ControlsContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box p={5}>
|
||||||
|
<Grid container alignItems="center" justifyContent="flex-end">
|
||||||
|
<Grid item>
|
||||||
|
<IconButton onClick={closeDrawer} size="large">
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
item
|
||||||
|
md={6}
|
||||||
|
xs={12}
|
||||||
|
spacing={2}
|
||||||
|
alignContent="flex-start"
|
||||||
|
>
|
||||||
|
<GlobalControls
|
||||||
|
config={newGlobalConfigs[currentGame]}
|
||||||
|
setConfig={(newConfig: GlobalConfig) => {
|
||||||
|
setNewGlobalConfigs({
|
||||||
|
...newGlobalConfigs,
|
||||||
|
[currentGame]: newConfig,
|
||||||
|
});
|
||||||
|
setGlobalConfigsTouched({
|
||||||
|
...globalConfigsTouched,
|
||||||
|
[currentGame]: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
item
|
||||||
|
md={6}
|
||||||
|
xs={12}
|
||||||
|
spacing={2}
|
||||||
|
alignContent="flex-start"
|
||||||
|
>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography>Game-Specific Settings</Typography>
|
||||||
|
<Divider />
|
||||||
|
</Grid>
|
||||||
|
<GameSpecificControls />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
onClick={() => {
|
||||||
|
start(closeDrawer);
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
Start!
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Controls;
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import {
|
||||||
|
Grid,
|
||||||
|
TextField,
|
||||||
|
MenuItem,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
FormGroup,
|
||||||
|
FormControlLabel,
|
||||||
|
Checkbox,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { MuiColorInput } from "mui-color-input";
|
||||||
|
import {
|
||||||
|
ConwayConfig,
|
||||||
|
GameContext,
|
||||||
|
Preset,
|
||||||
|
RulesetName,
|
||||||
|
} from "../../../../internal";
|
||||||
|
import { presetDisplayNameMap } from "./const";
|
||||||
|
import { ConwayColorProp } from "./types";
|
||||||
|
import { useGameSpecificOptions } from "../hooks";
|
||||||
|
|
||||||
|
const conwayColorProps = [
|
||||||
|
"liveColor",
|
||||||
|
"deadColor",
|
||||||
|
"startingEnvelopeColor",
|
||||||
|
"finalEnvelopeColor",
|
||||||
|
] as ConwayColorProp[];
|
||||||
|
|
||||||
|
const ConwayControls = () => {
|
||||||
|
const { grid } = useContext(GameContext);
|
||||||
|
const { config, handleUpdate } = useGameSpecificOptions<ConwayConfig>(
|
||||||
|
RulesetName.CONWAY
|
||||||
|
);
|
||||||
|
|
||||||
|
const neighborNumbers =
|
||||||
|
grid.config.cellShape === "hex"
|
||||||
|
? [0, 1, 2, 3, 4, 5, 6]
|
||||||
|
: [0, 1, 2, 3, 4, 5, 6, 7, 8];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Preset"
|
||||||
|
onChange={(e) => {
|
||||||
|
handleUpdate({ ...config, preset: e.target.value as Preset });
|
||||||
|
}}
|
||||||
|
select
|
||||||
|
value={config.preset}
|
||||||
|
>
|
||||||
|
{Object.keys(Preset).map((preset) => (
|
||||||
|
<MenuItem key={preset} value={Preset[preset]}>
|
||||||
|
{presetDisplayNameMap[Preset[preset]]}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Average % Alive at Start"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) =>
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
liveStartPercent: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={config.liveStartPercent}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<FormControl component="fieldset" variant="standard">
|
||||||
|
<FormLabel component="legend">
|
||||||
|
Live Neighbors Required (Survival)
|
||||||
|
</FormLabel>
|
||||||
|
<FormGroup row>
|
||||||
|
{neighborNumbers.map((num) => (
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={config.neighborsNeededToSurvive.includes(num)}
|
||||||
|
onChange={(e) => {
|
||||||
|
let newNeighborsNeededToSurvive = [
|
||||||
|
...config.neighborsNeededToSurvive,
|
||||||
|
];
|
||||||
|
if (e.target.checked) {
|
||||||
|
newNeighborsNeededToSurvive.push(num);
|
||||||
|
} else {
|
||||||
|
newNeighborsNeededToSurvive =
|
||||||
|
newNeighborsNeededToSurvive.filter(
|
||||||
|
(val) => val !== num
|
||||||
|
);
|
||||||
|
}
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
neighborsNeededToSurvive: newNeighborsNeededToSurvive,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
name={num.toString()}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={num.toString()}
|
||||||
|
key={`conway-survival-${num}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</FormGroup>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<FormControl component="fieldset" variant="standard">
|
||||||
|
<FormLabel component="legend">
|
||||||
|
Live Neighbors Required (Reproduction)
|
||||||
|
</FormLabel>
|
||||||
|
<FormGroup row>
|
||||||
|
{neighborNumbers.map((num) => (
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={config.neighborsNeededToReproduce.includes(num)}
|
||||||
|
onChange={(e) => {
|
||||||
|
let newNeighborsNeededToReproduce = [
|
||||||
|
...config.neighborsNeededToReproduce,
|
||||||
|
];
|
||||||
|
if (e.target.checked) {
|
||||||
|
newNeighborsNeededToReproduce.push(num);
|
||||||
|
} else {
|
||||||
|
newNeighborsNeededToReproduce =
|
||||||
|
newNeighborsNeededToReproduce.filter(
|
||||||
|
(val) => val !== num
|
||||||
|
);
|
||||||
|
}
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
neighborsNeededToReproduce:
|
||||||
|
newNeighborsNeededToReproduce,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
name={num.toString()}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={num.toString()}
|
||||||
|
key={`conway-reproduction-${num}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</FormGroup>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={6} xs={12}>
|
||||||
|
<FormGroup row>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={config.showEnvelope}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
showEnvelope: e.target.checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
name={"show-envelope-checkbox"}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={"Show Envelope"}
|
||||||
|
/>
|
||||||
|
{config.showEnvelope && (
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={config.showEnvelopeGradient}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
showEnvelopeGradient: e.target.checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
name={"envelope-gradient-checkbox"}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={"Envelope Gradient"}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FormGroup>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={6} xs={12}>
|
||||||
|
{config.showEnvelope && config.showEnvelopeGradient && (
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Envelope Gradient Steps"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) =>
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
envelopeGradientSteps: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={config.envelopeGradientSteps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
<Grid container item xs={12} spacing={2}>
|
||||||
|
{conwayColorProps.map((color: ConwayColorProp) => {
|
||||||
|
if (
|
||||||
|
(!config.showEnvelope && color === "startingEnvelopeColor") ||
|
||||||
|
((!config.showEnvelope || !config.showEnvelopeGradient) &&
|
||||||
|
color === "finalEnvelopeColor")
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
let label = "Alive Color";
|
||||||
|
if (color === "deadColor") {
|
||||||
|
label = "Dead Color";
|
||||||
|
} else if (color === "startingEnvelopeColor") {
|
||||||
|
label = config.showEnvelopeGradient
|
||||||
|
? "Initial Envelope Color"
|
||||||
|
: "Envelope Color";
|
||||||
|
} else if (color === "finalEnvelopeColor") {
|
||||||
|
label = "Final Envelope Color";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Grid item key={`conway-color-config-${color}`}>
|
||||||
|
<MuiColorInput
|
||||||
|
format="hex"
|
||||||
|
label={label}
|
||||||
|
value={config[color]}
|
||||||
|
onChange={(newColor) =>
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
[color]: newColor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={6} xs={12}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={config.mortalCells}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
mortalCells: e.target.checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
name={"mortal-cells-checkbox"}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={"Mortal Cells (Cells Die After X Ticks)"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={6} xs={12}>
|
||||||
|
{config.mortalCells && (
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Cell Lifespan"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) =>
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
cellLifespan: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={config.cellLifespan}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConwayControls;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { Preset } from "../../../../internal";
|
||||||
|
|
||||||
|
export const presetDisplayNameMap = {
|
||||||
|
[Preset.DEFAULT]: "Random Distribution",
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import ConwayControls from "./ConwayControls";
|
||||||
|
|
||||||
|
export { ConwayControls };
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export type ConwayColorProp =
|
||||||
|
| "liveColor"
|
||||||
|
| "deadColor"
|
||||||
|
| "startingEnvelopeColor"
|
||||||
|
| "finalEnvelopeColor";
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { Grid, Typography, Button } from "@mui/material";
|
||||||
|
import {
|
||||||
|
defaultGameOptions,
|
||||||
|
GameContext,
|
||||||
|
RulesetName,
|
||||||
|
} from "../../../internal";
|
||||||
|
import { ConwayControls } from "./ConwayControls";
|
||||||
|
import { PokemonControls } from "./PokemonControls";
|
||||||
|
import { RockPaperScissorsControls } from "./RockPaperScissorsControls";
|
||||||
|
import { MazeControls } from "./MazeControls";
|
||||||
|
import { SnowflakeControls } from "./SnowflakeControls";
|
||||||
|
import { WarControls } from "./WarControls.ts";
|
||||||
|
import { WaterFlowControls } from "./WaterFlowControls";
|
||||||
|
import { useContext, useMemo } from "react";
|
||||||
|
import { ControlsContext } from "../context";
|
||||||
|
|
||||||
|
const GameSpecificControls = () => {
|
||||||
|
const { currentGame } = useContext(GameContext);
|
||||||
|
const { newGameSpecificConfigs, setNewGameSpecificConfigs } =
|
||||||
|
useContext(ControlsContext);
|
||||||
|
|
||||||
|
const Component = useMemo(() => {
|
||||||
|
switch (currentGame) {
|
||||||
|
case RulesetName.CONWAY:
|
||||||
|
return () => <ConwayControls />;
|
||||||
|
case RulesetName.MAZE_GENERATOR:
|
||||||
|
return () => <MazeControls />;
|
||||||
|
case RulesetName.POKEMON:
|
||||||
|
return () => <PokemonControls />;
|
||||||
|
case RulesetName.ROCK_PAPER_SCISSORS:
|
||||||
|
return () => <RockPaperScissorsControls />;
|
||||||
|
case RulesetName.SNOWFLAKE:
|
||||||
|
return () => <SnowflakeControls />;
|
||||||
|
case RulesetName.WAR:
|
||||||
|
return () => <WarControls />;
|
||||||
|
case RulesetName.WATER_FLOW:
|
||||||
|
return () => <WaterFlowControls />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [currentGame]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Component === null ? (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography>
|
||||||
|
No settings have been developed for this game yet.
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
) : (
|
||||||
|
<Component />
|
||||||
|
)}
|
||||||
|
{Component !== null && (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setNewGameSpecificConfigs({
|
||||||
|
...newGameSpecificConfigs,
|
||||||
|
[currentGame]: defaultGameOptions[currentGame],
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset to Default
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GameSpecificControls;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { Grid } from "@mui/material";
|
||||||
|
import { MuiColorInput } from "mui-color-input";
|
||||||
|
import { MazeConfig, RulesetName } from "../../../../internal";
|
||||||
|
import { MazeColorProp } from "./types";
|
||||||
|
import { useGameSpecificOptions } from "../hooks";
|
||||||
|
|
||||||
|
const mazeColorProps = ["liveColor", "deadColor"] as MazeColorProp[];
|
||||||
|
|
||||||
|
const MazeControls = () => {
|
||||||
|
const { config, handleUpdate } = useGameSpecificOptions<MazeConfig>(
|
||||||
|
RulesetName.MAZE_GENERATOR
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid container item xs={12} spacing={2}>
|
||||||
|
{mazeColorProps.map((color: MazeColorProp) => {
|
||||||
|
let label = "Wall Color";
|
||||||
|
if (color === "deadColor") {
|
||||||
|
label = "Background Color";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Grid item key={`maze-color-config-${color}`}>
|
||||||
|
<MuiColorInput
|
||||||
|
format="hex"
|
||||||
|
label={label}
|
||||||
|
value={config[color]}
|
||||||
|
onChange={(newColor) =>
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
[color]: newColor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MazeControls;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import MazeControls from "./MazeControls";
|
||||||
|
|
||||||
|
export { MazeControls };
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export type MazeColorProp = "liveColor" | "deadColor";
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
Grid,
|
||||||
|
TextField,
|
||||||
|
FormControlLabel,
|
||||||
|
Checkbox,
|
||||||
|
FormGroup,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
MenuItem,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { MuiColorInput } from "mui-color-input";
|
||||||
|
import {
|
||||||
|
PokemonGameConfig,
|
||||||
|
PokemonType,
|
||||||
|
RulesetName,
|
||||||
|
} from "../../../../internal";
|
||||||
|
import { useGameSpecificOptions } from "../hooks";
|
||||||
|
|
||||||
|
const PokemonControls = () => {
|
||||||
|
const { config, handleUpdate } = useGameSpecificOptions<PokemonGameConfig>(
|
||||||
|
RulesetName.POKEMON
|
||||||
|
);
|
||||||
|
const [typeToChangeColor, setTypeToChangeColor] = useState<PokemonType>(
|
||||||
|
PokemonType.BUG
|
||||||
|
);
|
||||||
|
const { allowedTypes, typeColors } = config;
|
||||||
|
const allTypesAllowed =
|
||||||
|
allowedTypes.length === Object.keys(PokemonType).length;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Mutation Odds (1 in X)"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) =>
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
randomMutationChance: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={config.randomMutationChance}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid container item xs={12}>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Enable / Disable Pokemon Types</FormLabel>
|
||||||
|
<FormGroup row>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={allTypesAllowed}
|
||||||
|
onChange={() => {
|
||||||
|
let newTypes = Object.keys(PokemonType).map(
|
||||||
|
(key) => PokemonType[key]
|
||||||
|
);
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
allowedTypes: allTypesAllowed ? [] : newTypes,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
name={`enable-type-select-all-checkbox`}
|
||||||
|
value={!allTypesAllowed}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={`${allTypesAllowed ? "Deselect" : "Select"} All`}
|
||||||
|
/>
|
||||||
|
{Object.keys(PokemonType).map((key) => {
|
||||||
|
const type = PokemonType[key];
|
||||||
|
return (
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={allowedTypes.includes(type)}
|
||||||
|
onChange={(e) => {
|
||||||
|
let newTypes = [...allowedTypes];
|
||||||
|
if (e.target.checked) {
|
||||||
|
newTypes.push(type);
|
||||||
|
} else {
|
||||||
|
newTypes = newTypes.filter(
|
||||||
|
(newType) => newType !== type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
allowedTypes: newTypes,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
name={`enable-type-${type}-checkbox`}
|
||||||
|
value={allowedTypes.includes(type)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
key={`pokemon-type-form-${key}`}
|
||||||
|
label={type.toString()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</FormGroup>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={6} xs={12}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Change Color of Type"
|
||||||
|
onChange={(e) => {
|
||||||
|
setTypeToChangeColor(e.target.value as PokemonType);
|
||||||
|
}}
|
||||||
|
select
|
||||||
|
value={typeToChangeColor}
|
||||||
|
>
|
||||||
|
{Object.keys(PokemonType).map((key) => (
|
||||||
|
<MenuItem key={key} value={PokemonType[key]}>
|
||||||
|
{PokemonType[key]}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={6} xs={12}>
|
||||||
|
<MuiColorInput
|
||||||
|
format="hex"
|
||||||
|
label={`${typeToChangeColor} Color`}
|
||||||
|
value={typeColors[typeToChangeColor]}
|
||||||
|
onChange={(newColor) =>
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
typeColors: { ...typeColors, [typeToChangeColor]: newColor },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PokemonControls;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import PokemonControls from "./PokemonControls";
|
||||||
|
|
||||||
|
export { PokemonControls };
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
import { Grid } from "@mui/material";
|
||||||
|
import { MuiColorInput } from "mui-color-input";
|
||||||
|
import { RPSGameConfig, RulesetName } from "../../../../internal";
|
||||||
|
import { useGameSpecificOptions } from "../hooks";
|
||||||
|
|
||||||
|
const RockPaperScissorsControls = () => {
|
||||||
|
const { config, handleUpdate } = useGameSpecificOptions<RPSGameConfig>(
|
||||||
|
RulesetName.ROCK_PAPER_SCISSORS
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{["rockColor", "paperColor", "scissorsColor"].map(
|
||||||
|
(color: keyof RPSGameConfig, i) => (
|
||||||
|
<Grid item xs={12} key={`rpg-color-config-${color}`}>
|
||||||
|
<MuiColorInput
|
||||||
|
format="hex"
|
||||||
|
label={`Color ${i + 1}`}
|
||||||
|
value={config[color]}
|
||||||
|
onChange={(newColor) =>
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
[color]: newColor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RockPaperScissorsControls;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import RockPaperScissorsControls from "./RockPaperScissorsControls";
|
||||||
|
|
||||||
|
export { RockPaperScissorsControls };
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { Grid } from "@mui/material";
|
||||||
|
import { MuiColorInput } from "mui-color-input";
|
||||||
|
import { SnowflakeGameConfig, RulesetName } from "../../../../internal";
|
||||||
|
import { SnowflakeColorProp } from "./types";
|
||||||
|
import { useGameSpecificOptions } from "../hooks";
|
||||||
|
|
||||||
|
const mazeColorProps = ["liveColor", "deadColor"] as SnowflakeColorProp[];
|
||||||
|
|
||||||
|
const SnowflakeControls = () => {
|
||||||
|
const { config, handleUpdate } = useGameSpecificOptions<SnowflakeGameConfig>(
|
||||||
|
RulesetName.SNOWFLAKE
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid container item xs={12} spacing={2}>
|
||||||
|
{mazeColorProps.map((color: SnowflakeColorProp) => {
|
||||||
|
let label = "Alive Color";
|
||||||
|
if (color === "deadColor") {
|
||||||
|
label = "Dead Color";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Grid item key={`snowflake-color-config-${color}`}>
|
||||||
|
<MuiColorInput
|
||||||
|
format="hex"
|
||||||
|
label={label}
|
||||||
|
value={config[color]}
|
||||||
|
onChange={(newColor) =>
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
[color]: newColor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SnowflakeControls;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import SnowflakeControls from "./SnowflakeControls";
|
||||||
|
|
||||||
|
export { SnowflakeControls };
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export type SnowflakeColorProp = "liveColor" | "deadColor";
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { Grid, IconButton, Typography } from "@mui/material";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
|
import { MuiColorInput } from "mui-color-input";
|
||||||
|
import {
|
||||||
|
RulesetName,
|
||||||
|
WarGameConfig,
|
||||||
|
getRandomColor,
|
||||||
|
} from "../../../../internal";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useGameSpecificOptions } from "../hooks";
|
||||||
|
|
||||||
|
const WarControls = () => {
|
||||||
|
const { config, handleUpdate } = useGameSpecificOptions<WarGameConfig>(
|
||||||
|
RulesetName.WAR
|
||||||
|
);
|
||||||
|
const [newArmyColor, setNewArmyColor] = useState<string>(getRandomColor());
|
||||||
|
const { factionColors } = config;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid container item xs={12} spacing={2}>
|
||||||
|
{factionColors.map((color, i) => {
|
||||||
|
return (
|
||||||
|
<Grid item key={`army-${color}`} alignItems="center">
|
||||||
|
<Typography sx={{ color, display: "inline" }}>
|
||||||
|
Army {i + 1}
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
const newFactions = factionColors.filter(
|
||||||
|
(existingColor) => existingColor !== color
|
||||||
|
);
|
||||||
|
handleUpdate({ factionColors: newFactions });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
<Grid container item xs={12} alignItems="center">
|
||||||
|
<Grid item>
|
||||||
|
<MuiColorInput
|
||||||
|
format="hex"
|
||||||
|
label={"New Army Color"}
|
||||||
|
value={newArmyColor}
|
||||||
|
onChange={(newColor) => setNewArmyColor(newColor)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
const newFactions = [...factionColors, newArmyColor];
|
||||||
|
handleUpdate({ factionColors: newFactions });
|
||||||
|
setNewArmyColor(getRandomColor());
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WarControls;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import WarControls from "./WarControls";
|
||||||
|
|
||||||
|
export { WarControls };
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
Checkbox,
|
||||||
|
FormControlLabel,
|
||||||
|
Grid,
|
||||||
|
IconButton,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
|
import { MuiColorInput } from "mui-color-input";
|
||||||
|
import {
|
||||||
|
RulesetName,
|
||||||
|
WaterFlowGameConfig,
|
||||||
|
getRandomColor,
|
||||||
|
} from "../../../../internal";
|
||||||
|
import { useGameSpecificOptions } from "../hooks";
|
||||||
|
|
||||||
|
const WaterFlowControls = () => {
|
||||||
|
const [newWaterColor, setNewWaterColor] = useState(getRandomColor());
|
||||||
|
const { config, handleUpdate } = useGameSpecificOptions<WaterFlowGameConfig>(
|
||||||
|
RulesetName.WATER_FLOW
|
||||||
|
);
|
||||||
|
const { waterColors } = config;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid container item xs={12} spacing={2}>
|
||||||
|
<Grid item>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Average Number of Starting Streams"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) =>
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
averageStartingWaterCells: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={config.averageStartingWaterCells}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Branching-Off Chance"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) =>
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
branchingChance: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={config.branchingChance}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Rock Health Variation"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) =>
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
rockHealthVariance: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={config.rockHealthVariance}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={config.trueRandom}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
trueRandom: e.target.checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
name={"true-random-checkbox"}
|
||||||
|
value={config.trueRandom}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={"Random Flow Direction"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid container item xs={12} spacing={2} alignItems="center">
|
||||||
|
<Grid item>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={config.blurBackground}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
blurBackground: e.target.checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
name={"blur-background-checkbox"}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={"Blur Background"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
{config.blurBackground && (
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Blur Amount"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) =>
|
||||||
|
handleUpdate({
|
||||||
|
...config,
|
||||||
|
backgroundBlurAmount: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={config.backgroundBlurAmount}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid container item xs={12} spacing={2}>
|
||||||
|
{waterColors.map((color, i) => {
|
||||||
|
return (
|
||||||
|
<Grid item key={`water-color-${color}`} alignItems="center">
|
||||||
|
<Typography sx={{ color, display: "inline" }}>
|
||||||
|
Color {i + 1}
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
const newWaterColors = waterColors.filter(
|
||||||
|
(existingColor) => existingColor !== color
|
||||||
|
);
|
||||||
|
handleUpdate({ ...config, waterColors: newWaterColors });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
<Grid container item xs={12} spacing={2} alignItems="center">
|
||||||
|
<Grid item>
|
||||||
|
<MuiColorInput
|
||||||
|
format="hex"
|
||||||
|
label={"New Water Color"}
|
||||||
|
value={newWaterColor}
|
||||||
|
onChange={(newColor) => setNewWaterColor(newColor)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
const newWaterColors = [...waterColors, newWaterColor];
|
||||||
|
handleUpdate({ ...config, waterColors: newWaterColors });
|
||||||
|
setNewWaterColor(getRandomColor());
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<MuiColorInput
|
||||||
|
format="hex"
|
||||||
|
label={"Rock Color"}
|
||||||
|
value={config.baseBackgroundColor}
|
||||||
|
onChange={(newColor) =>
|
||||||
|
handleUpdate({ ...config, baseBackgroundColor: newColor })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WaterFlowControls;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import WaterFlowControls from "./WaterFlowControls";
|
||||||
|
|
||||||
|
export { WaterFlowControls };
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as useGameSpecificOptions } from "./useGameSpecificOptions";
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { useCallback, useContext, useMemo } from "react";
|
||||||
|
import { ControlsContext } from "../../context";
|
||||||
|
import { RulesetName } from "../../../../internal";
|
||||||
|
|
||||||
|
const useGameSpecificOptions = <T>(rulesetName: RulesetName) => {
|
||||||
|
const { newGameSpecificConfigs, setNewGameSpecificConfigs } =
|
||||||
|
useContext(ControlsContext);
|
||||||
|
|
||||||
|
const config = newGameSpecificConfigs[rulesetName] as T;
|
||||||
|
|
||||||
|
const handleUpdate = useCallback(
|
||||||
|
(newConfig: T) => {
|
||||||
|
setNewGameSpecificConfigs({
|
||||||
|
...newGameSpecificConfigs,
|
||||||
|
[rulesetName]: newConfig,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[newGameSpecificConfigs, setNewGameSpecificConfigs]
|
||||||
|
);
|
||||||
|
|
||||||
|
return useMemo(() => ({ config, handleUpdate }), [config, handleUpdate]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useGameSpecificOptions;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import GameSpecificControls from "./GameSpecificControls";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export { GameSpecificControls };
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { GameSpecificConfig, RulesetName } from "../../../internal";
|
||||||
|
|
||||||
|
export type UpdateConfigFn = (
|
||||||
|
prop: RulesetName,
|
||||||
|
value: GameSpecificConfig
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export interface GameSpecificControlsProps {}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import {
|
||||||
|
Grid,
|
||||||
|
Typography,
|
||||||
|
Divider,
|
||||||
|
TextField,
|
||||||
|
MenuItem,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
RadioGroup,
|
||||||
|
FormControlLabel,
|
||||||
|
Radio,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
CellShape,
|
||||||
|
defaultCellSize,
|
||||||
|
defaultGlobalConfigs,
|
||||||
|
defaultThrottleAmount,
|
||||||
|
GameContext,
|
||||||
|
GameDescription,
|
||||||
|
rulesetDisplayNameMap,
|
||||||
|
RulesetName,
|
||||||
|
} from "../../../internal";
|
||||||
|
import { GlobalControlsProps } from "./types";
|
||||||
|
import { useContext } from "react";
|
||||||
|
|
||||||
|
const GlobalControls = ({ config, setConfig }: GlobalControlsProps) => {
|
||||||
|
const { currentGame, setCurrentGame } = useContext(GameContext);
|
||||||
|
const { gridConfig, throttleAmount } = config;
|
||||||
|
|
||||||
|
const updateGridConfig = (prop: string, value: any) => {
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
gridConfig: { ...gridConfig, [prop]: value },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography>Global Settings</Typography>
|
||||||
|
<Divider />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
helperText={<GameDescription rulesetName={currentGame} />}
|
||||||
|
label="Game Mode"
|
||||||
|
onChange={(e) => {
|
||||||
|
setCurrentGame(e.target.value as RulesetName);
|
||||||
|
}}
|
||||||
|
select
|
||||||
|
value={currentGame}
|
||||||
|
>
|
||||||
|
{Object.keys(RulesetName).map((name) => {
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} value={name}>
|
||||||
|
{rulesetDisplayNameMap[name]}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={6} xs={12}>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Cell Shape</FormLabel>
|
||||||
|
<RadioGroup
|
||||||
|
row
|
||||||
|
name="cell-shape-buttons-group"
|
||||||
|
onChange={(e) => {
|
||||||
|
updateGridConfig(
|
||||||
|
"cellShape",
|
||||||
|
(e.target as HTMLInputElement).value as CellShape
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
value={gridConfig.cellShape}
|
||||||
|
>
|
||||||
|
<FormControlLabel
|
||||||
|
value="hex"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Hexagonal"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value="square"
|
||||||
|
control={<Radio />}
|
||||||
|
label="Square"
|
||||||
|
/>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={6} xs={12}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Cell Size"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) =>
|
||||||
|
updateGridConfig(
|
||||||
|
"cellSize",
|
||||||
|
parseInt(e.target.value) ?? defaultCellSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value={gridConfig.cellSize}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={6} xs={12}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={gridConfig.loops}
|
||||||
|
onChange={(e) => {
|
||||||
|
updateGridConfig("loops", e.target.checked);
|
||||||
|
}}
|
||||||
|
name={`enable-type-select-all-checkbox`}
|
||||||
|
value={gridConfig.loops}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={"Wrap Grid"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={6} xs={12}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
helperText="WARNING: Low throttle amounts may create patterns of flashing lights."
|
||||||
|
label="Animation Throttle Amount"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) =>
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
throttleAmount: parseInt(e.target.value) ?? defaultThrottleAmount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={throttleAmount}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Button onClick={() => setConfig(defaultGlobalConfigs[currentGame])}>
|
||||||
|
Reset to Default
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GlobalControls;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import GlobalControls from "./GlobalControls";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export { GlobalControls };
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
import { GlobalConfig } from "../../../internal";
|
||||||
|
|
||||||
|
export interface GlobalControlsProps {
|
||||||
|
config: GlobalConfig;
|
||||||
|
setConfig: Dispatch<SetStateAction<GlobalConfig>>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
const ResetGameDefaultsButton = () => {};
|
||||||
|
|
||||||
|
export default ResetGameDefaultsButton;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import ResetGameDefaultsButton from "./ResetGameDefaultsButton";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export { ResetGameDefaultsButton };
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export interface ResetGameDefaultsButtonProps {}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
const ResetGlobalDefaultsButton = () => {};
|
||||||
|
|
||||||
|
export default ResetGlobalDefaultsButton;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import ResetGlobalDefaultsButton from "./ResetGlobalDefaultsButton";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export { ResetGlobalDefaultsButton };
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export interface ResetGlobalDefaultsButtonProps {}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
import { ControlsContextType } from "./types";
|
||||||
|
|
||||||
|
const ControlsContext = createContext<ControlsContextType>(null);
|
||||||
|
|
||||||
|
export default ControlsContext;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import useControlsContext from "./useControlsContext";
|
||||||
|
import ControlsContext from "./ControlsContext";
|
||||||
|
|
||||||
|
const ControlsContextProvider = ({ children }) => {
|
||||||
|
const contextValue = useControlsContext();
|
||||||
|
return (
|
||||||
|
<ControlsContext.Provider value={contextValue}>
|
||||||
|
{children}
|
||||||
|
</ControlsContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ControlsContextProvider;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import ControlsContext from "./ControlsContext";
|
||||||
|
import ControlsContextProvider from "./ControlsContextProvider";
|
||||||
|
import useControlsContext from "./useControlsContext";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export { ControlsContext, ControlsContextProvider, useControlsContext };
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
import {
|
||||||
|
GameSpecificConfigs,
|
||||||
|
GlobalConfigs,
|
||||||
|
RulesetName,
|
||||||
|
} from "../../../internal";
|
||||||
|
|
||||||
|
export type GlobalConfigsTouched = {
|
||||||
|
[key in RulesetName]: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ControlsContextType {
|
||||||
|
globalConfigsTouched: GlobalConfigsTouched;
|
||||||
|
newGameSpecificConfigs: GameSpecificConfigs;
|
||||||
|
newGlobalConfigs: GlobalConfigs;
|
||||||
|
setGlobalConfigsTouched: Dispatch<SetStateAction<GlobalConfigsTouched>>;
|
||||||
|
setNewGameSpecificConfigs: Dispatch<SetStateAction<GameSpecificConfigs>>;
|
||||||
|
setNewGlobalConfigs: Dispatch<SetStateAction<GlobalConfigs>>;
|
||||||
|
start: (closeDrawer: () => void) => void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { useCallback, useContext, useMemo, useState } from "react";
|
||||||
|
import { ControlsContextType, GlobalConfigsTouched } from "./types";
|
||||||
|
import {
|
||||||
|
GameSpecificConfigs,
|
||||||
|
GameContext,
|
||||||
|
GlobalConfigs,
|
||||||
|
RulesetName,
|
||||||
|
clearCanvas,
|
||||||
|
createGrid,
|
||||||
|
} from "../../../internal";
|
||||||
|
|
||||||
|
let initialGlobalConfigsTouched = {};
|
||||||
|
Object.keys(RulesetName).forEach(
|
||||||
|
(key) => (initialGlobalConfigsTouched[RulesetName[key]] = false)
|
||||||
|
);
|
||||||
|
|
||||||
|
const useControlsContext = (): ControlsContextType => {
|
||||||
|
const {
|
||||||
|
currentGame,
|
||||||
|
gameSpecificConfigs,
|
||||||
|
globalConfigs,
|
||||||
|
paused,
|
||||||
|
setGameSpecificConfigs,
|
||||||
|
setGlobalConfigs,
|
||||||
|
setGrid,
|
||||||
|
togglePause,
|
||||||
|
} = useContext(GameContext);
|
||||||
|
const [newGlobalConfigs, setNewGlobalConfigs] = useState<GlobalConfigs>({
|
||||||
|
...globalConfigs,
|
||||||
|
});
|
||||||
|
const [newGameSpecificConfigs, setNewGameSpecificConfigs] =
|
||||||
|
useState<GameSpecificConfigs>({
|
||||||
|
...gameSpecificConfigs,
|
||||||
|
});
|
||||||
|
const [globalConfigsTouched, setGlobalConfigsTouched] =
|
||||||
|
useState<GlobalConfigsTouched>();
|
||||||
|
|
||||||
|
const start = useCallback(
|
||||||
|
(closeDrawer: () => void) => {
|
||||||
|
setGlobalConfigs({ ...newGlobalConfigs });
|
||||||
|
setGameSpecificConfigs({ ...newGameSpecificConfigs });
|
||||||
|
clearCanvas();
|
||||||
|
setGrid(
|
||||||
|
createGrid(
|
||||||
|
currentGame,
|
||||||
|
newGlobalConfigs[currentGame],
|
||||||
|
newGameSpecificConfigs[currentGame]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (paused) togglePause();
|
||||||
|
closeDrawer();
|
||||||
|
},
|
||||||
|
[
|
||||||
|
currentGame,
|
||||||
|
newGameSpecificConfigs,
|
||||||
|
newGlobalConfigs,
|
||||||
|
paused,
|
||||||
|
setGameSpecificConfigs,
|
||||||
|
setGlobalConfigs,
|
||||||
|
setGrid,
|
||||||
|
togglePause,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
return {
|
||||||
|
globalConfigsTouched,
|
||||||
|
newGameSpecificConfigs,
|
||||||
|
newGlobalConfigs,
|
||||||
|
setGlobalConfigsTouched,
|
||||||
|
setNewGameSpecificConfigs,
|
||||||
|
setNewGlobalConfigs,
|
||||||
|
start,
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
globalConfigsTouched,
|
||||||
|
newGameSpecificConfigs,
|
||||||
|
newGlobalConfigs,
|
||||||
|
setGlobalConfigsTouched,
|
||||||
|
setNewGameSpecificConfigs,
|
||||||
|
setNewGlobalConfigs,
|
||||||
|
start,
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useControlsContext;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import Controls from "./Controls";
|
||||||
|
|
||||||
|
export { Controls };
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface ControlsProps {
|
||||||
|
closeDrawer: () => void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { Link } from "@mui/material";
|
||||||
|
import { RulesetName } from "../../internal";
|
||||||
|
import { GameDescriptionProps } from "./types";
|
||||||
|
|
||||||
|
const GameDescription = ({ rulesetName }: GameDescriptionProps) => {
|
||||||
|
switch (rulesetName) {
|
||||||
|
case RulesetName.CONWAY:
|
||||||
|
return <>The iconic cellular automaton.</>;
|
||||||
|
case RulesetName.MAZE_GENERATOR:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
An interesting pattern discovered due to a bug while recreating
|
||||||
|
Conway's GoL.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
case RulesetName.POKEMON:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
The color of each cell represents one of the 18 Pokémon types. Each
|
||||||
|
turn, every cell checks to see if it would beat its neighbors, and
|
||||||
|
takes on the type that would have dealt it the most damage if not.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
case RulesetName.ROCK_PAPER_SCISSORS:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
Inspired by{" "}
|
||||||
|
<Link href="https://www.youtube.com/watch?v=TvZI6Xc0J1Y">EFrans</Link>
|
||||||
|
. If a majority of a cell's neighbors beat it, it takes that color.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
case RulesetName.SNOWFLAKE:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
An aesthetically pleasing pattern discovered while messing around with
|
||||||
|
the settings for Conway's Game of Life.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
case RulesetName.WAR:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
Inspired by /u/AlexanderDudarev's{" "}
|
||||||
|
<Link href="https://www.reddit.com/r/cellular_automata/comments/1bmicq6/a_simple_cellular_automaton_that_simulates_war/">
|
||||||
|
post
|
||||||
|
</Link>{" "}
|
||||||
|
on Reddit. Each turn, every cell takes a random color from its
|
||||||
|
neighbors.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
case RulesetName.WATER_FLOW:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
Based on the idea of water eroding and flowing through rock. Streams
|
||||||
|
can occasionally meet with other streams and blend their colors
|
||||||
|
together.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GameDescription;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import GameDescription from "./GameDescription";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export { GameDescription };
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { RulesetName } from "../../internal";
|
||||||
|
|
||||||
|
export interface GameDescriptionProps {
|
||||||
|
rulesetName: RulesetName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { useContext, useState } from "react";
|
||||||
|
import { Box, Fab, Collapse, Drawer } from "@mui/material";
|
||||||
|
import DownIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||||
|
import UpIcon from "@mui/icons-material/KeyboardArrowUp";
|
||||||
|
import PauseIcon from "@mui/icons-material/Pause";
|
||||||
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||||
|
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||||
|
import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
|
import { Canvas, CANVAS_ID, Controls, GameContext } from "../../internal";
|
||||||
|
import { ControlsContextProvider } from "../Controls/context";
|
||||||
|
|
||||||
|
const GameScreen = () => {
|
||||||
|
const { grid, paused, togglePause } = useContext(GameContext);
|
||||||
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
const [controlsOpen, setControlsOpen] = useState(false);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
minHeight="100vh"
|
||||||
|
pl={grid.config.cellSize / 6}
|
||||||
|
>
|
||||||
|
<Canvas canvasAttributes={{ id: CANVAS_ID }} />
|
||||||
|
<Fab
|
||||||
|
onClick={() => setMenuOpen(!menuOpen)}
|
||||||
|
sx={{ position: "absolute", bottom: 15, right: 15 }}
|
||||||
|
>
|
||||||
|
{menuOpen ? <DownIcon /> : <UpIcon />}
|
||||||
|
</Fab>
|
||||||
|
<Collapse in={menuOpen}>
|
||||||
|
<Fab
|
||||||
|
onClick={() => {
|
||||||
|
setControlsOpen(true);
|
||||||
|
setMenuOpen(false);
|
||||||
|
}}
|
||||||
|
sx={{ position: "absolute", bottom: 100, right: 15 }}
|
||||||
|
>
|
||||||
|
<SettingsIcon />
|
||||||
|
</Fab>
|
||||||
|
<Fab
|
||||||
|
onClick={() => {
|
||||||
|
togglePause();
|
||||||
|
}}
|
||||||
|
sx={{ position: "absolute", bottom: 185, right: 15 }}
|
||||||
|
>
|
||||||
|
{paused ? <PlayArrowIcon /> : <PauseIcon />}
|
||||||
|
</Fab>
|
||||||
|
<Fab
|
||||||
|
onClick={() => {
|
||||||
|
grid.reset();
|
||||||
|
}}
|
||||||
|
sx={{ position: "absolute", bottom: 270, right: 15 }}
|
||||||
|
>
|
||||||
|
<RefreshIcon />
|
||||||
|
</Fab>
|
||||||
|
</Collapse>
|
||||||
|
</Box>
|
||||||
|
<ControlsContextProvider>
|
||||||
|
<Drawer
|
||||||
|
anchor="bottom"
|
||||||
|
open={controlsOpen}
|
||||||
|
onClose={() => setControlsOpen(false)}
|
||||||
|
>
|
||||||
|
<Controls closeDrawer={() => setControlsOpen(false)} />
|
||||||
|
</Drawer>
|
||||||
|
</ControlsContextProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GameScreen;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import GameScreen from "./GameScreen";
|
||||||
|
|
||||||
|
export { GameScreen };
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./Canvas";
|
||||||
|
export * from "./Controls";
|
||||||
|
export * from "./GameDescription";
|
||||||
|
export * from "./GameScreen";
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { CellShape, RulesetName } from "../../internal";
|
||||||
|
|
||||||
|
const defaultCellShape: CellShape = "square";
|
||||||
|
const defaultCellSize = 16;
|
||||||
|
const defaultGridLoops = true;
|
||||||
|
const defaultRulesetName: RulesetName = RulesetName.CONWAY;
|
||||||
|
const defaultThrottleAmount = 10;
|
||||||
|
|
||||||
|
export {
|
||||||
|
defaultCellShape,
|
||||||
|
defaultCellSize,
|
||||||
|
defaultGridLoops,
|
||||||
|
defaultRulesetName,
|
||||||
|
defaultThrottleAmount,
|
||||||
|
};
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import {
|
||||||
|
RulesetName,
|
||||||
|
Preset,
|
||||||
|
PokemonType,
|
||||||
|
ConwayConfig,
|
||||||
|
MazeConfig,
|
||||||
|
PokemonGameConfig,
|
||||||
|
RPSGameConfig,
|
||||||
|
SnowflakeGameConfig,
|
||||||
|
WarGameConfig,
|
||||||
|
WaterFlowGameConfig,
|
||||||
|
GameSpecificConfigs,
|
||||||
|
} from "../../../internal";
|
||||||
|
import defaultPokemonColors from "./defaultPokemonColors";
|
||||||
|
|
||||||
|
const defaultGameOptions: GameSpecificConfigs = {
|
||||||
|
[RulesetName.CONWAY]: {
|
||||||
|
cellLifespan: 10,
|
||||||
|
deadColor: "#293462",
|
||||||
|
envelopeGradientSteps: 20,
|
||||||
|
finalEnvelopeColor: "#D800A6",
|
||||||
|
neighborsNeededToReproduce: [3],
|
||||||
|
neighborsNeededToSurvive: [2, 3],
|
||||||
|
liveColor: "#B59410",
|
||||||
|
liveStartPercent: 10,
|
||||||
|
mortalCells: true,
|
||||||
|
preset: Preset.DEFAULT,
|
||||||
|
showEnvelope: true,
|
||||||
|
showEnvelopeGradient: true,
|
||||||
|
startingEnvelopeColor: "#5E78E0",
|
||||||
|
} as ConwayConfig,
|
||||||
|
[RulesetName.MAZE_GENERATOR]: {
|
||||||
|
deadColor: "#640d14",
|
||||||
|
neighborsNeededToReproduce: [3],
|
||||||
|
neighborsNeededToSurvive: [2, 3],
|
||||||
|
liveColor: "#90be6d",
|
||||||
|
liveStartPercent: 10,
|
||||||
|
} as MazeConfig,
|
||||||
|
[RulesetName.POKEMON]: {
|
||||||
|
// disable dragon type by default because it (and dragon-steel-fairy tricolor spirals) tend to dominate the screen
|
||||||
|
allowedTypes: Object.keys(PokemonType)
|
||||||
|
.map((key) => PokemonType[key])
|
||||||
|
.filter((type) => type !== PokemonType.DRAGON),
|
||||||
|
randomMutationChance: 750,
|
||||||
|
typeColors: defaultPokemonColors,
|
||||||
|
} as PokemonGameConfig,
|
||||||
|
[RulesetName.ROCK_PAPER_SCISSORS]: {
|
||||||
|
rockColor: "#FF218C",
|
||||||
|
paperColor: "#FFD800",
|
||||||
|
scissorsColor: "#21B1FF",
|
||||||
|
} as RPSGameConfig,
|
||||||
|
[RulesetName.SNOWFLAKE]: {
|
||||||
|
deadColor: "#161b33",
|
||||||
|
liveColor: "#639fab",
|
||||||
|
} as SnowflakeGameConfig,
|
||||||
|
[RulesetName.WAR]: {
|
||||||
|
factionColors: ["#edae49", "#d1495b", "#00798c", "#30638e", "#003d5b"],
|
||||||
|
} as WarGameConfig,
|
||||||
|
[RulesetName.WATER_FLOW]: {
|
||||||
|
averageStartingWaterCells: 20,
|
||||||
|
backgroundBlurAmount: 1,
|
||||||
|
baseBackgroundColor: "#0b0027",
|
||||||
|
blurBackground: true,
|
||||||
|
branchingChance: 5,
|
||||||
|
rockHealthVariance: 5,
|
||||||
|
trueRandom: false,
|
||||||
|
waterColors: ["#3e8090", "#1a546a", "#F6FDC3", "#D74B76", "#FF8080"],
|
||||||
|
} as WaterFlowGameConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defaultGameOptions;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { PokemonType } from "../../../internal";
|
||||||
|
|
||||||
|
const defaultPokemonColors = {
|
||||||
|
[PokemonType.NORMAL]: "#A8A77A",
|
||||||
|
[PokemonType.FIRE]: "#EE8130",
|
||||||
|
[PokemonType.WATER]: "#6390F0",
|
||||||
|
[PokemonType.ELECTRIC]: "#F7D02C",
|
||||||
|
[PokemonType.GRASS]: "#7AC74C",
|
||||||
|
[PokemonType.ICE]: "#96D9D6",
|
||||||
|
[PokemonType.FIGHTING]: "#C22E28",
|
||||||
|
[PokemonType.POISON]: "#A33EA1",
|
||||||
|
[PokemonType.GROUND]: "#E2BF65",
|
||||||
|
[PokemonType.FLYING]: "#A98FF3",
|
||||||
|
[PokemonType.PSYCHIC]: "#F95587",
|
||||||
|
[PokemonType.BUG]: "#A6B91A",
|
||||||
|
[PokemonType.ROCK]: "#B6A136",
|
||||||
|
[PokemonType.GHOST]: "#735797",
|
||||||
|
[PokemonType.DRAGON]: "#6F35FC",
|
||||||
|
[PokemonType.DARK]: "#705746",
|
||||||
|
[PokemonType.STEEL]: "#B7B7CE",
|
||||||
|
[PokemonType.FAIRY]: "#D685AD",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defaultPokemonColors;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import defaultPokemonColors from "./defaultPokemonColors";
|
||||||
|
import defaultGameOptions from "./defaultGameOptions";
|
||||||
|
|
||||||
|
export { defaultGameOptions, defaultPokemonColors };
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import defaultGridConfig from "./defaultGridConfig";
|
||||||
|
import { GlobalConfig } from "../../../internal";
|
||||||
|
import { defaultThrottleAmount } from "../defaultConfigValues";
|
||||||
|
|
||||||
|
const defaultGlobalConfig: GlobalConfig = {
|
||||||
|
gridConfig: defaultGridConfig,
|
||||||
|
throttleAmount: defaultThrottleAmount,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defaultGlobalConfig;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { createRuleset, getCanvasSize, GridConfig } from "../../../internal";
|
||||||
|
import {
|
||||||
|
defaultCellShape,
|
||||||
|
defaultCellSize,
|
||||||
|
defaultGridLoops,
|
||||||
|
defaultRulesetName,
|
||||||
|
} from "../defaultConfigValues";
|
||||||
|
|
||||||
|
const { height, width } = getCanvasSize();
|
||||||
|
|
||||||
|
const defaultGridConfig: GridConfig = {
|
||||||
|
cellShape: defaultCellShape,
|
||||||
|
cellSize: defaultCellSize,
|
||||||
|
height,
|
||||||
|
loops: defaultGridLoops,
|
||||||
|
ruleset: createRuleset(defaultRulesetName),
|
||||||
|
width,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defaultGridConfig;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { defaultGlobalConfigs } from "./useDefaultGlobalConfig";
|
||||||
|
import useDefaultGlobalConfig from "./useDefaultGlobalConfig";
|
||||||
|
|
||||||
|
export { defaultGlobalConfigs, useDefaultGlobalConfig };
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import {
|
||||||
|
GlobalConfig,
|
||||||
|
GlobalConfigs,
|
||||||
|
GridConfig,
|
||||||
|
RulesetName,
|
||||||
|
} from "../../../internal";
|
||||||
|
import defaultGlobalConfig from "./defaultGlobalConfig";
|
||||||
|
import defaultGridConfig from "./defaultGridConfig";
|
||||||
|
|
||||||
|
type Override = Omit<Partial<GlobalConfig>, "gridConfig"> & {
|
||||||
|
gridConfig?: Partial<GridConfig>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const overrideDefaultGlobalConfig = (config: Override) => {
|
||||||
|
const { gridConfig, ...otherConfigs } = config;
|
||||||
|
return {
|
||||||
|
...defaultGlobalConfig,
|
||||||
|
...otherConfigs,
|
||||||
|
gridConfig: { ...defaultGridConfig, ...(gridConfig ? gridConfig : {}) },
|
||||||
|
} as GlobalConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultGlobalConfigs: GlobalConfigs = {
|
||||||
|
[RulesetName.CONWAY]: overrideDefaultGlobalConfig({}),
|
||||||
|
[RulesetName.MAZE_GENERATOR]: overrideDefaultGlobalConfig({}),
|
||||||
|
[RulesetName.POKEMON]: overrideDefaultGlobalConfig({
|
||||||
|
gridConfig: { cellShape: "hex", loops: false },
|
||||||
|
}),
|
||||||
|
[RulesetName.ROCK_PAPER_SCISSORS]: overrideDefaultGlobalConfig({
|
||||||
|
gridConfig: { cellShape: "hex" },
|
||||||
|
}),
|
||||||
|
[RulesetName.SNOWFLAKE]: overrideDefaultGlobalConfig({
|
||||||
|
gridConfig: { cellSize: 8 },
|
||||||
|
}),
|
||||||
|
[RulesetName.WAR]: overrideDefaultGlobalConfig({
|
||||||
|
gridConfig: { cellShape: "hex" },
|
||||||
|
}),
|
||||||
|
[RulesetName.WATER_FLOW]: overrideDefaultGlobalConfig({
|
||||||
|
gridConfig: { cellShape: "hex", cellSize: 12, loops: false },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const useDefaultGlobalConfig = (rulesetName: RulesetName) => {
|
||||||
|
return useMemo(() => defaultGlobalConfigs[rulesetName], [rulesetName]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDefaultGlobalConfig;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./defaultConfigValues";
|
||||||
|
export * from "./gameSpecificOptions";
|
||||||
|
export * from "./globalOptions";
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./defaults";
|
||||||
|
export * from "./math";
|
||||||
|
export * from "./strings";
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
const angleOfHexagonalSide = (2 * Math.PI) / 6;
|
||||||
|
|
||||||
|
export default angleOfHexagonalSide;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import angleOfHexagonalSide from "./angleOfHexagonalSide";
|
||||||
|
|
||||||
|
export { angleOfHexagonalSide };
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
const CANVAS_ID = "canvas";
|
||||||
|
|
||||||
|
export default CANVAS_ID;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import CANVAS_ID from "./canvasId";
|
||||||
|
|
||||||
|
export { CANVAS_ID };
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
import defaultGameContextValue from "./const";
|
||||||
|
import { GameContextType } from "./types";
|
||||||
|
|
||||||
|
const GameContext = createContext<GameContextType>(defaultGameContextValue);
|
||||||
|
|
||||||
|
export default GameContext;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import useGameContext from "./useGameContext";
|
||||||
|
import GameContext from "./GameContext";
|
||||||
|
|
||||||
|
const GameContextProvider = ({ children }) => {
|
||||||
|
const contextValue = useGameContext();
|
||||||
|
return (
|
||||||
|
<GameContext.Provider value={contextValue}>{children}</GameContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GameContextProvider;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { GameContextType } from "./types";
|
||||||
|
import {
|
||||||
|
defaultGameOptions,
|
||||||
|
defaultGlobalConfigs,
|
||||||
|
defaultRulesetName,
|
||||||
|
} from "../internal";
|
||||||
|
|
||||||
|
const defaultGameContextValue: GameContextType = {
|
||||||
|
currentGame: defaultRulesetName,
|
||||||
|
gameSpecificConfigs: defaultGameOptions,
|
||||||
|
globalConfigs: defaultGlobalConfigs,
|
||||||
|
grid: null,
|
||||||
|
paused: false,
|
||||||
|
setCurrentGame: () => null,
|
||||||
|
setGameSpecificConfigs: () => null,
|
||||||
|
setGlobalConfigs: () => null,
|
||||||
|
setGrid: () => null,
|
||||||
|
togglePause: () => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defaultGameContextValue;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import GameContext from "./GameContext";
|
||||||
|
import GameContextProvider from "./GameContextProvider";
|
||||||
|
import useGameContext from "./useGameContext";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export { GameContext, GameContextProvider, useGameContext };
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
import { GameSpecificConfig, Grid, GridConfig, RulesetName } from "../internal";
|
||||||
|
|
||||||
|
export type GameSpecificConfigs = { [key in RulesetName]: GameSpecificConfig };
|
||||||
|
|
||||||
|
export interface GlobalConfig {
|
||||||
|
gridConfig: GridConfig;
|
||||||
|
throttleAmount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GlobalConfigs = { [key in RulesetName]: GlobalConfig };
|
||||||
|
|
||||||
|
export interface GameContextType {
|
||||||
|
currentGame: RulesetName;
|
||||||
|
gameSpecificConfigs: GameSpecificConfigs;
|
||||||
|
globalConfigs: GlobalConfigs;
|
||||||
|
grid: Grid;
|
||||||
|
paused: boolean;
|
||||||
|
setCurrentGame: Dispatch<SetStateAction<RulesetName>>;
|
||||||
|
setGameSpecificConfigs: Dispatch<SetStateAction<GameSpecificConfigs>>;
|
||||||
|
setGlobalConfigs: Dispatch<SetStateAction<GlobalConfigs>>;
|
||||||
|
setGrid: Dispatch<SetStateAction<Grid>>;
|
||||||
|
togglePause: () => void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
import { GameContextType, GameSpecificConfigs, GlobalConfigs } from "./types";
|
||||||
|
import defaultGameContextValue from "./const";
|
||||||
|
import {
|
||||||
|
createGrid,
|
||||||
|
defaultRulesetName,
|
||||||
|
Grid,
|
||||||
|
defaultGlobalConfigs,
|
||||||
|
defaultGameOptions,
|
||||||
|
RulesetName,
|
||||||
|
} from "../internal";
|
||||||
|
|
||||||
|
const useGameContext = (): GameContextType => {
|
||||||
|
const [currentGame, setCurrentGame] =
|
||||||
|
useState<RulesetName>(defaultRulesetName);
|
||||||
|
const [globalConfigs, setGlobalConfigs] =
|
||||||
|
useState<GlobalConfigs>(defaultGlobalConfigs);
|
||||||
|
const [gameSpecificConfigs, setGameSpecificConfigs] =
|
||||||
|
useState<GameSpecificConfigs>(defaultGameOptions);
|
||||||
|
const [grid, setGrid] = useState<Grid>(
|
||||||
|
createGrid(
|
||||||
|
defaultRulesetName,
|
||||||
|
defaultGlobalConfigs[defaultRulesetName],
|
||||||
|
gameSpecificConfigs[defaultRulesetName]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const [paused, setPaused] = useState(defaultGameContextValue.paused);
|
||||||
|
|
||||||
|
const togglePause = useCallback(() => {
|
||||||
|
setPaused(!paused);
|
||||||
|
}, [paused]);
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
currentGame,
|
||||||
|
gameSpecificConfigs,
|
||||||
|
globalConfigs,
|
||||||
|
grid,
|
||||||
|
paused,
|
||||||
|
setCurrentGame,
|
||||||
|
setGameSpecificConfigs,
|
||||||
|
setGlobalConfigs,
|
||||||
|
setGrid,
|
||||||
|
togglePause,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
currentGame,
|
||||||
|
gameSpecificConfigs,
|
||||||
|
globalConfigs,
|
||||||
|
grid,
|
||||||
|
paused,
|
||||||
|
setCurrentGame,
|
||||||
|
setGameSpecificConfigs,
|
||||||
|
setGlobalConfigs,
|
||||||
|
setGrid,
|
||||||
|
togglePause,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useGameContext;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import useCanvas from "./useCanvas";
|
||||||
|
|
||||||
|
export { useCanvas };
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { useRef, useEffect, useContext } from "react";
|
||||||
|
import { GameContext } from "../internal";
|
||||||
|
|
||||||
|
const useCanvas = (draw: (frameCount: number) => void) => {
|
||||||
|
const { paused } = useContext(GameContext);
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let frameCount = 0;
|
||||||
|
let animationFrameId: number;
|
||||||
|
|
||||||
|
const render = () => {
|
||||||
|
frameCount++;
|
||||||
|
draw(frameCount);
|
||||||
|
animationFrameId = window.requestAnimationFrame(render);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!paused) render();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.cancelAnimationFrame(animationFrameId);
|
||||||
|
};
|
||||||
|
}, [draw, paused]);
|
||||||
|
|
||||||
|
return canvasRef;
|
||||||
|
};
|
||||||
|
export default useCanvas;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
body {
|
||||||
|
background-color: #181a1b;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import "./index.css";
|
||||||
|
import { App } from "./internal";
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(
|
||||||
|
document.getElementById("root") as HTMLElement
|
||||||
|
);
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
manage export order to avoid circular dependencies
|
||||||
|
https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de
|
||||||
|
this feels hacky but it stops the app from crashing for now
|
||||||
|
*/
|
||||||
|
export * from "./const/math";
|
||||||
|
export * from "./const/strings";
|
||||||
|
export * from "./utils/arrayUtils";
|
||||||
|
export * from "./utils/colorUtils";
|
||||||
|
export * from "./rulesets/RockPaperScissors/types";
|
||||||
|
export * from "./rulesets/Snowflake/types";
|
||||||
|
export * from "./rulesets/War/types";
|
||||||
|
export * from "./rulesets/types";
|
||||||
|
export * from "./classes/Cell/types";
|
||||||
|
export * from "./classes/Grid/types";
|
||||||
|
export * from "./classes/Ruleset/types";
|
||||||
|
export * from "./const/defaults/defaultConfigValues";
|
||||||
|
// todo: remove this
|
||||||
|
export const possibleColors = [
|
||||||
|
"#430f6e",
|
||||||
|
"#472765",
|
||||||
|
"#566888",
|
||||||
|
"#506e86",
|
||||||
|
"#37566f",
|
||||||
|
"#FFC0CB",
|
||||||
|
];
|
||||||
|
|
||||||
|
export { default as Cell } from "./classes/Cell/Cell";
|
||||||
|
|
||||||
|
// depends on Cell
|
||||||
|
export * from "./rulesets/Conway/types";
|
||||||
|
export * from "./rulesets/MazeGenerator/types";
|
||||||
|
export * from "./rulesets/Pokemon/types";
|
||||||
|
export * from "./rulesets/WaterFlow/types";
|
||||||
|
|
||||||
|
// depends on Cell, angleOfHexagonalSide, and CANVAS_ID
|
||||||
|
export * from "./utils/canvasUtils";
|
||||||
|
|
||||||
|
// depends on Cell
|
||||||
|
export { default as createHexRows } from "./utils/gridUtils/createHexRows";
|
||||||
|
export { default as createSquareRows } from "./utils/gridUtils/createSquareRows";
|
||||||
|
export { default as getCellById } from "./utils/gridUtils/getCellById";
|
||||||
|
export { default as getNeighbors } from "./utils/gridUtils/getNeighbors";
|
||||||
|
export { default as getRandomCell } from "./utils/gridUtils/getRandomCell";
|
||||||
|
|
||||||
|
export { default as Ruleset } from "./classes/Ruleset/Ruleset";
|
||||||
|
export { default as Grid } from "./classes/Grid/Grid";
|
||||||
|
export * from "./context/types";
|
||||||
|
|
||||||
|
export { default as defaultPokemonColors } from "./const/defaults/gameSpecificOptions/defaultPokemonColors";
|
||||||
|
export { default as defaultGameOptions } from "./const/defaults/gameSpecificOptions/defaultGameOptions";
|
||||||
|
|
||||||
|
export { default as Conway } from "./rulesets/Conway/Conway";
|
||||||
|
export { default as MazeGenerator } from "./rulesets/MazeGenerator/MazeGenerator";
|
||||||
|
export { default as Pokemon } from "./rulesets/Pokemon/Pokemon";
|
||||||
|
export { default as RockPaperScissors } from "./rulesets/RockPaperScissors/RockPaperScissors";
|
||||||
|
export { default as Snowflake } from "./rulesets/Snowflake/Snowflake";
|
||||||
|
export { default as War } from "./rulesets/War/War";
|
||||||
|
export { default as WaterFlow } from "./rulesets/WaterFlow/WaterFlow";
|
||||||
|
export { default as createRuleset } from "./utils/rulesetUtils/createRuleset";
|
||||||
|
export { default as createGrid } from "./utils/gridUtils/createGrid";
|
||||||
|
export {
|
||||||
|
defaultGlobalConfigs,
|
||||||
|
default as useDefaultGlobalConfig,
|
||||||
|
} from "./const/defaults/globalOptions/useDefaultGlobalConfig";
|
||||||
|
export { default as GameContext } from "./context/GameContext";
|
||||||
|
|
||||||
|
export { default as useCanvas } from "./hooks/useCanvas";
|
||||||
|
export { default as GameContextProvider } from "./context/GameContextProvider";
|
||||||
|
export { default as Canvas } from "./components/Canvas/Canvas";
|
||||||
|
export { default as GameDescription } from "./components/GameDescription/GameDescription";
|
||||||
|
export { default as Controls } from "./components/Controls/Controls";
|
||||||
|
export { default as GameScreen } from "./components/GameScreen/GameScreen";
|
||||||
|
export { default as App } from "./App";
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="react-scripts" />
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import {
|
||||||
|
defaultGameOptions,
|
||||||
|
getGradientSteps,
|
||||||
|
Grid,
|
||||||
|
Ruleset,
|
||||||
|
RulesetName,
|
||||||
|
} from "../../internal";
|
||||||
|
import { ConwayCell, ConwayConfig } from "./types";
|
||||||
|
|
||||||
|
class Conway implements Ruleset {
|
||||||
|
constructor(config: Partial<ConwayConfig> = {}) {
|
||||||
|
this.config = {
|
||||||
|
...defaultGameOptions[RulesetName.CONWAY],
|
||||||
|
...config,
|
||||||
|
} as ConwayConfig;
|
||||||
|
const {
|
||||||
|
envelopeGradientSteps,
|
||||||
|
finalEnvelopeColor,
|
||||||
|
showEnvelope,
|
||||||
|
showEnvelopeGradient,
|
||||||
|
startingEnvelopeColor,
|
||||||
|
} = this.config;
|
||||||
|
if (!showEnvelope) {
|
||||||
|
this.envelopeGradientColors = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!showEnvelopeGradient) {
|
||||||
|
this.envelopeGradientColors = [startingEnvelopeColor];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const envelopeGradientColors = getGradientSteps(
|
||||||
|
startingEnvelopeColor,
|
||||||
|
finalEnvelopeColor,
|
||||||
|
envelopeGradientSteps
|
||||||
|
);
|
||||||
|
this.envelopeGradientColors = envelopeGradientColors;
|
||||||
|
}
|
||||||
|
config: ConwayConfig;
|
||||||
|
envelopeGradientColors: string[];
|
||||||
|
init(grid: Grid) {
|
||||||
|
switch (this.config.preset) {
|
||||||
|
default:
|
||||||
|
// start with a random distribution
|
||||||
|
grid.iterateCells((cell: ConwayCell) => {
|
||||||
|
const isAlive =
|
||||||
|
Math.floor(Math.random() * 100) < this.config.liveStartPercent;
|
||||||
|
cell.config = {
|
||||||
|
age: 0,
|
||||||
|
isAlive,
|
||||||
|
nextAlive: false,
|
||||||
|
timesAlive: isAlive ? 1 : 0,
|
||||||
|
};
|
||||||
|
cell.currentColor = isAlive
|
||||||
|
? this.config.liveColor
|
||||||
|
: this.config.deadColor;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update(grid: Grid) {
|
||||||
|
const {
|
||||||
|
deadColor,
|
||||||
|
envelopeGradientSteps,
|
||||||
|
liveColor,
|
||||||
|
mortalCells,
|
||||||
|
neighborsNeededToReproduce,
|
||||||
|
neighborsNeededToSurvive,
|
||||||
|
showEnvelope,
|
||||||
|
} = this.config;
|
||||||
|
const { envelopeGradientColors } = this;
|
||||||
|
grid.iterateCells((cell: ConwayCell) => {
|
||||||
|
const { isAlive, timesAlive } = cell.config;
|
||||||
|
let nextColor = deadColor;
|
||||||
|
|
||||||
|
const handleDeadCell = () => {
|
||||||
|
cell.config.age = 0;
|
||||||
|
cell.config.nextAlive = false;
|
||||||
|
if (showEnvelope && timesAlive > 0) {
|
||||||
|
nextColor =
|
||||||
|
envelopeGradientColors[timesAlive] ??
|
||||||
|
envelopeGradientColors[envelopeGradientColors.length - 1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cell.config.age > this.config.cellLifespan) {
|
||||||
|
handleDeadCell();
|
||||||
|
cell.setNextColor(nextColor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const neighbors = grid.getNeighbors(cell);
|
||||||
|
const numberOfLiveNeighbors = neighbors.filter(
|
||||||
|
(neighbor: ConwayCell) => neighbor.config.isAlive
|
||||||
|
).length;
|
||||||
|
let nextAliveState = false;
|
||||||
|
if (
|
||||||
|
(isAlive && neighborsNeededToSurvive.includes(numberOfLiveNeighbors)) ||
|
||||||
|
(!isAlive && neighborsNeededToReproduce.includes(numberOfLiveNeighbors))
|
||||||
|
) {
|
||||||
|
nextAliveState = true;
|
||||||
|
}
|
||||||
|
cell.config.nextAlive = nextAliveState;
|
||||||
|
if (nextAliveState) {
|
||||||
|
nextColor = liveColor;
|
||||||
|
if (timesAlive < envelopeGradientSteps) {
|
||||||
|
cell.config.timesAlive++;
|
||||||
|
}
|
||||||
|
if (mortalCells) cell.config.age++;
|
||||||
|
} else {
|
||||||
|
handleDeadCell();
|
||||||
|
}
|
||||||
|
cell.setNextColor(nextColor);
|
||||||
|
});
|
||||||
|
grid.iterateCells((cell: ConwayCell) => {
|
||||||
|
cell.config.isAlive = cell.config.nextAlive;
|
||||||
|
cell.setCurrentColor(cell.nextColor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Conway;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import Conway from "./Conway";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export { Conway };
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Cell } from "../../internal";
|
||||||
|
|
||||||
|
export enum Preset {
|
||||||
|
DEFAULT = "default",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConwayCellConfig {
|
||||||
|
age: number;
|
||||||
|
isAlive: boolean;
|
||||||
|
nextAlive: boolean;
|
||||||
|
timesAlive: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConwayCell = Omit<Cell, "config"> & {
|
||||||
|
config: ConwayCellConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ConwayConfig {
|
||||||
|
cellLifespan: number;
|
||||||
|
deadColor: string;
|
||||||
|
envelopeGradientSteps: number;
|
||||||
|
finalEnvelopeColor: string;
|
||||||
|
neighborsNeededToReproduce: number[];
|
||||||
|
neighborsNeededToSurvive: number[];
|
||||||
|
liveColor: string;
|
||||||
|
liveStartPercent: number;
|
||||||
|
mortalCells: boolean;
|
||||||
|
preset: Preset;
|
||||||
|
showEnvelope: boolean;
|
||||||
|
showEnvelopeGradient: boolean;
|
||||||
|
startingEnvelopeColor: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { defaultGameOptions, Grid, Ruleset, RulesetName } from "../../internal";
|
||||||
|
import { MazeCell, MazeConfig } from "./types";
|
||||||
|
|
||||||
|
class MazeGenerator implements Ruleset {
|
||||||
|
constructor(config: Partial<MazeConfig> = {}) {
|
||||||
|
this.config = {
|
||||||
|
...defaultGameOptions[RulesetName.MAZE_GENERATOR],
|
||||||
|
...config,
|
||||||
|
} as MazeConfig;
|
||||||
|
}
|
||||||
|
config: MazeConfig;
|
||||||
|
init(grid: Grid) {
|
||||||
|
// start with a random distribution
|
||||||
|
grid.iterateCells((cell: MazeCell) => {
|
||||||
|
const isAlive =
|
||||||
|
Math.floor(Math.random() * 100) < this.config.liveStartPercent;
|
||||||
|
cell.config = { isAlive };
|
||||||
|
cell.currentColor = isAlive
|
||||||
|
? this.config.liveColor
|
||||||
|
: this.config.deadColor;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
update(grid: Grid) {
|
||||||
|
grid.iterateCells((cell: MazeCell) => {
|
||||||
|
const neighbors = grid.getNeighbors(cell);
|
||||||
|
let nextAliveState = false;
|
||||||
|
const isAlive = cell.config.isAlive;
|
||||||
|
const numberOfLiveNeighbors = neighbors.filter(
|
||||||
|
(neighbor: MazeCell) => neighbor.config.isAlive
|
||||||
|
).length;
|
||||||
|
if (
|
||||||
|
(isAlive &&
|
||||||
|
this.config.neighborsNeededToSurvive.includes(
|
||||||
|
numberOfLiveNeighbors
|
||||||
|
)) ||
|
||||||
|
(!isAlive &&
|
||||||
|
this.config.neighborsNeededToReproduce.includes(
|
||||||
|
numberOfLiveNeighbors
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
nextAliveState = true;
|
||||||
|
}
|
||||||
|
cell.config.isAlive = nextAliveState;
|
||||||
|
cell.setNextColor(
|
||||||
|
nextAliveState ? this.config.liveColor : this.config.deadColor
|
||||||
|
);
|
||||||
|
});
|
||||||
|
grid.iterateCells((cell) => cell.setCurrentColor(cell.nextColor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MazeGenerator;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import MazeGenerator from "./MazeGenerator";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export { MazeGenerator };
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Cell } from "../../internal";
|
||||||
|
|
||||||
|
export interface MazeCellConfig {
|
||||||
|
isAlive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MazeCell = Omit<Cell, "config"> & {
|
||||||
|
config: MazeCellConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface MazeConfig {
|
||||||
|
deadColor: string;
|
||||||
|
neighborsNeededToReproduce: number[];
|
||||||
|
neighborsNeededToSurvive: number[];
|
||||||
|
liveColor: string;
|
||||||
|
liveStartPercent: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import {
|
||||||
|
defaultGameOptions,
|
||||||
|
GlobalConfig,
|
||||||
|
Grid,
|
||||||
|
randomFromArray,
|
||||||
|
Ruleset,
|
||||||
|
RulesetName,
|
||||||
|
} from "../../internal";
|
||||||
|
import { typeMatchup, matchupScoring } from "./const";
|
||||||
|
import {
|
||||||
|
MatchupKey,
|
||||||
|
PokemonCell,
|
||||||
|
PokemonGameConfig,
|
||||||
|
PokemonType,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
class Pokemon implements Ruleset {
|
||||||
|
constructor(config: Partial<PokemonGameConfig> = {}) {
|
||||||
|
this.config = {
|
||||||
|
...defaultGameOptions[RulesetName.POKEMON],
|
||||||
|
...config,
|
||||||
|
} as PokemonGameConfig;
|
||||||
|
}
|
||||||
|
config: PokemonGameConfig;
|
||||||
|
defaultGlobalSettings: GlobalConfig;
|
||||||
|
init(grid: Grid) {
|
||||||
|
grid.iterateCells((cell) => {
|
||||||
|
const { allowedTypes, typeColors } = this.config;
|
||||||
|
const type = randomFromArray(allowedTypes) || PokemonType.ELECTRIC;
|
||||||
|
cell.config = { currentType: type };
|
||||||
|
cell.currentColor = typeColors[type];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
update(grid: Grid) {
|
||||||
|
grid.iterateCells((cell) => {
|
||||||
|
const { allowedTypes, randomMutationChance, typeColors } = this.config;
|
||||||
|
const neighbors = grid.getNeighbors(cell);
|
||||||
|
|
||||||
|
const neighborTypes = neighbors.map(
|
||||||
|
(neighbor: PokemonCell) => neighbor.config.currentType
|
||||||
|
);
|
||||||
|
const { currentType } = cell.config;
|
||||||
|
const matchupForCurrentType = typeMatchup[currentType];
|
||||||
|
let nextType = cell.config.currentType;
|
||||||
|
|
||||||
|
// add random mutations to prevent stable states
|
||||||
|
if (Math.floor(Math.random() * randomMutationChance) === 1) {
|
||||||
|
nextType = randomFromArray(allowedTypes) || PokemonType.ELECTRIC;
|
||||||
|
} else {
|
||||||
|
let averageDamageDealt = 0;
|
||||||
|
let averageDamageReceived = 0;
|
||||||
|
const damageByType: { [key: string]: number } = {};
|
||||||
|
neighborTypes.forEach((neighborType) => {
|
||||||
|
if (!damageByType.neighborType) damageByType[neighborType] = 0;
|
||||||
|
Object.keys(matchupForCurrentType).forEach((key: MatchupKey) => {
|
||||||
|
if (matchupForCurrentType[key].includes(neighborType)) {
|
||||||
|
const damageScore = matchupScoring[key];
|
||||||
|
damageByType[neighborType] += damageScore;
|
||||||
|
if (damageScore > 0) {
|
||||||
|
averageDamageDealt += damageScore;
|
||||||
|
} else {
|
||||||
|
averageDamageReceived += damageScore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
averageDamageDealt /= neighbors.length;
|
||||||
|
averageDamageReceived /= neighbors.length;
|
||||||
|
});
|
||||||
|
if (averageDamageDealt > averageDamageReceived) {
|
||||||
|
let highestHit = 0;
|
||||||
|
Object.keys(damageByType).forEach((key: PokemonType) => {
|
||||||
|
if (damageByType[key] > highestHit && key !== currentType) {
|
||||||
|
highestHit = damageByType[key];
|
||||||
|
nextType = key;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cell.config.nextType = nextType;
|
||||||
|
cell.setNextColor(typeColors[nextType]);
|
||||||
|
});
|
||||||
|
grid.iterateCells((cell) => {
|
||||||
|
cell.setCurrentColor(cell.nextColor);
|
||||||
|
cell.config.currentType = cell.config.nextType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Pokemon;
|
||||||
@@ -0,0 +1,439 @@
|
|||||||
|
import { MatchupKey, PokemonType } from "./types";
|
||||||
|
|
||||||
|
export const matchupScoring = {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: 0.1,
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: -0.1,
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: 1,
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: -1,
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: 4,
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: -4,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const typeMatchup: {
|
||||||
|
[key: string]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: PokemonType[];
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: PokemonType[];
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: PokemonType[];
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: PokemonType[];
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: PokemonType[];
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: PokemonType[];
|
||||||
|
};
|
||||||
|
} = {
|
||||||
|
[PokemonType.NORMAL]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [PokemonType.GHOST],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [PokemonType.GHOST],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.ROCK,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [PokemonType.FIGHTING],
|
||||||
|
},
|
||||||
|
[PokemonType.FIRE]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.WATER,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
PokemonType.DRAGON,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.ICE,
|
||||||
|
PokemonType.BUG,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
PokemonType.FAIRY,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.ICE,
|
||||||
|
PokemonType.BUG,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.WATER,
|
||||||
|
PokemonType.GROUND,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.WATER]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.WATER,
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.DRAGON,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.WATER,
|
||||||
|
PokemonType.ICE,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.GROUND,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.ELECTRIC,
|
||||||
|
PokemonType.GRASS,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.ELECTRIC]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [PokemonType.GROUND],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.ELECTRIC,
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.DRAGON,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.ELECTRIC,
|
||||||
|
PokemonType.FLYING,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.WATER,
|
||||||
|
PokemonType.FLYING,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [PokemonType.GROUND],
|
||||||
|
},
|
||||||
|
[PokemonType.GRASS]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [PokemonType.GROUND],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.ELECTRIC,
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.DRAGON,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.ELECTRIC,
|
||||||
|
PokemonType.FLYING,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.WATER,
|
||||||
|
PokemonType.FLYING,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [PokemonType.GROUND],
|
||||||
|
},
|
||||||
|
[PokemonType.ICE]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.WATER,
|
||||||
|
PokemonType.ICE,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [PokemonType.ICE],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.GROUND,
|
||||||
|
PokemonType.FLYING,
|
||||||
|
PokemonType.DRAGON,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.FIGHTING]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [PokemonType.GHOST],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.POISON,
|
||||||
|
PokemonType.FLYING,
|
||||||
|
PokemonType.PSYCHIC,
|
||||||
|
PokemonType.BUG,
|
||||||
|
PokemonType.FAIRY,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.BUG,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
PokemonType.DARK,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.NORMAL,
|
||||||
|
PokemonType.ICE,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
PokemonType.DARK,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.FLYING,
|
||||||
|
PokemonType.PSYCHIC,
|
||||||
|
PokemonType.FAIRY,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.POISON]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [PokemonType.STEEL],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.POISON,
|
||||||
|
PokemonType.GROUND,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
PokemonType.GHOST,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.POISON,
|
||||||
|
PokemonType.BUG,
|
||||||
|
PokemonType.FAIRY,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.FAIRY,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.GROUND,
|
||||||
|
PokemonType.PSYCHIC,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.GROUND]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [PokemonType.FLYING],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [PokemonType.ELECTRIC],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.BUG,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.POISON,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.ELECTRIC,
|
||||||
|
PokemonType.POISON,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.WATER,
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.ICE,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.FLYING]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [PokemonType.GROUND],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.ELECTRIC,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.BUG,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.BUG,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.ELECTRIC,
|
||||||
|
PokemonType.ICE,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.PSYCHIC]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [PokemonType.DARK],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.PSYCHIC,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.PSYCHIC,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.POISON,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.BUG,
|
||||||
|
PokemonType.GHOST,
|
||||||
|
PokemonType.DARK,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.BUG]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.POISON,
|
||||||
|
PokemonType.FLYING,
|
||||||
|
PokemonType.GHOST,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
PokemonType.FAIRY,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.GROUND,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.PSYCHIC,
|
||||||
|
PokemonType.DARK,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.FLYING,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.ROCK]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.GROUND,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.NORMAL,
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.POISON,
|
||||||
|
PokemonType.FLYING,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.ICE,
|
||||||
|
PokemonType.FLYING,
|
||||||
|
PokemonType.BUG,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.WATER,
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.GROUND,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.GHOST]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [PokemonType.NORMAL],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [
|
||||||
|
PokemonType.NORMAL,
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [PokemonType.DARK],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.POISON,
|
||||||
|
PokemonType.BUG,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.PSYCHIC,
|
||||||
|
PokemonType.GHOST,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.GHOST,
|
||||||
|
PokemonType.DARK,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.DRAGON]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [PokemonType.FAIRY],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [PokemonType.STEEL],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.WATER,
|
||||||
|
PokemonType.ELECTRIC,
|
||||||
|
PokemonType.GRASS,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [PokemonType.DRAGON],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.ICE,
|
||||||
|
PokemonType.DRAGON,
|
||||||
|
PokemonType.FAIRY,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.DARK]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [PokemonType.PSYCHIC],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.DARK,
|
||||||
|
PokemonType.FAIRY,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.GHOST,
|
||||||
|
PokemonType.DARK,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.PSYCHIC,
|
||||||
|
PokemonType.GHOST,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.BUG,
|
||||||
|
PokemonType.FAIRY,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.STEEL]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [PokemonType.POISON],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.WATER,
|
||||||
|
PokemonType.ELECTRIC,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.NORMAL,
|
||||||
|
PokemonType.GRASS,
|
||||||
|
PokemonType.ICE,
|
||||||
|
PokemonType.FLYING,
|
||||||
|
PokemonType.PSYCHIC,
|
||||||
|
PokemonType.BUG,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
PokemonType.DRAGON,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
PokemonType.FAIRY,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.ICE,
|
||||||
|
PokemonType.ROCK,
|
||||||
|
PokemonType.FAIRY,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.GROUND,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[PokemonType.FAIRY]: {
|
||||||
|
[MatchupKey.NO_EFFECT_ATTACKING]: [],
|
||||||
|
[MatchupKey.NO_EFFECT_DEFENDING]: [PokemonType.DRAGON],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.FIRE,
|
||||||
|
PokemonType.POISON,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
[MatchupKey.NOT_VERY_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.BUG,
|
||||||
|
PokemonType.DARK,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_ATTACKING]: [
|
||||||
|
PokemonType.FIGHTING,
|
||||||
|
PokemonType.DRAGON,
|
||||||
|
PokemonType.DARK,
|
||||||
|
],
|
||||||
|
[MatchupKey.SUPER_EFFECTIVE_DEFENDING]: [
|
||||||
|
PokemonType.POISON,
|
||||||
|
PokemonType.STEEL,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user