Skip to content

Commit b28ee74

Browse files
committed
ability to change playerID from Debug UI
also simplify semantics by getting rid of special cases for undefined and null singleplayer mode (handled by reducer.js): - if playerID is not set, anyone can move - if playerID is set, check actionPlayers multiplayer mode (handled by src/server): - check actionPlayers always
1 parent 87c4f76 commit b28ee74

14 files changed

Lines changed: 211 additions & 54 deletions

File tree

examples/react/modules/tic-tac-toe/components/board.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class Board extends React.Component {
6767
}
6868

6969
let player = null;
70-
if (this.props.playerID !== null) {
70+
if (this.props.playerID) {
7171
player = <div id="player">Player: {this.props.playerID}</div>;
7272
}
7373

src/client/client.js

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,24 @@ class _ClientImpl {
156156
// isActive.
157157

158158
let isActive = true;
159-
if (this.multiplayer) {
160-
if (this.playerID == null) {
161-
isActive = false;
162-
}
163-
if (
164-
!this.game.flow.canPlayerMakeMove(state.G, state.ctx, this.playerID)
165-
) {
166-
isActive = false;
167-
}
159+
160+
const canPlayerMakeMove = this.game.flow.canPlayerMakeMove(
161+
state.G,
162+
state.ctx,
163+
this.playerID
164+
);
165+
166+
if (this.multiplayer && !canPlayerMakeMove) {
167+
isActive = false;
168+
}
169+
170+
if (
171+
!this.multiplayer &&
172+
this.playerID !== null &&
173+
this.playerID !== undefined &&
174+
!canPlayerMakeMove
175+
) {
176+
isActive = false;
168177
}
169178

170179
if (state.ctx.gameover !== undefined) {

src/client/debug/debug.css

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,18 @@
4949
background: #ddd;
5050
position: fixed;
5151
bottom: 0;
52-
width: 100%;
52+
box-sizing: border-box;
53+
width: 285px;
5354
margin-left: -20px;
5455
margin-bottom: 0;
5556
padding: 10px;
5657
}
5758

59+
.debug-ui .gameinfo-item div {
60+
float: right;
61+
text-align: right;
62+
}
63+
5864
.debug-ui .pane {
5965
float: left;
6066
padding: 20px;
@@ -163,11 +169,17 @@
163169
width: 30px;
164170
height: 30px;
165171
line-height: 30px;
166-
border: 3px solid #eee;
172+
background: #eee;
173+
border: 3px solid #fff;
167174
box-sizing: content-box;
168175
}
169176

177+
.debug-ui .player.current {
178+
background: #555;
179+
color: #eee;
180+
font-weight: bold;
181+
}
182+
170183
.debug-ui .player.active {
171-
background: #ccc;
172-
border: 3px solid #555;
184+
border: 3px solid #ff7f50;
173185
}

src/client/debug/debug.js

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Mousetrap from 'mousetrap';
1212
import { AssignShortcuts } from './assign-shortcuts';
1313
import { GameInfo } from './gameinfo';
1414
import { Controls } from './controls';
15+
import { PlayerInfo } from './playerinfo';
1516
import { DebugMove } from './debug-move';
1617
import { GameLog } from '../log/log';
1718
import { restore } from '../../core/action-creators';
@@ -31,11 +32,11 @@ export class Debug extends React.Component {
3132
ctx: PropTypes.any.isRequired,
3233
log: PropTypes.array.isRequired,
3334
isActive: PropTypes.bool,
35+
isConnected: PropTypes.bool,
3436
_initial: PropTypes.any.isRequired,
3537
}),
3638
gameID: PropTypes.string.isRequired,
3739
playerID: PropTypes.string,
38-
isConnected: PropTypes.bool,
3940
isMultiplayer: PropTypes.bool,
4041
moves: PropTypes.any,
4142
events: PropTypes.any,
@@ -47,6 +48,9 @@ export class Debug extends React.Component {
4748
reducer: PropTypes.func,
4849
overrideGameState: PropTypes.func,
4950
visualizeAI: PropTypes.func,
51+
updateGameID: PropTypes.func,
52+
updatePlayerID: PropTypes.func,
53+
updateCredentials: PropTypes.func,
5054
};
5155

5256
constructor(props) {
@@ -155,19 +159,6 @@ export class Debug extends React.Component {
155159
);
156160
}
157161

158-
let players = [];
159-
for (let i = 0; i < this.props.gamestate.ctx.numPlayers; i++) {
160-
let className = 'player active';
161-
if (i != this.props.gamestate.ctx.currentPlayer) {
162-
className = 'player';
163-
}
164-
players.push(
165-
<div className={className} key={i}>
166-
{i}
167-
</div>
168-
);
169-
}
170-
171162
let className = 'debug-ui';
172163
if (this.state.dockControls) {
173164
className += ' docktop';
@@ -204,7 +195,7 @@ export class Debug extends React.Component {
204195
gameID={this.props.gameID}
205196
playerID={this.props.playerID}
206197
isActive={this.props.gamestate.isActive}
207-
isConnected={this.props.isConnected}
198+
isConnected={this.props.gamestate.isConnected}
208199
isMultiplayer={this.props.isMultiplayer}
209200
/>
210201
)}
@@ -221,7 +212,11 @@ export class Debug extends React.Component {
221212
/>
222213

223214
<h3>Players</h3>
224-
<div className="player-box">{players}</div>
215+
<PlayerInfo
216+
ctx={this.props.gamestate.ctx}
217+
playerID={this.props.playerID}
218+
onClick={this.props.updatePlayerID}
219+
/>
225220

226221
<h3>Moves</h3>
227222

src/client/debug/gameinfo.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import React from 'react';
1010
import PropTypes from 'prop-types';
1111

1212
const Item = props => (
13-
<div>
14-
<strong>{props.name}</strong> {JSON.stringify(props.value)}
13+
<div className="gameinfo-item">
14+
<strong>{props.name} </strong>
15+
<div>{JSON.stringify(props.value)}</div>
1516
</div>
1617
);
1718

src/client/debug/playerinfo.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2018 The boardgame.io Authors
3+
*
4+
* Use of this source code is governed by a MIT-style
5+
* license that can be found in the LICENSE file or at
6+
* https://opensource.org/licenses/MIT.
7+
*/
8+
9+
import React from 'react';
10+
import PropTypes from 'prop-types';
11+
12+
/**
13+
* Component that renders information about the
14+
* players in the game (whose turn it is etc.).
15+
*/
16+
export class PlayerInfo extends React.Component {
17+
static propTypes = {
18+
ctx: PropTypes.any.isRequired,
19+
playerID: PropTypes.any,
20+
onClick: PropTypes.func,
21+
};
22+
23+
onClick = playerID => {
24+
const arg = playerID == this.props.playerID ? null : playerID;
25+
this.props.onClick(arg);
26+
};
27+
28+
render() {
29+
let players = [];
30+
for (let i = 0; i < this.props.ctx.numPlayers; i++) {
31+
const playerID = i + '';
32+
33+
let className = 'player';
34+
35+
if (playerID === this.props.ctx.currentPlayer) {
36+
className += ' current';
37+
}
38+
39+
if (playerID === this.props.playerID) {
40+
className += ' active';
41+
}
42+
43+
players.push(
44+
<div
45+
className={className}
46+
key={i}
47+
onClick={() => this.onClick(playerID)}
48+
>
49+
{playerID}
50+
</div>
51+
);
52+
}
53+
54+
return <div className="player-box">{players}</div>;
55+
}
56+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2017 The boardgame.io Authors
3+
*
4+
* Use of this source code is governed by a MIT-style
5+
* license that can be found in the LICENSE file or at
6+
* https://opensource.org/licenses/MIT.
7+
*/
8+
9+
import React from 'react';
10+
import { PlayerInfo } from './playerinfo';
11+
import Enzyme from 'enzyme';
12+
import Adapter from 'enzyme-adapter-react-16';
13+
14+
Enzyme.configure({ adapter: new Adapter() });
15+
16+
describe('PlayerInfo', () => {
17+
const ctx = {
18+
numPlayers: 2,
19+
currentPlayer: '0',
20+
};
21+
22+
test('click', () => {
23+
const fn = jest.fn();
24+
const root = Enzyme.mount(<PlayerInfo ctx={ctx} onClick={fn} />);
25+
expect(fn).not.toHaveBeenCalled();
26+
root
27+
.find('.player')
28+
.at(0)
29+
.simulate('click');
30+
expect(root.html()).not.toContain('active');
31+
expect(fn).toHaveBeenCalled();
32+
});
33+
34+
test('click while active', () => {
35+
const fn = jest.fn();
36+
const root = Enzyme.mount(
37+
<PlayerInfo ctx={ctx} onClick={fn} playerID="0" />
38+
);
39+
expect(fn).not.toHaveBeenCalled();
40+
root
41+
.find('.player')
42+
.at(0)
43+
.simulate('click');
44+
expect(root.html()).toContain('active');
45+
expect(fn).toHaveBeenCalled();
46+
});
47+
});

src/client/react-native.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ test('board props', () => {
6969
multiplayer: true,
7070
});
7171
board = Enzyme.mount(<Board />).find(TestBoard);
72-
expect(board.props().isActive).toBe(false);
72+
expect(board.props().isActive).toBe(true);
7373
board = Enzyme.mount(<Board playerID={'0'} />).find(TestBoard);
7474
expect(board.props().isActive).toBe(true);
7575
board = Enzyme.mount(<Board playerID={'1'} />).find(TestBoard);

src/client/react.js

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,21 +88,43 @@ export function Client({
8888
enhancer,
8989
});
9090

91+
this.gameID = props.gameID;
92+
this.playerID = props.playerID;
93+
this.credentials = props.credentials;
94+
9195
this.client.subscribe(() => this.forceUpdate());
9296
}
9397

9498
componentDidUpdate(prevProps) {
9599
if (this.props.gameID != prevProps.gameID) {
96-
this.client.updateGameID(this.props.gameID);
100+
this.updateGameID(this.props.gameID);
97101
}
98102
if (this.props.playerID != prevProps.playerID) {
99-
this.client.updatePlayerID(this.props.playerID);
103+
this.updatePlayerID(this.props.playerID);
100104
}
101105
if (this.props.credentials != prevProps.credentials) {
102-
this.client.updateCredentials(this.props.credentials);
106+
this.updateCredentials(this.props.credentials);
103107
}
104108
}
105109

110+
updateGameID = gameID => {
111+
this.client.updateGameID(gameID);
112+
this.gameID = gameID;
113+
this.forceUpdate();
114+
};
115+
116+
updatePlayerID = playerID => {
117+
this.client.updatePlayerID(playerID);
118+
this.playerID = playerID;
119+
this.forceUpdate();
120+
};
121+
122+
updateCredentials = credentials => {
123+
this.client.updateCredentials(credentials);
124+
this.credentials = credentials;
125+
this.forceUpdate();
126+
};
127+
106128
componentDidMount() {
107129
this.client.connect();
108130
}
@@ -116,7 +138,7 @@ export function Client({
116138
let _debug = null;
117139

118140
let state = this.client.getState();
119-
const { gameID, playerID, debug: debugProp, ...rest } = this.props;
141+
const { debug: debugProp, ...rest } = this.props;
120142

121143
if (this.state.gameStateOverride) {
122144
state = { ...state, ...this.state.gameStateOverride };
@@ -128,8 +150,8 @@ export function Client({
128150
isMultiplayer: multiplayer !== undefined,
129151
moves: this.client.moves,
130152
events: this.client.events,
131-
gameID: gameID,
132-
playerID: playerID,
153+
gameID: this.gameID,
154+
playerID: this.playerID,
133155
reset: this.client.reset,
134156
undo: this.client.undo,
135157
redo: this.client.redo,
@@ -145,14 +167,18 @@ export function Client({
145167
isMultiplayer: multiplayer !== undefined,
146168
moves: this.client.moves,
147169
events: this.client.events,
148-
gameID: gameID,
149-
playerID: playerID,
170+
gameID: this.gameID,
171+
playerID: this.playerID,
172+
credentials: this.credentials,
150173
step: this.client.step,
151174
reset: this.client.reset,
152175
undo: this.client.undo,
153176
redo: this.client.redo,
154177
visualizeAI: ai && ai.visualize,
155178
overrideGameState: this.overrideGameState,
179+
updateGameID: this.updateGameID,
180+
updatePlayerID: this.updatePlayerID,
181+
updateCredentials: this.updateCredentials,
156182
});
157183
}
158184

src/client/react.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ test('board props', () => {
7070
multiplayer: true,
7171
});
7272
board = Enzyme.mount(<Board />).find(TestBoard);
73-
expect(board.props().isActive).toBe(false);
73+
expect(board.props().isActive).toBe(true);
7474
board = Enzyme.mount(<Board playerID={'0'} />).find(TestBoard);
7575
expect(board.props().isActive).toBe(true);
7676
board = Enzyme.mount(<Board playerID={'1'} />).find(TestBoard);

0 commit comments

Comments
 (0)