]> git.cworth.org Git - lmno.games/blob - tictactoe/tictactoe.jsx
Change styling to not put game info to the right of the board
[lmno.games] / tictactoe / tictactoe.jsx
1 function undisplay(element) {
2   element.style.display="none";
3 }
4
5 function add_message(severity, message) {
6   message = `<div class="message ${severity}" onclick="undisplay(this)">
7 <span class="hide-button" onclick="undisplay(this.parentElement)">&times;</span>
8 ${message}
9 </div>`;
10   const message_area = document.getElementById('message-area');
11   message_area.insertAdjacentHTML('beforeend', message);
12 }
13
14 const events = new EventSource("events");
15
16 events.onerror = function(event) {
17   if (event.target.readyState === EventSource.CLOSED) {
18       add_message("danger", "Connection to server lost.");
19   }
20 };
21
22 events.addEventListener("move", event => {
23   const square = JSON.parse(event.data);
24
25   window.game.receiveMove(square);
26 });
27
28 function Square(props) {
29   return (
30     <button className="square" onClick={props.onClick}>
31       {props.value}
32     </button>
33   );
34 }
35
36 class Board extends React.Component {
37   renderSquare(i) {
38     return (
39       <Square
40         value={this.props.squares[i]}
41         onClick={() => this.props.onClick(i)}
42       />
43     );
44   }
45
46   render() {
47     return (
48       <div>
49         <div className="board-row">
50           {this.renderSquare(0)}
51           {this.renderSquare(1)}
52           {this.renderSquare(2)}
53         </div>
54         <div className="board-row">
55           {this.renderSquare(3)}
56           {this.renderSquare(4)}
57           {this.renderSquare(5)}
58         </div>
59         <div className="board-row">
60           {this.renderSquare(6)}
61           {this.renderSquare(7)}
62           {this.renderSquare(8)}
63         </div>
64       </div>
65     );
66   }
67 }
68
69 function fetch_post_json(api = '', data = {}) {
70   const response = fetch(api, {
71     method: 'POST',
72     headers: {
73       'Content-Type': 'application/json'
74     },
75     body: JSON.stringify(data)
76   });
77   return response;
78 }
79
80 class Game extends React.Component {
81   constructor(props) {
82     super(props);
83     this.state = {
84       history: [
85         {
86           squares: Array(9).fill(null)
87         }
88       ],
89       stepNumber: 0,
90       xIsNext: true
91     };
92   }
93
94   sendMove(i) {
95     return fetch_post_json("move", { square: i });
96   }
97
98   receiveMove(i) {
99     const history = this.state.history.slice(0, this.state.stepNumber + 1);
100     const current = history[history.length - 1];
101     const squares = current.squares.slice();
102     if (calculateWinner(squares) || squares[i]) {
103       return;
104     }
105     squares[i] = this.state.xIsNext ? "X" : "O";
106     this.setState({
107       history: history.concat([
108         {
109           squares: squares
110         }
111       ]),
112       stepNumber: history.length,
113       xIsNext: !this.state.xIsNext
114     });
115   }
116
117   async handleClick(i) {
118     const response = await this.sendMove(i);
119     if (response.status == 200) {
120       const legal = await response.json();
121       if (! legal)
122         add_message("danger", `Illegal move.`);
123     } else {
124       add_message("danger", `Error occurred sending move`);
125     }
126   }
127
128   jumpTo(step) {
129     this.setState({
130       stepNumber: step,
131       xIsNext: (step % 2) === 0
132     });
133   }
134
135   render() {
136     const history = this.state.history;
137     const current = history[this.state.stepNumber];
138     const winner = calculateWinner(current.squares);
139
140     let status;
141     if (winner) {
142       status = "Winner: " + winner;
143     } else {
144       status = "Next player: " + (this.state.xIsNext ? "X" : "O");
145     }
146
147     return (
148       <div className="game">
149         <div className="game-info">
150           <div>{status}</div>
151         </div>
152         <div className="game-board">
153           <Board
154             squares={current.squares}
155             onClick={i => this.handleClick(i)}
156           />
157         </div>
158       </div>
159     );
160   }
161 }
162
163 // ========================================
164
165 ReactDOM.render(<Game
166                   ref={(me) => window.game = me}
167                 />, document.getElementById("tictactoe"));
168
169 function calculateWinner(squares) {
170   const lines = [
171     [0, 1, 2],
172     [3, 4, 5],
173     [6, 7, 8],
174     [0, 3, 6],
175     [1, 4, 7],
176     [2, 5, 8],
177     [0, 4, 8],
178     [2, 4, 6]
179   ];
180   for (let i = 0; i < lines.length; i++) {
181     const [a, b, c] = lines[i];
182     if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
183       return squares[a];
184     }
185   }
186   return null;
187 }