diff --git a/.node-version b/.node-version index 1825d0e..58a4133 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -nodejs 16.13.0 +16.13.0 diff --git a/tic-tac-toe-app/.prettierrc.json b/tic-tac-toe-app/.prettierrc.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tic-tac-toe-app/.prettierrc.json @@ -0,0 +1 @@ +{} diff --git a/tic-tac-toe-app/package-lock.json b/tic-tac-toe-app/package-lock.json index a01316d..d9b240d 100644 --- a/tic-tac-toe-app/package-lock.json +++ b/tic-tac-toe-app/package-lock.json @@ -20,6 +20,9 @@ "react-scripts": "4.0.3", "typescript": "^4.5.2", "web-vitals": "^1.1.2" + }, + "devDependencies": { + "prettier": "2.5.0" } }, "node_modules/@babel/code-frame": { @@ -15689,6 +15692,18 @@ "node": ">=0.10.0" } }, + "node_modules/prettier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.0.tgz", + "integrity": "sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -33336,6 +33351,12 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, + "prettier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.0.tgz", + "integrity": "sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg==", + "dev": true + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", diff --git a/tic-tac-toe-app/package.json b/tic-tac-toe-app/package.json index b95e677..ba77538 100644 --- a/tic-tac-toe-app/package.json +++ b/tic-tac-toe-app/package.json @@ -39,5 +39,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "prettier": "2.5.0" } } diff --git a/tic-tac-toe-app/src/index.tsx b/tic-tac-toe-app/src/index.tsx index b15bfb1..e02db4a 100644 --- a/tic-tac-toe-app/src/index.tsx +++ b/tic-tac-toe-app/src/index.tsx @@ -1,29 +1,38 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import reportWebVitals from './reportWebVitals'; +import React from "react"; +import ReactDOM from "react-dom"; +import "./index.css"; +import reportWebVitals from "./reportWebVitals"; -class Square extends React.Component { - render() { - return ( - - ); - } +type SquareValue = "X" | "O" | null; +type SquareProps = { + value: SquareValue; + onClick: () => void; +}; +function Square(props: SquareProps) { + return ( + + ); } -class Board extends React.Component { - renderSquare(_: number) { - return ; +type BoardProps = { + squares: SquareValue[]; + onClick: (i: number) => void; +}; +class Board extends React.Component { + renderSquare(i: number) { + return ( + this.props.onClick(i)} + /> + ); } render() { - const status = 'Next player: X'; - return (
-
{status}
{this.renderSquare(0)} {this.renderSquare(1)} @@ -43,30 +52,110 @@ class Board extends React.Component { ); } } - -class Game extends React.Component { +type GameHistory = { + squares: SquareValue[]; +}; +type GameProps = {}; +type GameState = { + history: GameHistory[]; + stepNumber: number; + xIsNext: boolean; +}; +class Game extends React.Component { + constructor(props: GameProps) { + super(props); + this.state = { + history: [ + { + squares: Array(9).fill(null), + }, + ], + stepNumber: 0, + xIsNext: true, + }; + } + handleClick(i: number) { + const history = this.state.history.slice(0, this.state.stepNumber + 1); + const current = history[this.state.stepNumber]; + const squares = current.squares.slice(); + if (calculateWinner(squares) || squares[i]) { + return; + } + squares[i] = this.state.xIsNext ? "X" : "O"; + this.setState({ + history: history.concat([ + { + squares: squares, + }, + ]), + stepNumber: history.length, + xIsNext: !this.state.xIsNext, + }); + } + jumpTo(step: number) { + this.setState({ + stepNumber: step, + xIsNext: step % 2 === 0, + }); + } render() { + const history = this.state.history; + const current = history[this.state.stepNumber]; + const winner = calculateWinner(current.squares); + let status; + if (winner) { + status = "Winner: " + winner; + } else { + status = "Next player: " + (this.state.xIsNext ? "X" : "O"); + } + const moves = history.map((_, move) => { + const desc = move ? "Go to move #" + move : "Go to game start"; + return ( +
  • + +
  • + ); + }); return (
    - + this.handleClick(i)} + />
    -
    {/* status */}
    -
      {/* TODO */}
    +
    {status}
    +
      {moves}
    ); } } -// ======================================== +function calculateWinner(squares: Array) { + const lines = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], + ]; + for (let i = 0; i < lines.length; i++) { + const [a, b, c] = lines[i]; + if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { + return squares[a]; + } + } + return null; +} -ReactDOM.render( - , - document.getElementById('root') -); +// ======================================== +ReactDOM.render(, document.getElementById("root")); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log))