10 function undisplay(element) {
11 element.style.display="none";
14 function add_message(severity, message) {
15 message = `<div class="message ${severity}" onclick="undisplay(this)">
16 <span class="hide-button" onclick="undisplay(this.parentElement)">×</span>
19 const message_area = document.getElementById('message-area');
20 message_area.insertAdjacentHTML('beforeend', message);
23 /*********************************************************
24 * Handling server-sent event stream *
25 *********************************************************/
27 const events = new EventSource("events");
29 events.onerror = function(event) {
30 if (event.target.readyState === EventSource.CLOSED) {
31 add_message("danger", "Connection to server lost.");
35 events.addEventListener("game-info", event => {
36 const info = JSON.parse(event.data);
38 window.game.set_game_info(info);
41 events.addEventListener("player-info", event => {
42 const info = JSON.parse(event.data);
44 window.game.set_player_info(info);
47 events.addEventListener("player-update", event => {
48 const info = JSON.parse(event.data);
50 if (info.id === window.game.state.player_info.id)
51 window.game.set_player_info(info);
54 events.addEventListener("move", event => {
55 const move = JSON.parse(event.data);
57 window.game.receive_move(move);
60 events.addEventListener("game-state", event => {
61 const state = JSON.parse(event.data);
63 window.game.reset_board();
65 for (let square of state.moves) {
66 window.game.receive_move(square);
70 /*********************************************************
71 * Game and supporting classes *
72 *********************************************************/
74 function GameInfo(props) {
76 <div className="game-info">
78 Invite a friend to play by sending this URL: {props.url}
83 function PlayerInfo(props) {
85 <div className="player-info">
87 {props.name}, ID: {props.id}, on team: {props.team}
92 function Square(props) {
93 let className = "square";
96 className += " occupied";
97 } else if (props.active) {
101 const onClick = props.active ? props.onClick : null;
104 <div className={className}
111 class Board extends React.Component {
113 const value = this.props.squares[i];
117 active={! this.props.game_over && ! value}
118 onClick={() => this.props.onClick(i)}
126 <div className="board-row">
127 {this.render_square(0)}
128 {this.render_square(1)}
129 {this.render_square(2)}
131 <div className="board-row">
132 {this.render_square(3)}
133 {this.render_square(4)}
134 {this.render_square(5)}
136 <div className="board-row">
137 {this.render_square(6)}
138 {this.render_square(7)}
139 {this.render_square(8)}
146 function fetch_post_json(api = '', data = {}) {
147 const response = fetch(api, {
150 'Content-Type': 'application/json'
152 body: JSON.stringify(data)
157 class Game extends React.Component {
165 squares: Array(9).fill(null)
173 set_game_info(info) {
179 set_player_info(info) {
186 return fetch_post_json("move", { move: i });
193 squares: Array(9).fill(null)
202 const history = this.state.history.slice(0, this.state.step_number + 1);
203 const current = history[history.length - 1];
204 const squares = current.squares.slice();
205 if (calculate_winner(squares) || squares[i]) {
208 squares[i] = Team.properties[this.state.next_to_play].name;
210 if (this.state.next_to_play === Team.X)
211 next_to_play = Team.O;
213 next_to_play = Team.X;
215 history: history.concat([
220 step_number: history.length,
221 next_to_play: next_to_play
225 async handle_click(i) {
226 const response = await this.send_move(i);
227 if (response.status == 200) {
228 const result = await response.json();
230 add_message("danger", result.message);
232 add_message("danger", `Error occurred sending move`);
237 const history = this.state.history;
238 const current = history[this.state.step_number];
239 const winner = calculate_winner(current.squares);
243 status = "Winner: " + winner;
245 status = "Next player: " + (Team.properties[this.state.next_to_play].name);
250 id={this.state.game_info.id}
251 url={this.state.game_info.url}
254 id={this.state.player_info.id}
255 name={this.state.player_info.name}
256 team={this.state.player_info.team}
258 <div className="game">
260 <div className="game-board">
263 squares={current.squares}
264 onClick={i => this.handle_click(i)}
272 ReactDOM.render(<Game
273 ref={(me) => window.game = me}
274 />, document.getElementById("tictactoe"));
276 function calculate_winner(squares) {
287 for (let i = 0; i < lines.length; i++) {
288 const [a, b, c] = lines[i];
289 if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {