1

I am trying to replicate a backyard game of catch using event listeners and emissions. Person A 'throws' and - nine times out of ten - person B 'catches' and then throws back. Neither person can catch their own throws. How can I go about doing this? Example/psuedo code below.

var events = require('events'),
    EventEmitter = require('events').EventEmitter;

var a = new EventEmitter(),
    b = new EventEmitter();

a.on('start', function() {
  this.emit('throw', b);
})

a.on('catch', function() {
  console.log('a caught b\'s throw');
  this.emit('throw', b);
});

b.on('catch', function() {
  console.log('b caught a\'s throw');
  this.emit('throw', a);
});

a.emit('start');

What if I wanted to extend the game to include a third person (in which case the target of the throw can only be one of the two possible recipients)

Emil
  • 249
  • 1
  • 2
  • 14
  • 2
    You might want to introduce a mediator, who gains control over the `throw`n objects and gives them to the recipient to `catch` them. – Bergi Jan 31 '15 at 22:03
  • possible duplicate of [Nodejs: How to handle event listening between objects?](http://stackoverflow.com/questions/14020697/nodejs-how-to-handle-event-listening-between-objects) – Aweary Jan 31 '15 at 22:22
  • throw is not event of thrower (it is action), it is event in world. – zb' Jan 31 '15 at 22:24

1 Answers1

0

I don't know exactly how your game works, so I have coded a game with a different game logic. But you can adjust it to your needs easily.

The game works like this:

Each player throws two dices in alternating manner. First player 1 throws the dice than player2 and so on. Each player can catch the dice of the other players randomly. The catched dice is summed to the score and the player with the highest score wins the game.

Now to the code. At first, I didn't see that you're doing a nodeJs app. But my front-end app should be runnable on node with some tweaks.

I have used SignalsJS for the eventing. You can find examples here.

The following quote is from here:

A Signal is similar to an Event Emitter/Dispatcher or a Pub/Sub system, the main difference is that each event type has its own controller and doesn't rely on strings to broadcast/subscribe to events

To think of signalJS as publisher and subscriber system is the best to understand what it is doing.

The subscriber/listener is listening for events from the publisher. Once the publisher dispatches something the callback of the subscriber(s) will be called.

And to manage the events you need some kind of mediator as proposed in the comments, so you can handle everything. In my demo the object DiceGame acts as a mediator because it holds the players and the thrown dices in arrays.

Please find the demo of the game below and here at jsFiddle.

(If there is something to improve, please let me know in the comments.)

  //store local reference for brevity
  var Signal = signals.Signal;

/*
  // simple signals demo code here as an example
  
  //custom object that dispatch signals
  var dice = {
      threw: new Signal(), //past tense is the recommended signal naming convention
      carched: new Signal()
  };


  function onThrew(param1, param2) {
      console.log(param1 + param2);
  }
  dice.threw.add(onThrew); //add listener
  dice.threw.dispatch('user1', ' - dice no. = 6'); //dispatch signal passing custom parameters
  dice.threw.remove(onThrew); //remove a single listener

*/

var DiceGame = function(settings) {
    this.settings = $.extend(DiceGame.defaultSettings, settings);
    var activePlayer = {};
    
    this.addPlayer = function() {
        var index = this.players.push({
            name: 'player' + (this.players.length + 1),
             //custom object that dispatch signals
            dice: {
              threw: new Signal(), //past tense is the recommended signal naming convention
              catched: new Signal()
            },
            score: 0,
            dicesThrown: 0
        });
        activePlayer = this.players[index-1];
        activePlayer.index = index-1;
        this.setActivePlayer(activePlayer);
        
        // add display listener
        activePlayer.dice.threw.add(this.onDiceThrew, this);
        activePlayer.dice.catched.add(this.onDiceCatched, this);
        //console.log(this.players, index, this.$activePlayerInfo, activePlayer);
    };
    
    this.getActivePlayer = function() {
        return activePlayer;
    };
    
    this.setActivePlayer = function(player, index){
        if ( typeof index != 'undefined' ) { 
            console.log(index, this.players[index]);
            activePlayer = this.players[index];
        }
        else {
            activePlayer = player;
        }
        this.updatePlayerInfo(activePlayer.name);
        this.$activePlayerScore.html(activePlayer.score);
    };
    
    this.initGame = function() {
        this.$activePlayerInfo  = $(this.settings.elActivePlayer);
        this.$activePlayerScore = $(this.settings.elActivePlayerScore);
        this.$gameInfo          = $(this.settings.elGameInfo);
        this.$playField         = $(this.settings.elPlayField);
        
        // add click handlers (bind to DiceGame obj. with this)
        $('#newGame').click(this.reset.bind(this));
        $('#addPlayer').click(this.addPlayer.bind(this));
        $('#changePlayer').click(this.nextPlayer.bind(this));
        $('#throw').click(this.throwDice.bind(this));
        $('#catch').click(this.catchDice.bind(this));
        
        // add two players
        _.each(new Array(this.settings.defaultPlayerCount), function(){
            this.addPlayer();
        }, this);
        
        this.setActivePlayer(null, 0); // can change current player by index
    }
    
    this.initGame();    
};

DiceGame.defaultSettings = {
    elActivePlayer: '#activePlayer',
    elActivePlayerScore: '#activePlayerScore',
    elGameInfo: '#gameInfo',
    elPlayField: '#playField',
    defaultPlayerCount: 2,
    maxThrownCount: 2
};
DiceGame.prototype = {
    players: [],
    diceList: [],
    
    updatePlayerInfo: function(text) {
        this.$activePlayerInfo.html(text);
    },
    reset: function() {
        this.diceList = [];
        $.each(this.players, function(index, item) {
            console.log(item);
            item.score = 0;
            item.dicesThrown = 0;
        });
        this.setActivePlayer(null, 0); // can change current player by index
        this.refreshPlayField();
        //this.showGameInfo('');
        this.hideGameInfo();
    },
    nextPlayer: function() {
        var index = this.getActivePlayer().index;
        index++; 
        
        if (index >= this.players.length ) {
            //'roll over' required!
            index = 0;
        }
        //var playerCopy = this.players.slice(0);
        this.setActivePlayer(this.players[index]); // next player
    },
    onDiceThrew: function(diceNo) {
        console.log('threw dice', diceNo);
        var newDice = {player: this.getActivePlayer(),
                       diceValue: diceNo};
        
        if ( newDice.player.dicesThrown < this.settings.maxThrownCount ) {
            this.diceList.push(newDice);
            this.$playField.append($('<p/>').text(newDice.player.name + ' - threw dice: ' +newDice.diceValue));
            //console.log('threw', this.diceList);
            newDice.player.dicesThrown++;
        }
        else {
            //alert(newDice.player.dicesThrown+ ' dices thrown. None left.');
            //show message that all dices are thrown
            this.showGameInfo(newDice.player.dicesThrown+ ' dices thrown. None left.');
            return;
        }
        console.log(newDice);
        this.nextPlayer(); // change to next player
    },
    checkGameOver: function() {
        // all thrown and nothing to catch --> game is over
        var winner = _.max(this.players, function(player) {
            console.log(player);
            return player.score;
        });
        console.log("winner", winner, this.players);
        var otherPlayers = _.omit(this.players, function(value) {
            console.log('value', value, value===winner);
            return value === winner;});
        
        var scoresStr = '';
        _.each(otherPlayers, function(player) {
            scoresStr += player.name + ': ' + player.score + '<br/>'; 
        });
        
        // check if we have a draw.
        //this.players[0].score = 5; // needed for testing
        //this.players[1].score = 5;
        
        var draw = _.every(this.players, function(player) {
            return player.score === winner.score;
        });
        
        console.log(draw);
        
        if (draw) {
             this.showGameInfo('Game over!<br/>Draw with score ' + 
                               winner.score, true);
        }
        else
        {
            // call showGameInfo with true --> keeps message displayed
            this.showGameInfo('Game over!<br/>' + winner.name + 
                  ' wins the game with score ' + winner.score 
                  + '!<br/>Other scores:<br/>' + scoresStr, true);
        }
    },
    onDiceCatched: function() {
        // catch one dice of other player 
        
        var player = this.getActivePlayer();
        var diceList = this.diceList.slice(0); // copy dice list
        
        var allowedDice = _.filter(diceList, function(dice) {
             return dice.player.name !== player.name;
        });
        
        var catched = allowedDice[Math.floor(Math.random()*allowedDice.length)];
        // console.log('catched dice = ', catched);
        
        // add score to active player
        if ( catched ) {
            player.score += catched.diceValue;
            this.$activePlayerScore.html(player.score);
            
            // update play field
            var newDiceList = this.removeItem(diceList, catched);
            this.diceList = newDiceList.slice(0); // copy new list to the dice list
            this.refreshPlayField();
            
            var allDone = _.every(this.players.dicesThrown, function(element) {
                return element == this.settings.maxThrownCount;
            });
            
            if ( this.diceList.length == 0 && allDone ){
                this.checkGameOver();
            }
        }
        else {
            // nothing catched
            // check if game is over? if yes, who is the winner?
            if ( player.dicesThrown >= this.settings.maxThrownCount )             
            {
                this.checkGameOver();
                return;
            }
        }
        this.nextPlayer(); // change to next player
    },
    removeItem: function(array, id) {
        // remove dice from list
        return _.reject(array, function(item) {
            //console.log(item, id, item===id);
            return item === id; // or some complex logic
        });
    },
    refreshPlayField: function() {
        var $field = this.$playField, 
            $row = $('<p/>');
        
        $field.empty();
        $.each(this.diceList, function(index, item) {
            console.log(index, item);
            $row.text(item.player.name + ' - threw dice: ' +item.diceValue)
            $field.append($row.clone());
        });
    },
    showGameInfo: function(message, keep) {
        var $info = this.$gameInfo;
        $info.html(message);
        // show info with jQuery animation
        $info
            .stop()
            .animate({opacity:1}, 'fast');
        
        if ( !keep ) { // don't auto hidde --> required for game over
            $info
                .delay(2000) // display time of message
                .animate({opacity:0},'fast');
        }
    },
    hideGameInfo: function() {
        // required to hide gameover message
        this.$gameInfo.stop()
            .animate({opacity:0}, 'fast'); // also stop every animation if any is active
    },
    throwDice: function() {
        var player = this.getActivePlayer();
        player.dice.threw.dispatch(Math.floor(Math.random()*6+1));
    },
    catchDice: function() {
        console.log('catch dice method');
        var player = this.getActivePlayer();
        player.dice.catched.dispatch();
    }
}


$(function() {
    // start game after DOM ready
    var game = new DiceGame();
});
.playerInfo {
    position: absolute;
    top: 0px;
    right: 10px;
    border: 1px solid black;
    width: 200px;
}

#gameInfo {
    opacity: 0;
    margin: 0 auto;
    color: red;
    text-align: center;
    width: 300px;
    height: 100px;
    /* center div horizontally and vertically */
    position:absolute;
    left:50%;
    top:50%;
    margin:-50px 0 0 -150px;
    border: 0px solid black;
    background-color: #FAFFBF;
    
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore.js"></script>
<script src="https://cdn.rawgit.com/millermedeiros/js-signals/master/dist/signals.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="newGame">New Game</button>
<button id="addPlayer">New Player</button>
<!-- <button id="changePlayer">Change player</button> -->

<button id="throw">Throw dice</button>
<button id="catch">Catch dice</button>

<div class="playerInfo">
    Active player: <span id="activePlayer"></span><br/>
    Score: <span id="activePlayerScore"></span>
</div>
<div id="gameInfo"></div>
<div id="playField"></div>
AWolf
  • 8,770
  • 5
  • 33
  • 39
  • Wow. Thank you for the incredibly detailed response. It will take some time for me to wrap my head around the code you've supplied (_for an entire game!_) but I am accepting this answer anyway due to the pub/sub mediator explanation. – Emil Feb 02 '15 at 07:38