From: Carl Worth Date: Mon, 25 May 2020 20:37:10 +0000 (-0700) Subject: Add a simple tictactoe game, implemented with React X-Git-Url: https://git.cworth.org/git?p=lmno.games;a=commitdiff_plain;h=f0c30f7cd59ecfca24e6a7332910f87a6b2045e6 Add a simple tictactoe game, implemented with React This isn't a proper LMNO game, (it's not networked at all), but it's a starting point for seeing how we might structure a React-based client (and we can develop this into a proper LMNO game). In fact, the source code here came directly from the React tutorial here: https://reactjs.org/tutorial/tutorial.html Note that I've not pulled in the 1000+ npm modules that would have come from using create-react-app as recommended in that tutorial. As can be seen here, by example, none of them are needed. The only build tool requireed is something to compile JSX and we've got that working via the Debian-provided babel packages (as seen in the most recent commits here). --- diff --git a/tictactoe/Makefile b/tictactoe/Makefile new file mode 100644 index 0000000..9f07401 --- /dev/null +++ b/tictactoe/Makefile @@ -0,0 +1,12 @@ +# Defer all targets up to the upper-level +# +# This requires two recipes. The first to cover the case of no +# explicit target specifed (so when invoked as "make" we call "make" +# at the upper-level) and then a .DEFAULT recipe to pass any explicit +# target up as well, (so that an invocation of "make foo" results in a +# call to "make foo" above. +all: + $(MAKE) -C .. + +.DEFAULT: + $(MAKE) -C .. $@ diff --git a/tictactoe/index.html b/tictactoe/index.html new file mode 100644 index 0000000..6a6d289 --- /dev/null +++ b/tictactoe/index.html @@ -0,0 +1,17 @@ + + + + + + + Tic-tac-toe + + + + + + + +
+ + diff --git a/tictactoe/tictactoe.css b/tictactoe/tictactoe.css new file mode 100644 index 0000000..0e56082 --- /dev/null +++ b/tictactoe/tictactoe.css @@ -0,0 +1,50 @@ +body { + font: 14px "Century Gothic", Futura, sans-serif; + margin: 20px; +} + +ol, ul { + padding-left: 30px; +} + +.board-row:after { + clear: both; + content: ""; + display: table; +} + +.status { + margin-bottom: 10px; +} + +.square { + background: #fff; + border: 1px solid #999; + float: left; + font-size: 24px; + font-weight: bold; + line-height: 34px; + height: 34px; + margin-right: -1px; + margin-top: -1px; + padding: 0; + text-align: center; + width: 34px; +} + +.square:focus { + outline: none; +} + +.kbd-navigation .square:focus { + background: #ddd; +} + +.game { + display: flex; + flex-direction: row; +} + +.game-info { + margin-left: 20px; +} diff --git a/tictactoe/tictactoe.jsx b/tictactoe/tictactoe.jsx new file mode 100644 index 0000000..eff7eff --- /dev/null +++ b/tictactoe/tictactoe.jsx @@ -0,0 +1,144 @@ +function Square(props) { + return ( + + ); +} + +class Board extends React.Component { + renderSquare(i) { + return ( + this.props.onClick(i)} + /> + ); + } + + render() { + return ( +
+
+ {this.renderSquare(0)} + {this.renderSquare(1)} + {this.renderSquare(2)} +
+
+ {this.renderSquare(3)} + {this.renderSquare(4)} + {this.renderSquare(5)} +
+
+ {this.renderSquare(6)} + {this.renderSquare(7)} + {this.renderSquare(8)} +
+
+ ); + } +} + +class Game extends React.Component { + constructor(props) { + super(props); + this.state = { + history: [ + { + squares: Array(9).fill(null) + } + ], + stepNumber: 0, + xIsNext: true + }; + } + + handleClick(i) { + const history = this.state.history.slice(0, this.state.stepNumber + 1); + const current = history[history.length - 1]; + 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) { + 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); + + const moves = history.map((step, move) => { + const desc = move ? + 'Go to move #' + move : + 'Go to game start'; + return ( +
  • + +
  • + ); + }); + + let status; + if (winner) { + status = "Winner: " + winner; + } else { + status = "Next player: " + (this.state.xIsNext ? "X" : "O"); + } + + return ( +
    +
    + this.handleClick(i)} + /> +
    +
    +
    {status}
    +
      {moves}
    +
    +
    + ); + } +} + +// ======================================== + +ReactDOM.render(, document.getElementById("tictactoe")); + +function calculateWinner(squares) { + 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; +}