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.
+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)">×</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}>
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
+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);
class Game extends React.Component {
constructor(props) {
super(props);
+ 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();
const history = this.state.history.slice(0, this.state.stepNumber + 1);
const current = history[history.length - 1];
const squares = current.squares.slice();
+ 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,
jumpTo(step) {
this.setState({
stepNumber: step,
// ========================================
// ========================================
-ReactDOM.render(<Game />, document.getElementById("tictactoe"));
+ReactDOM.render(<Game
+ ref={(me) => window.game = me}
+ />, document.getElementById("tictactoe"));
function calculateWinner(squares) {
const lines = [
function calculateWinner(squares) {
const lines = [