first commit
This commit is contained in:
commit
65b747689e
|
|
@ -0,0 +1,24 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||
|
||||
- Configure the top-level `parserOptions` property like this:
|
||||
|
||||
```js
|
||||
export default tseslint.config({
|
||||
languageOptions: {
|
||||
// other options...
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
|
||||
- Optionally add `...tseslint.configs.stylisticTypeChecked`
|
||||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import react from 'eslint-plugin-react'
|
||||
|
||||
export default tseslint.config({
|
||||
// Set the react version
|
||||
settings: { react: { version: '18.3' } },
|
||||
plugins: {
|
||||
// Add the react plugin
|
||||
react,
|
||||
},
|
||||
rules: {
|
||||
// other rules...
|
||||
// Enable its recommended rules
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs['jsx-runtime'].rules,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Minesweeper</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "minesweeper",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"lucide-react": "^0.441.0",
|
||||
"react": "^18.3.1",
|
||||
"react-confetti-boom": "^1.0.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"use-sound": "^4.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"eslint": "^9.9.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.9",
|
||||
"globals": "^15.9.0",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^8.0.1",
|
||||
"vite": "^5.4.1"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { Button } from "./Button";
|
||||
import { useGame } from "./GameContext";
|
||||
import Timer from "./Timer";
|
||||
import Options from "./Options";
|
||||
|
||||
function App() {
|
||||
const game = useGame();
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<h1>Minesweeper</h1>
|
||||
<Options />
|
||||
<div className="game-wrapper">
|
||||
<div>
|
||||
<Timer />
|
||||
<div
|
||||
className="game-board"
|
||||
style={{
|
||||
gridTemplateColumns: `repeat(${game?.getWidth()}, 1fr)`,
|
||||
gridTemplateRows: `repeat(${game?.getHeight()}, 1fr)`,
|
||||
}}
|
||||
>
|
||||
{game?.mines[0].map((_, y) =>
|
||||
game?.mines.map((_, x) => (
|
||||
<Button key={`${x},${y}`} x={x} y={y} />
|
||||
)),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import { ReactNode } from "react";
|
||||
import { updateGame, useGame } from "./GameContext";
|
||||
import { Bomb, Flag } from "lucide-react";
|
||||
|
||||
interface ButtonProps {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export const colorMap: Record<string, string> = {
|
||||
"1": "#049494",
|
||||
"2": "#8c9440",
|
||||
"3": "#cc6666",
|
||||
"4": "#b294bb",
|
||||
"5": "#f7c530",
|
||||
"6": "#81a2be",
|
||||
"7": "#707880",
|
||||
"8": "#b5bd68",
|
||||
};
|
||||
|
||||
export const Button = ({ x, y }: ButtonProps) => {
|
||||
const game = useGame();
|
||||
|
||||
let content: ReactNode = "";
|
||||
|
||||
if (game?.isRevealed[x][y]) {
|
||||
content = game?.isMine(x, y) ? <Bomb /> : game?.getValue(x, y).toString();
|
||||
}
|
||||
|
||||
if (game?.isFlagged[x][y]) {
|
||||
content = <Flag fill="red" />;
|
||||
}
|
||||
if (content === "0") content = "";
|
||||
|
||||
return (
|
||||
<div
|
||||
className="mine-button"
|
||||
style={{
|
||||
background: game?.isRevealed[x][y] ? "#444" : undefined,
|
||||
borderRight: !game?.isRevealed[x][y] ? "3px solid black" : undefined,
|
||||
borderTop: !game?.isRevealed[x][y] ? "3px solid #999" : undefined,
|
||||
borderLeft: !game?.isRevealed[x][y] ? "3px solid #999" : undefined,
|
||||
borderBottom: !game?.isRevealed[x][y] ? "3px solid black" : undefined,
|
||||
color: game?.isRevealed[x][y]
|
||||
? colorMap[String(content)] ?? "#eee"
|
||||
: undefined,
|
||||
fontSize: Number(content) > 0 ? "1.75rem" : undefined,
|
||||
cursor: game?.isRevealed[x][y] ? "default" : "pointer",
|
||||
}}
|
||||
onMouseUp={(e) => {
|
||||
if (game?.getHasWon() || game?.isGameOver) {
|
||||
return;
|
||||
}
|
||||
if (e.button === 0) {
|
||||
// Left click
|
||||
if (!game?.isRevealed[x][y]) {
|
||||
updateGame((game) => game?.reveal(x, y));
|
||||
} else {
|
||||
const neighborFlagCount = game
|
||||
?.getNeighborFlags(x, y)
|
||||
.filter((n) => n).length;
|
||||
const value = game?.getValue(x, y);
|
||||
if (neighborFlagCount === value) {
|
||||
updateGame((game) => {
|
||||
if (!game?.isFlagged[x - 1]?.[y]) game?.reveal(x - 1, y);
|
||||
if (!game?.isFlagged[x - 1]?.[y - 1])
|
||||
game?.reveal(x - 1, y - 1);
|
||||
if (!game?.isFlagged[x - 1]?.[y + 1])
|
||||
game?.reveal(x - 1, y + 1);
|
||||
if (!game?.isFlagged[x]?.[y - 1]) game?.reveal(x, y - 1);
|
||||
if (!game?.isFlagged[x]?.[y + 1]) game?.reveal(x, y + 1);
|
||||
if (!game?.isFlagged[x + 1]?.[y - 1])
|
||||
game?.reveal(x + 1, y - 1);
|
||||
if (!game?.isFlagged[x + 1]?.[y]) game?.reveal(x + 1, y);
|
||||
if (!game?.isFlagged[x + 1]?.[y + 1])
|
||||
game?.reveal(x + 1, y + 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (e.button === 2 && !game?.isRevealed[x][y]) {
|
||||
// Right click
|
||||
updateGame((game) => game?.flag(x, y));
|
||||
}
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
export class Game {
|
||||
mines: boolean[][] = [];
|
||||
minesCount: number = 0;
|
||||
isRevealed: boolean[][] = [];
|
||||
isFlagged: boolean[][] = [];
|
||||
isGameOver: boolean = false;
|
||||
startTime: number = Date.now();
|
||||
|
||||
constructor(width: number, height: number, mines: number) {
|
||||
if (mines > width * height) {
|
||||
throw new Error("Too many mines");
|
||||
}
|
||||
this.minesCount = mines;
|
||||
for (let i = 0; i < width; i++) {
|
||||
this.mines.push(new Array(height).fill(false));
|
||||
this.isRevealed.push(new Array(height).fill(false));
|
||||
this.isFlagged.push(new Array(height).fill(false));
|
||||
}
|
||||
while (mines > 0) {
|
||||
const x = Math.floor(Math.random() * width);
|
||||
const y = Math.floor(Math.random() * height);
|
||||
if (!this.mines[x][y]) {
|
||||
this.mines[x][y] = true;
|
||||
mines--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getWidth() {
|
||||
return this.mines.length;
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
return this.mines[0].length;
|
||||
}
|
||||
|
||||
isMine(x: number, y: number) {
|
||||
return this.mines[x][y];
|
||||
}
|
||||
|
||||
flag(x: number, y: number) {
|
||||
this.isFlagged[x][y] = !this.isFlagged[x][y];
|
||||
}
|
||||
|
||||
isValid(x: number, y: number) {
|
||||
return x >= 0 && x < this.getWidth() && y >= 0 && y < this.getHeight();
|
||||
}
|
||||
|
||||
reveal(x: number, y: number) {
|
||||
if (!this.isValid(x, y)) return;
|
||||
this.isRevealed[x][y] = true;
|
||||
if (this.isMine(x, y)) {
|
||||
this.isGameOver = true;
|
||||
return;
|
||||
}
|
||||
const value = this.getValue(x, y);
|
||||
if (value === 0) {
|
||||
if (this.isValid(x - 1, y - 1) && !this.isRevealed[x - 1]?.[y - 1])
|
||||
this.reveal(x - 1, y - 1);
|
||||
if (this.isValid(x, y - 1) && !this.isRevealed[x]?.[y - 1])
|
||||
this.reveal(x, y - 1);
|
||||
if (this.isValid(x + 1, y - 1) && !this.isRevealed[x + 1]?.[y - 1])
|
||||
this.reveal(x + 1, y - 1);
|
||||
if (this.isValid(x - 1, y) && !this.isRevealed[x - 1]?.[y])
|
||||
this.reveal(x - 1, y);
|
||||
if (this.isValid(x + 1, y) && !this.isRevealed[x + 1]?.[y])
|
||||
this.reveal(x + 1, y);
|
||||
if (this.isValid(x - 1, y + 1) && !this.isRevealed[x - 1]?.[y + 1])
|
||||
this.reveal(x - 1, y + 1);
|
||||
if (this.isValid(x, y + 1) && !this.isRevealed[x]?.[y + 1])
|
||||
this.reveal(x, y + 1);
|
||||
if (this.isValid(x + 1, y + 1) && !this.isRevealed[x + 1]?.[y + 1])
|
||||
this.reveal(x + 1, y + 1);
|
||||
}
|
||||
}
|
||||
|
||||
getHasWon() {
|
||||
if (this.isGameOver) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < this.getWidth(); i++) {
|
||||
for (let j = 0; j < this.getHeight(); j++) {
|
||||
if (!this.isRevealed[i][j] && !this.isFlagged[i][j]) {
|
||||
return false;
|
||||
}
|
||||
if (this.isMine(i, j) && !this.isFlagged[i][j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
getMinesLeft() {
|
||||
return this.minesCount - this.isFlagged.flat().filter((m) => m).length;
|
||||
}
|
||||
|
||||
getNeighborFlags(x: number, y: number) {
|
||||
const neighbors = [
|
||||
this.isFlagged[x - 1]?.[y - 1],
|
||||
this.isFlagged[x]?.[y - 1],
|
||||
this.isFlagged[x + 1]?.[y - 1],
|
||||
this.isFlagged[x - 1]?.[y],
|
||||
this.isFlagged[x + 1]?.[y],
|
||||
this.isFlagged[x - 1]?.[y + 1],
|
||||
this.isFlagged[x]?.[y + 1],
|
||||
this.isFlagged[x + 1]?.[y + 1],
|
||||
];
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
getNeighborMines(x: number, y: number) {
|
||||
const neighbors = [
|
||||
this.mines[x - 1]?.[y - 1],
|
||||
this.mines[x]?.[y - 1],
|
||||
this.mines[x + 1]?.[y - 1],
|
||||
this.mines[x - 1]?.[y],
|
||||
this.mines[x + 1]?.[y],
|
||||
this.mines[x - 1]?.[y + 1],
|
||||
this.mines[x]?.[y + 1],
|
||||
this.mines[x + 1]?.[y + 1],
|
||||
];
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
getValue(x: number, y: number) {
|
||||
const neighbors = this.getNeighborMines(x, y);
|
||||
const mines = neighbors.filter((n) => n).length;
|
||||
return mines;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Game } from "./Game";
|
||||
import useSound from "use-sound";
|
||||
import explosion from "./sound/explosion.mp3";
|
||||
|
||||
const GameContext = createContext<Game | null>(null);
|
||||
const useGame = () => useContext(GameContext);
|
||||
|
||||
let updateGame: (cb: (game: Game) => void) => void;
|
||||
let resetGame: (width: number, height: number, mines: number) => void;
|
||||
|
||||
const GameProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [game, setGame] = useState<Game | null>(null);
|
||||
const [counter, setCounter] = useState(0);
|
||||
const [playSound] = useSound(explosion, {
|
||||
volume: 0.5,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (game?.isGameOver) {
|
||||
playSound();
|
||||
}
|
||||
}, [game?.isGameOver]);
|
||||
|
||||
useEffect(() => {
|
||||
const game = new Game(30, 20, 100);
|
||||
setGame(game);
|
||||
updateGame = (cb: (game: Game) => void) => {
|
||||
cb(game);
|
||||
setCounter((c) => c + 1);
|
||||
};
|
||||
resetGame = (width: number, height: number, mines: number) => {
|
||||
const game = new Game(width, height, mines);
|
||||
setGame(game);
|
||||
updateGame = (cb: (game: Game) => void) => {
|
||||
cb(game);
|
||||
setCounter((c) => c + 1);
|
||||
};
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<GameContext.Provider key={counter} value={game}>
|
||||
{children}
|
||||
</GameContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export { GameProvider, useGame, updateGame, resetGame };
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import { useState } from "react";
|
||||
import { resetGame, useGame } from "./GameContext";
|
||||
|
||||
function Options() {
|
||||
const game = useGame();
|
||||
const [width, setWidth] = useState(game?.getWidth() || 20);
|
||||
const [height, setHeight] = useState(game?.getHeight() || 20);
|
||||
const [mines, setMines] = useState(game?.minesCount || 20);
|
||||
const [showOptions, setShowOptions] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => setShowOptions(!showOptions)}>
|
||||
{showOptions ? "Hide" : "Show"} Options
|
||||
</button>
|
||||
{showOptions && (
|
||||
<>
|
||||
<p>
|
||||
Width:{" "}
|
||||
<input
|
||||
type="number"
|
||||
value={width}
|
||||
onChange={(e) => setWidth(Number(e.target.value))}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
Height:{" "}
|
||||
<input
|
||||
type="number"
|
||||
value={height}
|
||||
max="40"
|
||||
onChange={(e) => setHeight(Number(e.target.value))}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
Mines:{" "}
|
||||
<input
|
||||
type="number"
|
||||
value={mines}
|
||||
max="40"
|
||||
onChange={(e) => setMines(Number(e.target.value))}
|
||||
/>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
resetGame(width, height, mines);
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Options;
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useGame } from "./GameContext";
|
||||
import Confetti from "react-confetti-boom";
|
||||
|
||||
const Timer = () => {
|
||||
const game = useGame();
|
||||
const [currentTime, setCurrentTime] = useState(Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
if (game?.isGameOver || game?.getHasWon()) {
|
||||
return;
|
||||
}
|
||||
const interval = setInterval(() => {
|
||||
setCurrentTime(Date.now());
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="timer">
|
||||
<p style={{ width: "100px" }}>{game?.getMinesLeft()}</p>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "2rem",
|
||||
}}
|
||||
>
|
||||
{game?.getHasWon() ? "😎" : game?.isGameOver ? "😢" : "😐"}
|
||||
{game?.getHasWon() && <Confetti mode="boom" particleCount={301} />}
|
||||
</p>
|
||||
<p style={{ width: "100px", textAlign: "right" }}>
|
||||
{Math.max(0, Math.floor((currentTime - (game?.startTime || 0)) / 1000))}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Timer;
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
.game-board {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.game-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mine-button {
|
||||
background-color: #666;
|
||||
border: 1px solid black;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
font-size: 1.25rem;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
background: #111;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: auto;
|
||||
max-width: 1400px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.timer {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 2rem;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App.tsx";
|
||||
import "./index.css";
|
||||
import { GameProvider } from "./GameContext.tsx";
|
||||
|
||||
document.addEventListener("contextmenu", (event) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<GameProvider>
|
||||
<App />
|
||||
</GameProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"root":["./src/App.tsx","./src/Button.tsx","./src/Game.ts","./src/GameContext.tsx","./src/Options.tsx","./src/Timer.tsx","./src/main.tsx","./src/vite-env.d.ts"],"version":"5.6.2"}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"root":["./vite.config.ts"],"version":"5.6.2"}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
Loading…
Reference in New Issue