I'm trying to clone a Dots game with some little differences (power ups and objectives) as a school project. I have almost every objective done so now, I'm not trying to get my work done for free. I am however asking for code structure advice and to learn if you find any horrible mistake. I'm sorry that the code isn't commented, maybe a bad habit but I prefer to make the code as readable as I can without them (at least I hope it is readable).Also, pt.isel.a41530.lib.tile package is not my code and I'm not asking for a review of that code.GitHubMy main concerns are: Redundant code in the game logic, especially with the combo having objects with x,y when I have a bi-dimensional array.Too much work to add new Pieces:Creating a class for the object, creating a type (on the enum) but that static reference doesn't seem good to me, it still seems better than creating an object every time I want to check the object characteristics.The class for the GameLogic seems to have to much work: The game logic is divided in 2 classes, one handles all the base game mechanics and the other handles the score/objectives/playsleft/save/load. Should I split them a bit more?Visual characteristics of the pieces are on the model (that should only handle logic and be completely portable if I might want to make the game for desktop or something along those lines).Game Base Logic: pt/isel/a41530/linkeddots/model/LinkedDots.javapackage pt.isel.a41530.linkeddots.model;import java.io.BufferedWriter;import java.io.IOException;import java.util.LinkedList;import pt.isel.a41530.linkeddots.model.pieces.Piece;import pt.isel.a41530.linkeddots.model.pieces.PieceType;public class LinkedDots {    private static final boolean SIMPLE = false, CONNECTED = true, FINISH = true;    private final int COLS, LINES;    private PieceType[][] grid;    private LinkedList<Piece> connectedPieces = new LinkedList<>();    private LevelManager manager;    private OnActionListener listener;    public LinkedDots(){        this(new LevelManager());    }    public LinkedDots(LevelManager manager){        this.manager = manager;        COLS = manager.getCols();        LINES = manager.getLines();        grid = new PieceType[COLS][LINES];        manager.newLevel(this);    }    public boolean newCombo(int c, int l){        if(grid[c][l].sample().isBasic()){            addPieceToCombo(c, l);            return true;        }        return false;    }    public boolean addLink(int c, int l){        if(validMovement(c, l)){            if(checkUndo(c, l)){                listener.undo(connectedPieces.getLast().getX(), connectedPieces.getLast().getY());                connectedPieces.removeLast();            }            else if(checkComboCollision(c, l)){                if(specialCombo(c, l))                    finishCombo(CONNECTED);            }            else if (checkTypes(c, l)){                addPieceToCombo(c, l);                return true;            }        }        return false;    }    private boolean checkTypes(int c, int l){        return grid[c][l] == connectedPieces.getFirst().getType()                || grid[c][l].sample().alwaysConnects();    }    private boolean validMovement(int c, int l){        int x = connectedPieces.getLast().getX();        int y = connectedPieces.getLast().getY();        return (Math.abs(c - x) == 1 && l - y == 0) || (Math.abs(l - y) == 1 && c - x == 0);    }    private boolean checkUndo(int c, int l){        return connectedPieces.size() > 1 &&                c == connectedPieces.get(connectedPieces.size()-2).getX() &&                l == connectedPieces.get(connectedPieces.size()-2).getY();    }    private boolean checkComboCollision(int c, int l) {        for(Piece piece : connectedPieces){            if(piece.getX() == c && piece.getY() == l)                return true;        }        return false;    }    private boolean specialCombo(int c, int l){        return c == connectedPieces.getFirst().getX()                && l == connectedPieces.getFirst().getY();    }    public boolean finishCombo(){        if(connectedPieces.size()>1) {            finishCombo(SIMPLE);            return true;        }        listener.undo(connectedPieces.getFirst().getX(), connectedPieces.getFirst().getY());        resetCombo();        return false;    }    private void finishCombo(boolean type){        char pieceType = connectedPieces.getFirst().getType().getID();        int counter = clearCombo();        if(type)                                                    //type == CONNECTED            counter += clearAllPieces(connectedPieces.getFirst().getType());        newPieces();        resetCombo();        if(manager.play(counter, pieceType) != FINISH) {            listener.play();        }else            listener.onGameFinish();    }    private int clearCombo(){        for (Piece piece: connectedPieces){            clearPiece(piece.getX(), piece.getY());        }        return connectedPieces.size();    }    private int clearAllPieces(PieceType type){        int counter = 0;        for (int c = 0; c < COLS; c++)            for(int l = LINES - 1; l >= 0; l--)                if(grid[c][l] != null && grid[c][l] == type) {                    clearPiece(c, l);                    counter++;                }        return counter;    }    private void newPieces(){        boolean hasNext;        for (int c = 0; c < COLS; c++) {                    //for each column            hasNext = true;            for (int l = LINES-1; l >= 0; l--) {            //for each line                if(grid[c][l] == null && hasNext)           //check if is null(and still has next)                    hasNext = checkCol(c, l);               //check if truly has next and shift                if(!hasNext && grid[c][l] == null) {        //if there is no more pieces                    grid[c][l] = manager.newRandomPiece();  //bring a new random piece                    listener.newPiece(c, l);                //notify that there is a new piece                }            }        }    }    private boolean checkCol(int column, int lineOffset){        for(int line = lineOffset - 1; line >= 0; line--){  //for each line (starting in the offset)            if(grid[column][line] != null){                 //if the line != null hasNext                if(grid[column][line].sample().isMovable()) {                    /* Shift Piece in model and view */                    grid[column][lineOffset] = grid[column][line];                    listener.shiftPiece(column, line, lineOffset);                    grid[column][line] = null;                    return true;                }            }        }        return false;    }    private void addPieceToCombo(int c, int l){        connectedPieces.add(PieceType.newPiece(grid[c][l], c, l));    }    public void addPiece(char type, int c, int l){        grid[c][l] = PieceType.valueOf(type);    }    public void addPiece(PieceType type, int c, int l){        grid[c][l] = type;    }    private void resetCombo(){        connectedPieces = new LinkedList<>();    }    private void clearPiece(int c, int l) {        grid[c][l] = null;        listener.clearPiece(c, l);    }    public PieceType getPiece(int c, int l) {        return grid[c][l];    }    public int getCols(){        return COLS;    }    public int getLines(){        return LINES;    }    public int getScore(){        return manager.getScore();    }    public int getPlays() {        return manager.getPlaysRemaining()>0? manager.getPlaysRemaining() : 0;    }    public void saveLevel(BufferedWriter writer) throws IOException {        manager.saveLevel(writer, this);    }    public void setOnPlayListener(OnActionListener listener){        this.listener = listener;    }    @Override    public String toString(){        String model = ;        for (int l = 0; l < LINES; l++) {            for (int c = 0; c < COLS; c++) {                model += grid[c][l].getID() +  ;            }            model += \n;        }        return model;    }}Game Score Logic (plus Save/Load) pt/isel/a41530/linkeddots/model/LevelManager.javapackage pt.isel.a41530.linkeddots.model;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.IOException;import pt.isel.a41530.linkeddots.model.pieces.PieceType;public class LevelManager {    public static final int COLS = 6, LINES = 6, PLAYS_REMAINING = 15, SCORE = 0;    private final int cols, lines;    private int score;    private int playsRemaining;    private int totalPieceTypes;    private Objective[] objectives;    private String[] initialData;    public LevelManager(){        cols = COLS;        lines = LINES;        playsRemaining = PLAYS_REMAINING;        score = SCORE;        totalPieceTypes = 6;    }    public LevelManager(int cols, int lines, int playsRemaining, int score){        this.cols = cols;        this.lines = lines;        this.playsRemaining = playsRemaining;        this.score = score;        totalPieceTypes = 3;    }    public LevelManager(int cols, int lines, int playsRemaining, int score, int totalPieceTypes){        this(cols, lines, playsRemaining, score);        this.totalPieceTypes = totalPieceTypes;    }    public boolean play(int score, char type){        this.score += score;        for (Objective objective : objectives)            if(objective.getType() == type)                objective.increase(score);        return playsRemaining--<=0;    }    public static LevelManager loadLevel(BufferedReader reader) throws IOException{        String[] line = reader.readLine().split([ x]);        int cols = Integer.parseInt(line[0]);        int lines = Integer.parseInt(line[1]);        int playsRemaining = Integer.parseInt(line[2]);        int score = Integer.parseInt(line[3]);        LevelManager manager = new LevelManager(cols, lines, playsRemaining, score);        /* Seconds Line : Objectives */        String s = reader.readLine();        if(s.length()>1) {            line = s.split( );            Objective[] objectives = new Objective[line.length];            for (int i = 0; i < line.length; i++) {                char c = line[i].charAt(0);                String objective = ;                String progress = ;                for (int l = 1; l < line[i].length(); l++) {                    if (line[i].charAt(l) == '/') {                        for (l++; l < line[i].length(); l++) {                            progress += line[i].charAt(l);                        }                    } else objective += line[i].charAt(l);                }                objectives[i] = new Objective(c, Integer.valueOf(objective));                if (progress.length() > 0) {                    objectives[i].setProgress(Integer.valueOf(progress));                }            }            manager.setObjective(objectives);        }        /* New Pieces */        manager.initialData = new String[lines];        for(int l = 0; l < lines && (s = reader.readLine())!=null; l++) {            manager.initialData[l] = s.replace( , );        }        return manager;    }    public void saveLevel(BufferedWriter writer, LinkedDots model) throws IOException{        writer.write(cols + x + lines +   + playsRemaining +   + score);        writer.newLine();        if(objectives!=null) {            for (Objective obj : objectives) {                writer.write(obj.toString() +  );            }        }        writer.newLine();        writer.write(model.toString());    }    public void newLevel(LinkedDots model) {        if(initialData != null){            dataLevel(model);        }else{            randomLevel(model);        }    }    private void dataLevel(LinkedDots model) {        for(int l = 0; l < lines; l++)            for (int c = 0; c < cols; c++)                model.addPiece(initialData[l].charAt(c), c, l);    }    private void randomLevel(LinkedDots model){        for (int c = 0; c < cols; c++)            for(int l = 0; l < lines; l++)                model.addPiece(newRandomPiece(), c, l);    }    public PieceType newRandomPiece(){        return PieceType.createPiece((int)(Math.random() * totalPieceTypes));    }    private void setObjective(Objective[] obj){        this.objectives = new Objective[obj.length];        System.arraycopy(obj, 0, this.objectives, 0, obj.length);    }    public int getCols(){        return cols;    }    public int getLines(){        return lines;    }    public int getScore(){        return score;    }    public int getPlaysRemaining(){        return playsRemaining;    }    public int getPieceTypes() {        return totalPieceTypes;    }    public Objective[] getObjectives(){        return objectives;    }    public static class Objective{        private final char type;        private final int objective;        private int progress = 0;        private Objective(char type, int amount) {            this.type = type;            this.objective = amount;        }        public boolean increase(int progress){            return (this.progress += progress) >= objective;        }        public char getType() {            return type;        }        public int getObjective() {            return objective;        }        public int getProgress(){            return progress;        }        public void setProgress(int progress) {            this.progress = progress;        }        @Override        public String toString(){            String obj = String.valueOf((progress == 0)? objective : (objective + / + progress));            return type + obj;        }    }}PieceTypes Enum: pt/isel/a41530/linkeddots/model/pieces/PieceType.javapackage pt.isel.a41530.linkeddots.model.pieces;public enum PieceType {    RED('R'){        protected Piece newPiece(int c, int l){            return new BasicPiece(c, l, getID());        }        public Piece sample(){            return redPieceSample;        }    }, GREEN('G'){        protected Piece newPiece(int c, int l){            return new BasicPiece(c, l, getID());        }        public Piece sample(){            return greenPieceSample;        }    }, BLUE('B'){        protected Piece newPiece(int c, int l){            return new BasicPiece(c, l, getID());        }        public Piece sample(){            return bluePieceSample;        }    }, AllType('?'){        protected Piece newPiece(int c, int l) {            return new AllTypePiece(c, l);        }        public Piece sample() {            return allTypePieceSample;        }    }, BLOCK('#'){        protected Piece newPiece(int c, int l) {            return null;        }        public Piece sample() {            return blockPieceSample;        }    }, N_BLOCK('X'){        protected Piece newPiece(int c, int l) {            return null;        }        public Piece sample() {            return noGravityBlockSample;        }    };    private final char NAME_ID;    private PieceType(char nameID){        this.NAME_ID = nameID;    }    protected abstract Piece newPiece(int c, int l);    public abstract Piece sample();    public static PieceType valueOf(char c){        for(PieceType dot: values()){            if(dot.NAME_ID == c) {                return dot;            }        }        return null;    }    public char getID(){        return NAME_ID;    }    public static PieceType createPiece(int rng){        for(PieceType type: values()){            if(type.ordinal() == rng) {                return type;            }        }        return null;    }    public static Piece newPiece(PieceType type, int c, int l) {        return type.newPiece(c, l);    }    private static Piece redPieceSample = new BasicPiece('R');    private static Piece greenPieceSample = new BasicPiece('G');    private static Piece bluePieceSample = new BasicPiece('B');    private static Piece allTypePieceSample = new AllTypePiece();    private static Piece blockPieceSample = new BlockPiece();    private static Piece noGravityBlockSample = new NoGravityBlockPiece();}
Dots game in Android
java;game;android