diff --git a/README.md b/README.md index f264a54..7fae1cd 100644 --- a/README.md +++ b/README.md @@ -50,3 +50,12 @@ Step 7: Making tests extensible, we now don't rely on AiEngine to suggest move, Step 8: Using Prototype Design Pattern: It is used when we wanted to deep or shallow clone the object. It is beneficial when new Object creation is expensive Step 9: We used Lambda functions to adhere to the DRY principle. + +Step 10: Making Code Extensible Using the Open-Closed Principle + +Key Points: + +1. Our RuleEngine evaluates multiple scenarios to determine the game state. Each scenario is represented as a Rule. +2. Initially, adding a new rule required modifying the getState function, making the approach inflexible and violating the Open-Closed Principle. +3. To enhance extensibility, we introduced a Rule class and a set of rules that can be dynamically added to the RuleEngine via its constructor. +4. This design allows new rules to be incorporated without modifying existing logic, making the system more scalable and maintainable. \ No newline at end of file diff --git a/api/RuleEngine.java b/api/RuleEngine.java index 3f171a7..2f2113f 100644 --- a/api/RuleEngine.java +++ b/api/RuleEngine.java @@ -4,49 +4,59 @@ import game.Board; import game.GameState; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.function.BiFunction; import java.util.function.Function; public class RuleEngine { - public GameState getState(Board board){ - if(board instanceof TicTacToeBoard board1) { - - BiFunction getRow = board1::getSymbol; - BiFunction getCol = (i,j)->board1.getSymbol(j,i); - Function getDiagonal = (i)->board1.getSymbol(i,i); - Function getRevDiagonal = (i)->board1.getSymbol(i,3-i-1); + Map>> ruleMap = new HashMap<>(); - GameState rowWin = outerTraversal(getRow); - if(rowWin.isOver()) return rowWin; + public RuleEngine(){ + String name = TicTacToeBoard.class.getName(); + ruleMap.put(name,new ArrayList<>()); + // Rules + ruleMap.get(name).add(new Rule<> ((board)->outerTraversal(board::getSymbol))); + ruleMap.get(name).add(new Rule<> ((board)->outerTraversal((i,j)-> board.getSymbol(j,i)))); + ruleMap.get(name).add(new Rule<> ((board)->traverse((i)-> board.getSymbol(i,i)))); + ruleMap.get(name).add(new Rule<> ((board)->traverse((i)-> board.getSymbol(i,2-i)))); + ruleMap.get(name).add(new Rule<> (this::countMoves)); - GameState colWin = outerTraversal(getCol); - if(colWin.isOver()) return colWin; - - GameState diagonalWin = traverse(getDiagonal); - if(diagonalWin.isOver()) return diagonalWin; - - GameState reverseDiagonalWin = traverse(getRevDiagonal); - if(reverseDiagonalWin.isOver()) return reverseDiagonalWin; + } - int count=0; - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - if (board1.getSymbol(i,j)!=null){ - count ++; - } + public GameState getState(Board board){ + if(board instanceof TicTacToeBoard board1) { + List>rules = ruleMap.get(TicTacToeBoard.class.getName()); + for(Rule rule:rules){ + GameState gameState = rule.condition.apply(board1); + if(gameState.isOver()){ + return gameState; } } - if(count == 9){ - return new GameState(true,"-"); - } else{ - return new GameState(false,"-"); - } - + return new GameState(false,"-"); } else { return new GameState(true, "-"); } } + public GameState countMoves (TicTacToeBoard board1){ + int count=0; + GameState gameState = new GameState(false, "-"); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (board1.getSymbol(i,j)!=null){ + count ++; + } + } + } + if(count == 9){ + gameState = new GameState(true,"-"); + } + return gameState; + } + public GameState outerTraversal(BiFunction next){ GameState result = new GameState(false, "-"); for (int i = 0; i < 3; i++) { @@ -76,3 +86,11 @@ public GameState traverse(Function next){ return result; } } + + +class Rule { + Function condition; + Rule(Function condition){ + this.condition = condition; + } +}