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 const events = new EventSource("events");
25 events.onerror = function(event) {
26 if (event.target.readyState === EventSource.CLOSED) {
27 add_message("danger", "Connection to server lost.");
31 events.addEventListener("move", event => {
32 const move = JSON.parse(event.data);
34 window.game.receive_move(move);
37 events.addEventListener("game-state", event => {
38 const state = JSON.parse(event.data);
40 window.game.reset_state();
42 for (let square of state.moves) {
43 window.game.receive_move(square);
47 function Square(props) {
48 let className = "square";
51 className += " occupied";
52 } else if (props.active) {
56 const onClick = props.active ? props.onClick : null;
59 <div className={className}
66 class Board extends React.Component {
68 const value = this.props.squares[i];
72 active={! this.props.game_over && ! value}
73 onClick={() => this.props.onClick(i)}
81 <div className="board-row">
82 {this.render_square(0)}
83 {this.render_square(1)}
84 {this.render_square(2)}
86 <div className="board-row">
87 {this.render_square(3)}
88 {this.render_square(4)}
89 {this.render_square(5)}
91 <div className="board-row">
92 {this.render_square(6)}
93 {this.render_square(7)}
94 {this.render_square(8)}
101 function fetch_post_json(api = '', data = {}) {
102 const response = fetch(api, {
105 'Content-Type': 'application/json'
107 body: JSON.stringify(data)
112 class Game extends React.Component {
118 squares: Array(9).fill(null)
127 return fetch_post_json("move", { move: i });
134 squares: Array(9).fill(null)
143 const history = this.state.history.slice(0, this.state.step_number + 1);
144 const current = history[history.length - 1];
145 const squares = current.squares.slice();
146 if (calculate_winner(squares) || squares[i]) {
149 squares[i] = Team.properties[this.state.next_to_play].name;
151 if (this.state.next_to_play === Team.X)
152 next_to_play = Team.O;
154 next_to_play = Team.X;
156 history: history.concat([
161 step_number: history.length,
162 next_to_play: next_to_play
166 async handle_click(i) {
167 const response = await this.send_move(i);
168 if (response.status == 200) {
169 const result = await response.json();
171 add_message("danger", result.message);
173 add_message("danger", `Error occurred sending move`);
178 const history = this.state.history;
179 const current = history[this.state.step_number];
180 const winner = calculate_winner(current.squares);
184 status = "Winner: " + winner;
186 status = "Next player: " + (Team.properties[this.state.next_to_play].name);
190 <div className="game">
191 <div className="game-info">
194 <div className="game-board">
197 squares={current.squares}
198 onClick={i => this.handle_click(i)}
206 // ========================================
208 ReactDOM.render(<Game
209 ref={(me) => window.game = me}
210 />, document.getElementById("tictactoe"));
212 function calculate_winner(squares) {
223 for (let i = 0; i < lines.length; i++) {
224 const [a, b, c] = lines[i];
225 if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {