function team_symbol(team) { if (team === "+") return "+"; else return "o"; } function undisplay(element) { element.style.display="none"; } function add_message(severity, message) { message = `
× ${message}
`; const message_area = document.getElementById('message-area'); message_area.insertAdjacentHTML('beforeend', message); } /********************************************************* * Handling server-sent event stream * *********************************************************/ const events = new EventSource("events"); events.onerror = function(event) { if (event.target.readyState === EventSource.CLOSED) { setTimeout(() => { add_message("danger", "Connection to server lost."); }, 1000); } }; events.addEventListener("game-info", event => { const info = JSON.parse(event.data); window.game.set_game_info(info); }); events.addEventListener("player-info", event => { const info = JSON.parse(event.data); window.game.set_player_info(info); }); events.addEventListener("player-enter", event => { const info = JSON.parse(event.data); window.game.set_other_player_info(info); }); events.addEventListener("player-update", event => { const info = JSON.parse(event.data); if (info.id === window.game.state.player_info.id) window.game.set_player_info(info); else window.game.set_other_player_info(info); }); events.addEventListener("move", event => { const move = JSON.parse(event.data); window.game.receive_move(move); }); events.addEventListener("game-state", event => { const state = JSON.parse(event.data); window.game.reset_board(); for (let square of state.moves) { window.game.receive_move(square); } }); /********************************************************* * Game and supporting classes * *********************************************************/ function copy_to_clipboard(id) { const tmp = document.createElement("input"); tmp.setAttribute("value", document.getElementById(id).innerHTML); document.body.appendChild(tmp); tmp.select(); document.execCommand("copy"); document.body.removeChild(tmp); } function GameInfo(props) { if (! props.id) return null; return (
{props.id} {" "} Share this link to invite a friend:{" "} {props.url} {" "}
); } function TeamButton(props) { return ; } function TeamChoices(props) { let other_team; if (props.player.team === "+") other_team = "o"; else other_team = "+"; if (props.player.team === "") { if (props.first_move) { return null; } else { return [ , " ", ]; } } else { return ; } } function PlayerInfo(props) { if (! props.player.id) return null; const choices = ; return (
Players: {props.player.name} {props.player.team ? ` (${props.player.team})` : ""} {props.first_move ? "" : " "} {choices} {props.other_players.map(other => ( {", "} {other.name} {other.team ? ` (${other.team})` : ""} ))}
); } function Square(props) { let className = "square"; if (props.value) { className += " occupied"; } else if (props.active) { className += " open"; } const onClick = props.active ? props.onClick : null; return (
{props.value}
); } function MiniGrid(props) { function grid_square(j) { const value = props.squares[j]; return ( props.onClick(j)} /> ); } return (
{grid_square(0)} {grid_square(1)} {grid_square(2)} {grid_square(3)} {grid_square(4)} {grid_square(5)} {grid_square(6)} {grid_square(7)} {grid_square(8)}
); } class Board extends React.Component { mini_grid(i) { const squares = this.props.squares[i]; return ( this.props.onClick(i,j)} /> ); } render() { return (
{this.mini_grid(0)} {this.mini_grid(1)} {this.mini_grid(2)} {this.mini_grid(3)} {this.mini_grid(4)} {this.mini_grid(5)} {this.mini_grid(6)} {this.mini_grid(7)} {this.mini_grid(8)}
); } } function fetch_method_json(method, api = '', data = {}) { const response = fetch(api, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return response; } function fetch_post_json(api = '', data = {}) { return fetch_method_json('POST', api, data); } async function fetch_put_json(api = '', data = {}) { return fetch_method_json('PUT', api, data); } class Game extends React.Component { constructor(props) { super(props); this.state = { game_info: {}, player_info: {}, other_players: [], squares: Array(9).fill(null).map(() => Array(9).fill(null)), moves: 0, next_to_play: "+" }; } set_game_info(info) { this.setState({ game_info: info }); } set_player_info(info) { this.setState({ player_info: info }); } set_other_player_info(info) { const other_players_copy = [...this.state.other_players]; const idx = other_players_copy.findIndex(o => o.id === info.id); if (idx >= 0) { other_players_copy[idx] = info; } else { other_players_copy.push(info); } this.setState({ other_players: other_players_copy }); } reset_board() { this.setState({ next_to_play: "+" }); } receive_move(move) { if (this.state.moves === 81) { return; } const symbol = team_symbol(this.state.next_to_play); const new_squares = this.state.squares.map(arr => arr.slice()); new_squares[move[0]][move[1]] = symbol; let next_to_play; if (this.state.next_to_play === "+") next_to_play = "o"; else next_to_play = "+"; this.setState({ squares: new_squares, moves: this.state.moves + 1, next_to_play: next_to_play }); } async handle_click(i, j, first_move) { let move = { move: [i, j] }; if (first_move) { move.assert_first_move = true; } const response = await fetch_post_json("move", move); if (response.status == 200) { const result = await response.json(); if (! result.legal) add_message("danger", result.message); } else { add_message("danger", `Error occurred sending move`); } } join_team(team) { fetch_put_json("player", {team: team}); } render() { const state = this.state; const first_move = state.moves === 0; const my_team = state.player_info.team; var board_active; let status; if (this.state.moves.length === 81) { status = "Game over"; board_active = false; } else if (first_move) { if (state.other_players.length == 0) { status = "You can move or wait for another player to join."; } else { let qualifier; if (state.other_players.length == 1) { qualifier = "Either"; } else { qualifier = "Any"; } status = `${qualifier} player can make the first move.`; } board_active = true; } else if (my_team === "") { status = "You're just watching the game."; board_active = false; } else if (my_team === state.next_to_play) { status = "Your turn. Make a move."; board_active = true; } else { status = "Waiting for another player to "; if (state.other_players.length == 0) { status += "join."; } else { status += "move."; } board_active = false; } return [ , ,
{status}
this.handle_click(i, j, first_move)} />
]; } } ReactDOM.render( window.game = me} />, document.getElementById("scribe"));