Skip to content

Commit

Permalink
Add role picking
Browse files Browse the repository at this point in the history
Move move logic into their own modules.
Add typed-scss-modules to build .d.ts files from sass files
  • Loading branch information
Wizarth committed Oct 4, 2020
1 parent fd8c41c commit ae1de49
Show file tree
Hide file tree
Showing 20 changed files with 1,170 additions and 158 deletions.
7 changes: 6 additions & 1 deletion components/BoardArea.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
interface BoardAreaStyles {
readonly innerBorder: string;
readonly outerBorder: string;
}

type ParamsT = {
children: JSX.Element[]|JSX.Element;
className: string;
styles: {[x: string]: string};
styles: BoardAreaStyles;
}

export default function BoardArea({
Expand Down
129 changes: 50 additions & 79 deletions games/trolley.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,66 @@ import { Game, Ctx } from 'boardgame.io';

import { State, Player } from './trolley/types';

import * as PickTeam from './trolley/Logic/Setup/pickTeam'
import * as PickRole from './trolley/Logic/Setup/pickRole';
import innocentCards from './trolley/Logic/Decks/innocent';
import guiltyCards from './trolley/Logic/Decks/guilty';
import modifierCards from './trolley/Logic/Decks/modifier';
import * as Main from './trolley/Logic/main';

export const TrolleyGame : Game = {
name: 'TrolleyGame',
setup: (ctx) : State => {
if(!ctx.random) {
throw new Error('ctx.random missing');
}
const numPlayers = ctx.numPlayers;
// Populate the players field based on the number of players
const players: Record<string, Player> = {};
const players: State["players"] = {};
for( let i = 0; i < numPlayers; ++i ) {
players[""+i] = {
name: null,
score: 0,
choseTeam: false,
teamsDone: false
team: null,
innocentHand: null,
guiltyHand: null,
modifierHand: null,
teamsDone: false,
rolesDone: false
};
}
return {
secret: {
decks: {
goodies: [],
baddies: [],
modifiers: []
innocent: ctx.random.Shuffle(innocentCards),
guilty: ctx.random.Shuffle(guiltyCards),
modifier: ctx.random.Shuffle(modifierCards)
}
},
players,
teams: {
top: {
north: {
players: [],
goodies: [],
baddies: [],
modifiers: []
roles: {
innocent: null,
guilty: null,
modifier: []
}
},
bottom: {
south: {
players: [],
goodies: [],
baddies: [],
modifiers: []
roles: {
innocent: null,
guilty: null,
modifier: []
}
},
trolley: {
conductor: {
player: null
}
}
},
northTrack: [],
southTrack: []
};
},
// TODO: minPlayers isn't doc'ed in Game, it's Lobby plugin specific?
Expand All @@ -51,80 +71,31 @@ export const TrolleyGame : Game = {
phases: {
setup: {
start: true,
onBegin: (G: State, ctx: Ctx) => {
ctx.events.setActivePlayers({all: 'pickTeam'});
},
turn: {
activePlayers: {
all: 'pickTeam'
},
stages: {
pickTeam: {
moves: {
chooseTeam: (G: State, ctx: Ctx, teamId: string) => {
// Remove the player from all teams
G.teams.top.players = G.teams.top.players.filter(
(player: string) => player !== ctx.playerID
);
G.teams.bottom.players = G.teams.bottom.players.filter(
(player: string) => player !== ctx.playerID
);
if( G.teams.trolley.player === ctx.playerID ) {
G.teams.trolley.player = null;
}
// Add the player to the specified team
if( teamId === 'top' || teamId === 'bottom') {
G.teams[teamId].players.push(ctx.playerID);
} else {
G.teams[teamId].player = ctx.playerID;
}
G.players[ctx.playerID].choseTeam = true;
G.players[ctx.playerID].teamsDone = false;
},
toggleDone: (G: State, ctx: Ctx) => {
if(!G.players[ctx.playerID].choseTeam) {
return INVALID_MOVE;
}
G.players[ctx.playerID].teamsDone = !G.players[ctx.playerID].teamsDone;

for(let i = 0; i < ctx.numPlayers; ++i) {
if(!G.players[""+i].teamsDone) {
return;
}
}
if(G.teams.top.players.length === 0 ) {
return;
}
if(G.teams.bottom.players.length === 0 ) {
return;
}
if(G.teams.trolley.player === null ) {
return;
}
// Only players on track teams go to pick role.
// Trolley player is all set
const value = {};
for(let i = 0 ; i < G.teams.top.players.length; ++i ) {
value[G.teams.top.players[i]] = 'pickRole';
}
for(let i = 0 ; i < G.teams.bottom.players.length; ++i ) {
value[G.teams.bottom.players[i]] = 'pickRole';
}
ctx.events.setActivePlayers({value});
}
chooseTeam: PickTeam.chooseTeam,
toggleDone: PickTeam.toggleDone
}
},
pickRole: {
moves: {
joinRole: (G, ctx, roleId) => {
},
leaveRole: (G, ctx, roleId) => {
},
done: (G, ctx) => {
// TODO: Don't allow to end the stage until all roles are filled
ctx.events.endStage();
}
joinRole: PickRole.joinRole,
leaveRole: PickRole.leaveRole,
toggleDone: PickRole.toggleDone
}
}
}
}
},
endIf: PickRole.endIf,
next: 'main'
},
main: {
onBegin: Main.onBegin
}
}
// Disable for development
Expand Down
62 changes: 48 additions & 14 deletions games/trolley/Board.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import styles from './style.module.sass'
import styles from './Components/style.module.sass'

import React from 'react';
import TeamPicker from './TeamPicker';
import TeamPicker from './Components/TeamPicker';
import RolePicker from './Components/RolePicker';
import { BoardProps } from 'boardgame.io/react';
import {State} from './types';

Expand All @@ -10,20 +11,53 @@ export class TrolleyGameBoard extends React.Component<BoardProps<State>> {
onJoinTeam(teamId: string) {
this.props.moves.chooseTeam(teamId);
}
onJoinTeamDone() {
this.props.moves.toggleDone();
}
render() {
if(this.props.playerID === null) {
throw new Error("playerID null");
}
if(this.props.ctx.activePlayers === null ) {
// This happens when leaving a phase?
// throw new Error("activePlayers null")
return null;
}
const interactingPlayer = this.props.G.players[this.props.playerID];
if(!interactingPlayer) {
throw new Error("interacting player null");
}

let elementList: JSX.Element[] = [];
if( this.props.ctx.phase === 'setup' && this.props.ctx.activePlayers[this.props.ctx.currentPlayer] === 'pickTeam') {
elementList.push(
<TeamPicker
teams={this.props.G.teams}
playerId={this.props.playerID}
onJoinTeam={(teamId)=>this.onJoinTeam(teamId)}
onDone={()=>this.onJoinTeamDone()}
/>
)
if( this.props.ctx.phase === 'setup' ){
switch( this.props.ctx.activePlayers[this.props.playerID] ) {
case 'pickTeam':
elementList.push(
<TeamPicker
teams={this.props.G.teams}
player={interactingPlayer}
playerId={this.props.playerID}
onJoinTeam={(teamId)=>this.onJoinTeam(teamId)}
onDone={()=>this.props.moves.toggleDone()}
/>
);
break;
case 'pickRole':
const curTeam = interactingPlayer.team;
if(curTeam === "north" || curTeam === "south") {
elementList.push(
<RolePicker
team={this.props.G.teams[curTeam]}
player={interactingPlayer}
onJoinRole={(role)=>this.props.moves.joinRole(role)}
onLeaveRole={(role)=>this.props.moves.leaveRole(role)}
onDone={()=>this.props.moves.toggleDone()}
/>
);
}
break;
default:
elementList.push(
<h1>Please wait...</h1>
);
}
}
elementList.push(
<div>Board goes here</div>
Expand Down
129 changes: 129 additions & 0 deletions games/trolley/Components/RolePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import BoardArea from '../../../components/BoardArea';
import styles from './style.module.sass';
import {Player, TrackTeam, TrackTeamRoles} from '../types';

type RoleEvent = (role: keyof TrackTeamRoles) => void
type ParamsT = {
team: TrackTeam;
player: Player;
onJoinRole: RoleEvent;
onLeaveRole: RoleEvent;
onDone: () => void;
}

/** Helper to convert onchange to join/leave */
function onChange(
event: React.ChangeEvent<HTMLInputElement>,
role: keyof TrackTeamRoles,
onJoin: RoleEvent,
onLeave: RoleEvent
) {
if(event.target.checked) {
onJoin(role);
} else {
onLeave(role)
}
}

function generateModifierList(team:TrackTeam, onJoinRole: RoleEvent, onLeaveRole: RoleEvent) {
return team.players.map(
(playerId) => <span>
<input type="checkbox"
name={playerId}
checked={team.roles.modifier.indexOf(playerId) !== -1}
onChange={(event) => onChange(event, 'modifier', onJoinRole, onLeaveRole)}
/>
<label>{playerId}</label>
</span>
)
}

function generateRadioList(team:TrackTeam, role: "innocent"|"guilty", onJoinRole: RoleEvent, onLeaveRole: RoleEvent) {
return team.players.map(
(playerId) => <span>
<input type="radio"
name={role}
checked={team.roles[role] === playerId}
onChange={(event) => onChange(event, role, onJoinRole, onLeaveRole)}
/>
<label>{playerId}</label>
</span>
)
}

export default function RolePicker({team, player, onJoinRole, onLeaveRole, onDone} : ParamsT) {
const innocentList = generateRadioList(
team,
"innocent",
onJoinRole,
onLeaveRole
);
const guiltyList = generateRadioList(
team,
"guilty",
onJoinRole,
onLeaveRole
);
const modifierList = generateModifierList(
team,
onJoinRole,
onLeaveRole
);
let buttonDisabled = false;

let innocentWarning: JSX.Element|void;
if(!team.roles.innocent ) {
innocentWarning = <span>Role requires a player</span>
buttonDisabled = true;
}
let guiltyWarning: JSX.Element|void;
if(!team.roles.guilty ) {
guiltyWarning = <span>Role requires a player</span>
buttonDisabled = true;
}
let modifierWarning: JSX.Element|void;
if(team.roles.modifier.length === 0 ) {
modifierWarning = <span>Role requires at least one player</span>
buttonDisabled = true;
}

let buttonLabel = "Ready";
if(player.rolesDone) {
buttonLabel += "!";
} else {
buttonLabel += "?";
}
<button disabled={buttonDisabled} onClick={onDone}>{buttonLabel}</button>

return (
<div>
<h1>Choose your roles</h1>
<BoardArea className={styles.roleListArea} styles={styles}>
<div className={styles.listElements}>
<BoardArea className={styles.role} styles={styles}>
<h2>Innocent</h2>
<form>
{innocentList}
</form>
{innocentWarning}
</BoardArea>
<BoardArea className={styles.role} styles={styles}>
<h2>Guilty</h2>
<form>
{guiltyList}
</form>
{guiltyWarning}
</BoardArea>
<BoardArea className={styles.role} styles={styles}>
<h2>Modifier</h2>
<form>
{modifierList}
</form>
{modifierWarning}
</BoardArea>
</div>
</BoardArea>
<button disabled={buttonDisabled} onClick={onDone}>{buttonLabel}</button>
</div>
)
}
Loading

0 comments on commit ae1de49

Please sign in to comment.