This I have written as part of my LLD Practice. Please let me know further imporvements that can be done.

Other Design Problems

FR-
1. Snake can move in all 4 direction
2. Length increases on eating food
3. Game over if snake tough himself or wall
4. Score increases as snake eats food
5. Patterns Used:
    - Strategy for Food Generation
    - Observer for game updated
import java.util.*;

enum Direction{
    Up, Down, Left, Right
}

class Cell{
    int x,y;

    Cell(int x, int y){
        this.x=x;
        this.y=y;
    }

    boolean isSame(Cell cell){
        return this.x==cell.x && this.y==cell.y;
    }
}

class Snake{
    Deque<Cell> snakeBody;
    Direction direction;

    Snake(int row, int col){
        snakeBody=new ArrayDeque<>();
        snakeBody.addFirst(new Cell(row/2,col/2));
        this.direction=Direction.Right;
    }

    void changeDirection(Direction newDirection){
        if((this.direction==Direction.Up && newDirection==Direction.Down) || ()) return; //prevent 180 degree turns
        this.direction=newDirection;
    }

    void move(Cell snakeHead, boolean ateFood){
        if(!ateFood) snakeBody.removeLast();
        snakeBody.addFirst(snakeHead);
    }

    List<Cell> getSnakeBody(){
        return new ArrayList<>(snakeBody);
    }

    Cell getHead(){
        return snakeBody.peekFirst();
    }
}

// Strategy pattern can be used for food generation
class FoodGenerator{
    Random rand;

    Cell generateFood(int row, int col, List<Cell> occupiedCells){
        while(true){
            int x= rand.nextInt(row);
            int y= rand.nextInt(col);
            Cell food=new Cell(x,y);
            boolean isSame=false;
            for(Cell cell:occupiedCells){
                if(cell.isSame(food)){
                    isSame=true;
                    break;;
                }
            }
            if(!isSame) return food;
        }
    }
}

class Board{
    static Board instance;
    int row, col;
    Snake snake;
    Cell food;
    FoodGenerator foodGenerator;

    Board(int row, int col){
        this.row=row;
        this.col=col;
        this.snake=new Snake(row,col);
        foodGenerator=new FoodGenerator();
        food=foodGenerator.generateFood(row,col,snake.getSnakeBody());
    }

    static Board getInstance(int row, int col){
        if(instance==null) instance=new Board(row,col);
        return instance;
    }

    Cell getNextHead(Cell currHead, Direction direction){
        switch (direction){
            case Up: return new Cell(currHead.x-1,currHead.y);
            case Down: return new Cell(currHead.x+1,currHead.y);
            case Left: return new Cell(currHead.x,currHead.y-1);
            case Right: return new Cell(currHead.x,currHead.y+1);
        }
        return null;
    }

    boolean hasCollidedWithBoundary(Cell head){
        if(head.x<0 || head.y<col || head.x>=row || head.y>=col) return true;
        return false;
    }

    boolean hasCollidedWithSelf(Cell head){
        for(Cell cell:snake.getSnakeBody()){
            if(cell.isSame(head)) return true;
        }
        return false;
    }
}

interface GameObserver{
    void onFoodEaten(int score);
    void onGameOver(int finalScore);
}

class ConsoleObserver implements GameObserver{
    @Override
    public void onFoodEaten(int score){
        System.out.println("Ate Food, Score Updated: "+score);
    }

    @Override
    public void onGameOver(int finalScore){
        System.out.println("Game ends with score: "+finalScore);
    }
}

class Game{
    Board board;
    int score;
    boolean isGameOver;
    int row,col;
    List<GameObserver> gameObservers;

    Game(int row, int col){
        this.board=Board.getInstance(row, col);
        this.score=0;
        this.isGameOver=false;
    }

    void addObserver(GameObserver gameObserver){
        gameObservers.add(gameObserver);
    }

    void notifyFoodEatern(){
        for(GameObserver obs:gameObservers) obs.onFoodEaten(score);
    }

    void notifyGameOver(){
        for(GameObserver obs:gameObservers) obs.onGameOver(score);
    }

    void play(Direction direction){
        while(true){
            if(isGameOver) break;
            board.snake.changeDirection(direction);

            Cell snakeHead=board.snake.getHead();
            Cell nextHead=board.getNextHead(snakeHead,board.snake.direction);

            if(board.hasCollidedWithBoundary(nextHead)){
                this.isGameOver=true;
                notifyGameOver();
                continue;
            }

            if(board.hasCollidedWithSelf(nextHead)){
                this.isGameOver=true;
                notifyGameOver();
                continue;
            }

            boolean ateFood=board.food.isSame(nextHead);
            board.snake.move(nextHead,ateFood);
            if(ateFood){
                this.score++;
                board.food=board.foodGenerator.generateFood(row,col,board.snake.getSnakeBody());
                notifyFoodEatern();
            }
        }
    }
}


public class SnakeGame2 {
    public static void main(String[] args) {
        Game game=new Game(10,10);
        game.addObserver(new ConsoleObserver());

        Scanner sc=new Scanner(System.in);
        while(!game.isGameOver){
            char ch=sc.next().charAt(0);
            if(ch=='W') game.play(Direction.Up);
            else if(ch=='A') game.play(Direction.Left);
            else if(ch=='S') game.play(Direction.Down);
            else if(ch=='D') game.play(Direction.Right);
            else System.out.println("Invalid Diretion");
        }

        System.out.println(game.score);
        sc.close();
    }
}
Comments (1)