1 function team_symbol(team) {
8 function undisplay(element) {
9 element.style.display="none";
12 function add_message(severity, message) {
13 message = `<div class="message ${severity}" onclick="undisplay(this)">
14 <span class="hide-button" onclick="undisplay(this.parentElement)">×</span>
17 const message_area = document.getElementById('message-area');
18 message_area.insertAdjacentHTML('beforeend', message);
21 /*********************************************************
22 * Handling server-sent event stream *
23 *********************************************************/
25 const events = new EventSource("events");
27 events.onerror = function(event) {
28 if (event.target.readyState === EventSource.CLOSED) {
30 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-enter", event => {
48 const info = JSON.parse(event.data);
50 window.game.set_other_player_info(info);
53 events.addEventListener("player-update", event => {
54 const info = JSON.parse(event.data);
56 if (info.id === window.game.state.player_info.id)
57 window.game.set_player_info(info);
59 window.game.set_other_player_info(info);
62 events.addEventListener("move", event => {
63 const move = JSON.parse(event.data);
65 window.game.receive_move(move);
68 events.addEventListener("game-state", event => {
69 const state = JSON.parse(event.data);
71 window.game.reset_board();
73 for (let square of state.moves) {
74 window.game.receive_move(square);
78 /*********************************************************
79 * Game and supporting classes *
80 *********************************************************/
82 function copy_to_clipboard(id)
84 const tmp = document.createElement("input");
85 tmp.setAttribute("value", document.getElementById(id).innerHTML);
86 document.body.appendChild(tmp);
88 document.execCommand("copy");
89 document.body.removeChild(tmp);
92 function GameInfo(props) {
97 <div className="game-info">
98 <span className="game-id">{props.id}</span>
100 Share this link to invite a friend:{" "}
101 <span id="game-share-url">{props.url}</span>
105 onClick={() => copy_to_clipboard('game-share-url')}
111 function TeamButton(props) {
112 return <button className="inline"
113 onClick={() => props.game.join_team(props.team)}>
118 function TeamChoices(props) {
120 if (props.player.team === "+")
125 if (props.player.team === "") {
126 if (props.first_move) {
130 <TeamButton key="+" game={props.game} team="+" label="Join 🞥" />,
132 <TeamButton key="o" game={props.game} team="o" label="Join 🞇" />
136 return <TeamButton game={props.game} team={other_team} label="Switch" />;
140 function PlayerInfo(props) {
141 if (! props.player.id)
144 const choices = <TeamChoices
146 first_move={props.first_move}
147 player={props.player}
151 <div className="player-info">
152 <span className="players-header">Players: </span>
154 {props.player.team ? ` (${props.player.team})` : ""}
155 {props.first_move ? "" : " "}
157 {props.other_players.map(other => (
158 <span key={other.id}>
161 {other.team ? ` (${other.team})` : ""}
168 function Square(props) {
169 let className = "square";
172 className += " occupied";
173 } else if (props.active) {
174 className += " open";
177 const onClick = props.active ? props.onClick : null;
180 <div className={className}
187 function MiniGrid(props) {
188 function grid_square(j) {
189 const value = props.squares[j];
193 active={props.active}
194 onClick={() => props.onClick(j)}
200 <div className="mini-grid">
214 class Board extends React.Component {
216 const squares = this.props.squares[i];
220 active={this.props.active}
221 onClick={(j) => this.props.onClick(i,j)}
228 <div className="board-container">
229 <div className="board">
245 function fetch_method_json(method, api = '', data = {}) {
246 const response = fetch(api, {
249 'Content-Type': 'application/json'
251 body: JSON.stringify(data)
256 function fetch_post_json(api = '', data = {}) {
257 return fetch_method_json('POST', api, data);
260 async function fetch_put_json(api = '', data = {}) {
261 return fetch_method_json('PUT', api, data);
264 class Game extends React.Component {
271 squares: Array(9).fill(null).map(() => Array(9).fill(null)),
277 set_game_info(info) {
283 set_player_info(info) {
289 set_other_player_info(info) {
290 const other_players_copy = [...this.state.other_players];
291 const idx = other_players_copy.findIndex(o => o.id === info.id);
293 other_players_copy[idx] = info;
295 other_players_copy.push(info);
298 other_players: other_players_copy
309 if (this.state.moves === 81) {
312 const symbol = team_symbol(this.state.next_to_play);
313 const new_squares = this.state.squares.map(arr => arr.slice());
314 new_squares[move[0]][move[1]] = symbol;
316 if (this.state.next_to_play === "+")
321 squares: new_squares,
322 moves: this.state.moves + 1,
323 next_to_play: next_to_play
327 async handle_click(i, j, first_move) {
332 move.assert_first_move = true;
334 const response = await fetch_post_json("move", move);
335 if (response.status == 200) {
336 const result = await response.json();
338 add_message("danger", result.message);
340 add_message("danger", `Error occurred sending move`);
345 fetch_put_json("player", {team: team});
349 const state = this.state;
350 const first_move = state.moves === 0;
351 const my_team = state.player_info.team;
355 if (this.state.moves.length === 81)
357 status = "Game over";
358 board_active = false;
362 if (state.other_players.length == 0) {
363 status = "You can move or wait for another player to join.";
366 if (state.other_players.length == 1) {
367 qualifier = "Either";
371 status = `${qualifier} player can make the first move.`;
375 else if (my_team === "")
377 status = "You're just watching the game.";
378 board_active = false;
380 else if (my_team === state.next_to_play)
382 status = "Your turn. Make a move.";
387 status = "Waiting for another player to ";
388 if (state.other_players.length == 0) {
393 board_active = false;
399 id={state.game_info.id}
400 url={state.game_info.url}
405 first_move={first_move}
406 player={state.player_info}
407 other_players={state.other_players}
409 <div key="game" className="game">
411 <div className="game-board">
413 active={board_active}
414 squares={state.squares}
415 onClick={(i,j) => this.handle_click(i, j, first_move)}
423 ReactDOM.render(<Game
424 ref={(me) => window.game = me}
425 />, document.getElementById("scribe"));