]> git.cworth.org Git - lmno.games/commitdiff
tictactoe: Implement a minimally-functional multi-player game
authorCarl Worth <cworth@cworth.org>
Wed, 27 May 2020 04:13:49 +0000 (21:13 -0700)
committerCarl Worth <cworth@cworth.org>
Wed, 27 May 2020 04:13:49 +0000 (21:13 -0700)
Now that there's a server implemented at lmno.games/tictactoe/LMNO/
it's a simple matter to break the call chain at handleClick() to
not do any state updates, but instead hit the /move API, then wait
for data to come back from the /events API and only when the server
returns with that, _then_ to update the state.

So, with multiple clients connected, each client will now seem the
game state updated with each move.

As far as the gameplay of Tic Tac Toe, the only major feature missing
is that players are not yet restricted to play as either X or O but
can instead send events for either player. Obviously, that won't be
hard to fix.

Then, as far as implementation, this code copies the add_message()
function from lmno.js, so we'll want to find a better way to do
that. And there may be some refactoring to be done for event handling
as well, (to reduce code duplication between game implementations).

But this code does use the fetch() API which does seem easier to use
than XMLHttpRequest so that's something we will probably want to
switch to in existing code.

tictactoe/tictactoe.jsx

index eff7eff32f6df572437130d0ec2c0e8ecf8c2a7b..654d55ee17c45da10c83ab4ecf1b4ed276b719e7 100644 (file)
@@ -1,3 +1,30 @@
+function undisplay(element) {
+  element.style.display="none";
+}
+
+function add_message(severity, message) {
+  message = `<div class="message ${severity}" onclick="undisplay(this)">
+<span class="hide-button" onclick="undisplay(this.parentElement)">&times;</span>
+${message}
+</div>`;
+  const message_area = document.getElementById('message-area');
+  message_area.insertAdjacentHTML('beforeend', message);
+}
+
+const events = new EventSource("events");
+
+events.onerror = function(event) {
+  if (event.target.readyState === EventSource.CLOSED) {
+      add_message("danger", "Connection to server lost.");
+  }
+};
+
+events.addEventListener("move", event => {
+  const square = JSON.parse(event.data);
+
+  window.game.receiveMove(square);
+});
+
 function Square(props) {
   return (
     <button className="square" onClick={props.onClick}>
@@ -39,6 +66,17 @@ class Board extends React.Component {
   }
 }
 
+function fetch_post_json(api = '', data = {}) {
+  const response = fetch(api, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json'
+    },
+    body: JSON.stringify(data)
+  });
+  return response;
+}
+
 class Game extends React.Component {
   constructor(props) {
     super(props);
@@ -53,7 +91,11 @@ class Game extends React.Component {
     };
   }
 
-  handleClick(i) {
+  sendMove(i) {
+    return fetch_post_json("move", { square: i });
+  }
+
+  receiveMove(i) {
     const history = this.state.history.slice(0, this.state.stepNumber + 1);
     const current = history[history.length - 1];
     const squares = current.squares.slice();
@@ -72,6 +114,17 @@ class Game extends React.Component {
     });
   }
 
+  async handleClick(i) {
+    const response = await this.sendMove(i);
+    if (response.status == 200) {
+      const legal = await response.json();
+      if (! legal)
+        add_message("danger", `Illegal move.`);
+    } else {
+      add_message("danger", `Error occurred sending move`);
+    }
+  }
+
   jumpTo(step) {
     this.setState({
       stepNumber: step,
@@ -121,7 +174,9 @@ class Game extends React.Component {
 
 // ========================================
 
-ReactDOM.render(<Game />, document.getElementById("tictactoe"));
+ReactDOM.render(<Game
+                  ref={(me) => window.game = me}
+                />, document.getElementById("tictactoe"));
 
 function calculateWinner(squares) {
   const lines = [