I think it is worth another answer to my own question to show my implementation of a 'ruleEngine' as @mjgpy3 has suggested.
I really liked the idea of being able to pipe all the rules in correct order, however it bothered me that I would have to set up a call to invalidOn
after each "rule".
Here is what I did:
function ValidityCheck(){
this.result = true;
this.reason = '';
this.set = function(status,reason){
this.result = status;
this.reason = reason || '';
}
}
var ruleEngine = (function ruleEngine(){
var game;
function cardsToDraw(card){
var result = new ValidityCheck();
if(game.cardsToDraw() > 0 && card.value !== '7'){
result.set(false, 'cards to draw');
}
return result;
}
function cardMatch(card){
var result = new ValidityCheck();
var requirements = game.getRequirements();
if (card.color !== requirements.color && card.value !== requirements.value && card.value !== 'J') {
result.set(false, 'card mismatch')
}
return result;
}
var rules = {
'cards to draw': cardsToDraw,
'card match': cardMatch
};
function check(rule,move){
if(!game) throw new Error('No game was set');
game.validityCheck = rules[rule](move.card);
return this;
}
function setGame(gameData){
game = gameData;
}
return{
check: check,
setGame: setGame
}
})();
module.exports = ruleEngine;
This allows me to do this:
ruleEngine.setGame(game);
function checkCard(move){
ruleEngine
.check('cards to draw',move)
.check('card match',move);
if(game.validityCheck.result){
// make move
}
else{
// return reason
}
}
Which feels pretty nice, compared to my first solution. Still plenty of room to improve, though.
I wonder if it would be better to go the other way round and set up a rule engine for a game. Then I could implement a setRule
method and would get a strongly encapsulated game engine that can be passed to other games as well.
Also, currently a failed check will set up a property on the game itself. Is this a good idea?