Build the Multi-player Game Player UI

Players need to be able to submit decisions for their assigned role and see their world’s results. They also need to be notified if a value they submitted was not valid.

First, create three actions in js/actions/Actions.js. One for submitting decisions to the submit_decision topic defined by div-model game/ The others control setting/clearing any error status message it returns.

import {createAction} from 'redux-actions';

import AutobahnReact from 'simpl-react/lib/autobahn';

// submit player decision then calculate result if both dividend and divisor have been submitted
export const submitDecision =
    createAction('SUBMIT_DECISION', (period, operand, ...args) =>`model:model.period.${}.submit_decision`, [operand])
// actions for setting / clearing a status message
export const setStatus = createAction('SET_STATUS');
export const clearStatus = createAction('CLEAR_STATUS');

NOTE: The submit_decision action calls this topic because the div-model game/ submit_decision endpoint registers as an RPC on the topic. Because submit_decision validates the operand, using an RPC allows us to check the returned status.

Create js/reducers/StatusReducer.js to handle the setStatus and clearStatus actions:

import {createReducer} from 'redux-create-reducer';
import recycleState from 'redux-recycle';

import {recyleStateAction} from 'simpl-react/lib/actions/state';

import {
} from '../actions/Actions';

const initial = {message: ''};

const status = recycleState(createReducer(initial, {
  [setStatus](state, action) {
    const message = action.payload;
    console.log("setting status.message: ", message);
    return Object.assign({}, state, {
      message: message
  [clearStatus](state) {
    console.log("clearing status.message");
    return Object.assign({}, state, {
      message: ''
}), `${recyleStateAction}`);

export default status;

Then add status to reducers/combined/appReducers.js:

import {simplReducers} from 'simpl-react/lib/reducers/combined';
import {reducer as form} from 'redux-form';

import status from '../StatusReducer';

const reducers = simplReducers({
  // Add your customer reducers here, if any.

export default reducers;

Create a presentational component js/components/DecisionForm.js for entering player decisions and displaying an error message:

import React from 'react';
import PropTypes from 'prop-types';

import {Button} from 'react-bootstrap';

import {Field, reduxForm} from 'redux-form';

class DecisionForm extends React.Component {

  constructor(props) {
    this.submitForm = this.submitForm.bind(this);

  submitForm(values) {

  render() {
    const {error, handleSubmit, submitting, invalid} = this.props;
    return (
      <div className="content-wrapper">
        <form id="testScenarioForm" onSubmit={handleSubmit(this.submitForm)}>

          <div className="form-group">
            <label>Enter a number:</label>

              bsClass="btn btn-mr btn-labeled btn-success"
              disabled={invalid || submitting}
            >Submit Decision</Button>

            {error && <div><p>{error}</p></div>}

      </div >

DecisionForm.propTypes = {
  runuser: PropTypes.object.isRequired,
  initialValues: PropTypes.object.isRequired,

  // redux-form props
  handleSubmit: PropTypes.func,
  invalid: PropTypes.bool,
  submitting: PropTypes.bool,
  error: PropTypes.string,

  // dispatch actions
  submitDecision: PropTypes.func.isRequired

export default reduxForm({
  form: 'decisionForm'

Wrap it in a container component that checks the returned status js/containers/DecisionFormContainer.js:

import {connect} from 'react-redux';
import {withRouter} from 'react-router';

import DecisionForm from '../components/DecisionForm';

import {submitDecision, setStatus} from '../actions/Actions';

function mapStateToProps(state, ownProps) {
  const initialValues = {
    'operand': ownProps.operand
  return {
    runuser: state.simpl.current_runuser,

function mapDispatchToProps(dispatch, ownProps) {
  return {
    submitDecision(values) {
      // submit player's decision
      const operand = values.operand;
      dispatch(submitDecision(ownProps.period, operand))
        .then((result) => {
          const status = result.payload;
          if (status !== 'ok') {
            console.log("DecisionFormContainer.submitDecision failed due to: ", status);
          } else {
            console.log("DecisionFormContainer.submitDecision succeeded");

const DecisionFormContainer = connect(

export default withRouter(DecisionFormContainer);

In your js/modules/PlayerHome.js, replace the original contents with:

import React from 'react';
import PropTypes from 'prop-types';

import {connect} from 'react-redux';

import DecisionFormContainer from '../containers/DecisionFormContainer'

class PlayerHome extends React.Component {
  render() {
    const {message} = this.props;
    const quotient = (this.props.result) ? : '';
    const other_operand = (this.props.other_decision) ? : '';
    const operand = (this.props.decision) ? : 0;
    let play = '';
    if (this.props.canPlay) {
      play = (
          <p>You are in charge of submitting a valid {this.props.runuser.role_name}.</p>
          < DecisionFormContainer period={this.props.period} operand={operand}/>
    } else {
      play = (
          <span>World {this.props.runuser.role_name}: {operand}</span>

    return (
        <h1>Hello Player: {}</h1>
        <span>World Quotient: {quotient} </span><br/>
        <span>World {this.props.other_role_name}: {other_operand}</span><br/>
        <a href="/logout/" className="btn btn-success btn-lg">Logout</a>

PlayerHome.propTypes = {
  canPlay: PropTypes.bool.isRequired,
  runuser: PropTypes.object.isRequired,
  other_role_name: PropTypes.string.isRequired,
  period: PropTypes.object.isRequired,
  decision: PropTypes.object,
  other_decision: PropTypes.object,
  result: PropTypes.object,
  message: PropTypes.string,

PlayerHome.defaultProps = {
  message: '',

function mapStateToProps(state) {
  const runuser = state.simpl.current_runuser;

  const run =
    (r) => ===
  const currentPhase = state.simpl.phase.find(
    (p) => === run.phase
  const playPhase = state.simpl.phase.find(
    (p) => === 'Play'
  const canPlay = ===;
  // console.log("PlayerHome: currentPhase=", currentPhase, ",",, ", canPlay=", canPlay);

  const other_role = state.simpl.role.find((r) => !== runuser.role);
  const other_role_name =;

  const scenario = state.simpl.scenario.find(
    (s) => ===

  const period = state.simpl.period.find(
    (p) => === p.scenario

  const decision = state.simpl.decision.find(
    (d) => === d.period && d.role === runuser.role

  const other_decision = state.simpl.decision.find(
    (d) => === d.period && d.role !== runuser.role

  const result = state.simpl.result.find(
    (r) => === r.period

  return {
    message: state.status.message,

const module = connect(

export default module;

Now, when players log in, they see a form for entering decisions for their role and a logout link:

As a world’s players submit decisions, the redux state automatically updates with new periods, decisions and results:

Log in as [email protected] with password s2 and submit a zero decision. You should see a notification like this:

Congratulations! You are now ready to build the Multi-player Game Leader UI