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