5
\$\begingroup\$

I started self teaching myself Java a few months ago. I recently came to the point where I felt it was time to blackout myself from any help or guiding material other than Oracle's docs, so I made a 2 player chess game. It is very basic. I didn't include a visual indicator for crowning, only a text notification. I would just like some feedback on areas that need improvement, bad habits, suggestions, the like. This is going to be a bit of a doozy to post as it spans several class files.

There are 2 packages, main and main.checkers. I will abbreviate each block class file with its path above.

main.Main

package main;

import main.checkers.Entry;

public class Main {

    public static void main(String[] args) {
        Entry.main(null);
    }
}

main.checkers.Checker

package main.checkers;

import java.lang.Error;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.input.MouseEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.beans.binding.When;
import javafx.beans.binding.DoubleBinding;


@SuppressWarnings("restriction")
//make checker a child of checkerBoard so that it cannot exist independently
public class Checker extends Circle {


    private boolean king = false;
    public final String team;
    //for binding the checkers to the grid
    private DoubleBinding radiusBinding;

    Checker(CheckerBoard board, String tmp) {
        //decide if checker is red or black
        if(tmp.equals("red")) {
            team = tmp;
            setFill(Color.RED);
        }
        else if(tmp.equals("black")) {
            team = tmp;
            setFill(Color.BLACK);
        }
        else throw new Error("team must be red or black");
        //center piece
        board.setHalignment(this, HPos.CENTER);
        board.setValignment(this, VPos.CENTER);
        radiusBinding = (DoubleBinding)new When(board.cellWidthProperty().lessThan(board.cellHeightProperty()))
            .then(board.cellWidthProperty().multiply(.5*.8))
            .otherwise(board.cellHeightProperty().multiply(.5*.8)
        );
        //bind the radius of the checker to .8 * width of it's container
        radiusProperty().bind(radiusBinding);

        //add local event handlers to highlight and unhighlight the checker when dragged
        addEventHandler(MouseEvent.DRAG_DETECTED, new EventHandler<MouseEvent>() {
            public void handle(MouseEvent e) {
                toFront();
                setStroke(Color.YELLOW);
                startFullDrag();
                transferEvent(e);
            }
        });
        addEventHandler(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() {
            public void handle(MouseEvent e) {
                setStroke(null);
                transferEvent(e);
            }
        });
        //register static eventHandler to forward control of the action
        addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
            public void handle(MouseEvent e) {
                transferEvent(e);
            }
        });
        //add change listener to team 
    }

    //convienence method to transfer event to checkerboard
    private <T extends MouseEvent> void transferEvent(T e) {
        //cheating to get dynamic reference within a static object
        CheckerBoard dynamicBoard = (CheckerBoard)((Checker)e.getSource()).getParent();
        dynamicBoard.transferControl(e);
    }

    //getters and setters for king state
    public boolean getKing() {
        return king;
    }
    public void setKing(boolean state) {
        king = state;
    }
}

main.checkers.CheckerBoard

package main.checkers;

import javafx.scene.input.MouseEvent;
import javafx.beans.value.ChangeListener;
import javafx.beans.property.StringProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.shape.Rectangle;
import javafx.scene.paint.Color;


@SuppressWarnings("restriction")
public class CheckerBoard extends TrueGrid {

    //the controller object for this checkerboard
    private Controller maestro;
    //Keep track of the turn and alert controller when it changes
    private StringProperty turn;

    //factory method to create checkerboard object
    public static CheckerBoard createCheckerBoard() {
        CheckerBoard ret = new CheckerBoard();
        ret.setTiles();
        ret.setBoard();
        ret.setController(new Controller(ret));
        ret.getController().setValidSelection();
        return ret;
    }
    //create an 8x8 Grid 
    //wrap in a factory method
    private CheckerBoard() {
        super(8, 8);
        //set the property, add change listener
        turn = new SimpleStringProperty("red");
        turn.addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> property, String oldVal, String newVal) {

            }
        });
    }
    //set the board 
    private void setBoard() {
        String team = "red";
        //Only initiailze the first and last three rows with checkers
        for(int j=0; j<8; j++){
            if(j > 2 && j <5) {
                team = "black";
                continue;
            }
            for(int i=0; i<8; i+=2) {
                if(j%2 == 0) add(new Checker(this, team), i+1, j);
                else add(new Checker(this, team), i, j);
            }
        }
    }
    //set the tiles
    private void setTiles() {
        for(int j=0; j<8; j++) {
            for(int i=0; i<8; i++) {
                Rectangle tmp = new Rectangle();
                tmp.widthProperty().bind(cellWidthProperty());
                tmp.heightProperty().bind(cellHeightProperty());
                if(j%2 == 0) {
                    if(i%2 == 0) {
                        tmp.setFill(Color.MOCCASIN);
                        add(tmp, i, j);
                    }
                    else {
                        tmp.setFill(Color.MAROON);
                        add(tmp, i, j);
                    }
                }
                else {
                    if(i%2 == 0) {
                        tmp.setFill(Color.MAROON);
                        add(tmp, i, j);
                    }
                    else {
                        tmp.setFill(Color.MOCCASIN);
                        add(tmp, i, j);
                    }
                }
            }
        }
    }
    //transfers control of the event to the controller for this game of checkers
    public <T extends MouseEvent> void transferControl(T e) {
        maestro.recieveControl(e);      
    }
    //getters and setters for turn property
    public StringProperty getTurnProperty() {
        return turn;
    }
    public String getTurn() {
        return turn.getValue();
    }
    public Controller getController() {
        return maestro;
    }
    public void setController(Controller tmp) {
        maestro = tmp;
    }
}

main.checkers.Controller

package main.checkers;

import java.util.List;
import java.util.ArrayList;
import javafx.scene.input.MouseEvent;
import java.util.function.Predicate;
import javafx.scene.Node;
import javafx.scene.shape.Rectangle;
import javafx.scene.paint.Color;


@SuppressWarnings("restriction")
public class Controller {

    //the board which this controller is bound to
    private final CheckerBoard board;
    //a list of all valid moves that can be made
    private List<PointCB> absoluteValid = new ArrayList<PointCB>();
    //is the currently selected checker a valid piece to be moved?
    private boolean validSelection;
    //a global refernce to the last checker marked to be a valid movement as well it's previous coordinates
    private Checker cleared;
    private int prevI, prevJ;
    private List<PointCB> relativeValid;

    Controller(CheckerBoard tmp) {
        board = tmp;
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public void setValidSelection() {
        //clear old list
        absoluteValid.clear();
        //determine which pieces to scan
        List<Checker> team;
        Predicate<Node> filterCondition = (x) -> {
            if(x.getClass() == Checker.class) {
                return ((Checker)x).team.equals(board.getTurn());
            }
            else return false;
        };
        team = (List)(board.getChildren().filtered(filterCondition));
        //scan third row viability of each piece
        for(Checker checker : team) {
            absoluteValid.addAll(scanR2(checker));
        }
        //if no jumps are avaible then the list of valid movements it the list of single jump spaces availbe
        if(absoluteValid.isEmpty()) {
            for(Checker checker : team) {
                absoluteValid.addAll(scanR1(checker));
            }
        }
    }

    //scans ring 1 of adjacent grid cells for valid movements
    public List<PointCB>  scanR1(Checker checker) {
        //get coordinates of checker in grid
        int i = board.getColumnIndex(checker);
        int j = board.getRowIndex(checker);
        List<PointCB> ret = new ArrayList<PointCB>();
        //scanner loop ... y axis 
        for(int dY=-1; dY<2; dY+=2) {
            //if checker is not a checker.getKing() only scan subjectively forward
            if(!checker.getKing()) {
                if(checker.team.equals("red") && dY==-1) continue;
                else if(checker.team.equals("black") && dY==1) continue;
            }
            if(j+dY<0) continue;
            else if(j+dY>7) break;
            //x-axis loop
            for(int dX=-1; dX<2; dX+=2) {
                //add empty spaces to list
                if(board.getCell(Checker.class, i+dX, j+dY).isEmpty()) ret.add(new PointCB(checker, i+dX, j+dY));
            }
        }
        return ret;
    }
    //scans ring 3 of adjacent grid cells for valid movements
    public List<PointCB>  scanR2(Checker checker) {
        //get coordinates of checker in grid
        int i = board.getColumnIndex(checker).intValue();
        int j = board.getRowIndex(checker).intValue();
        List<PointCB> ret = new ArrayList<PointCB>();
        String searchFor = checker.team.equals("red") ? "black" : "red";
        //scanner loop ... y axis 
        for(int dY=-2; dY<3; dY+=4) {
            //if checker is not a checker.getKing() only scan subjectively forward
            if(!checker.getKing()) {
                if(checker.team.equals("red") && dY==-2) continue;
                else if(checker.team.equals("black") && dY==2) continue;
            }
            if(j+dY<0) continue;
            else if(j+dY>7) break;
            //x-axis loop
            for(int dX=-2; dX<3; dX+=4) {

                if(i+dX<0) continue;
                else if(i+dX>7) break;
                //add empty spaces to list
                if(!board.getCell(Checker.class, i+dX/2, j+dY/2).isEmpty()) {
                    if(board.getCell(Checker.class, i+dX/2, j+dY/2).get(0).team.equals(searchFor)) {
                        if(board.getCell(Checker.class, i+dX, j+dY).isEmpty()) ret.add(new PointCB(checker, i+dX, j+dY));
                    }
                }
            }
        }
        return ret;
    }

    public <T extends MouseEvent>void recieveControl(T e) {
        //Save event type as String for logical testing
        String eventType = e.getEventType().getName();
        //forward the event to the correct method for final processing
        switch(eventType) {

            case "DRAG_DETECTED" : controlDragDetected(e);
                break;
            case "MOUSE_DRAGGED" : controlMouseDragged(e);
                break;
            case "MOUSE_RELEASED" : controlMouseReleased(e);
                break;
        }
    }
    public List<PointCB> getRelativeValid(Checker checker) {
        //list for holding return value
        List<PointCB> ret = new ArrayList<PointCB>();
        //add all available jumps to list
        ret.addAll(scanR2(checker));
        //if list is empty add all available adjacent spaces
        if(ret.isEmpty()) ret.addAll(scanR1(checker));
        return ret;
    }
/********************************************************************************************
 * ******************************************************************************************
 *              DRAG CONTROLS
 * ******************************************************************************************
 * ******************************************************************************************/
    //PROBLEM 
    public void controlDragDetected(MouseEvent e) {
        //get relative valid movements for comparison
        Checker checker = (Checker)e.getSource();
        int i = board.getColumnIndex(checker);
        int j = board.getRowIndex(checker);
        relativeValid = getRelativeValid(checker);
        List<PointCB> tmp = new ArrayList<PointCB>();
        //compare relative vlaid movements to absolute get the true list of relatively valid movements
        for(PointCB local : relativeValid) {
            for(PointCB remote : absoluteValid) {
                if(remote.equals(local)) tmp.add(local);
            }
        }
        relativeValid = tmp;
        //if there are valid moves relative to the selected piece, enable it to move freely
        if(!relativeValid.isEmpty()) {
            for(PointCB p : relativeValid) {
                board.getCell(Rectangle.class, p).forEach((x) -> x.setFill(Color.CHARTREUSE));
            }
            checker.setManaged(false);
            //mark this as being the last cleared piece
            cleared = checker;
            prevI = i;
            prevJ = j;
        }
        else {
            board.getCell(Rectangle.class, i, j).forEach((x) -> x.setFill(Color.CRIMSON));
        }
    }
    //if the piece has been cleared as valid allow it to be moved
    public void controlMouseDragged(MouseEvent e) {
        Checker checker = (Checker)e.getSource();
        if(checker == cleared) {
            checker.setCenterX(e.getX());
            checker.setCenterY(e.getY());
        }
        else e.consume();
    }
    //when the mouse is released, place it according to wheather it was relased into a valid cell
    public void controlMouseReleased(MouseEvent e) {
        Checker checker = (Checker)e.getSource();
        if(checker == cleared) {
            boolean set = false;
            int i = (int)(e.getSceneX()/board.cellWidth());
            int j = (int)(e.getSceneY()/board.cellHeight());
            //check if the drop location is valid
            PointCB currentCoord = new PointCB(checker, i, j);
            for(PointCB p : relativeValid) {
                if(currentCoord.equals(p)) {
                    board.getChildren().remove(checker);
                    board.add(checker, i, j);
                    set = true;
                }
            }
            //reset the board to it's default coloring
            if(!set) {
                board.getChildren().remove(checker);
                board.add(checker, prevI, prevJ);
            }
            checker.setManaged(true);
            if(Math.abs(board.getColumnIndex(checker)-prevI)==2 && Math.abs(board.getRowIndex(checker)-prevJ)==2) {
                handleJump(checker);
            }
            else if( !(board.getColumnIndex(checker)==prevI || board.getRowIndex(checker)==prevJ)) changeTurn();
            checkCrown();
        }
        resetTiles();
    }
    /*******************************************************************************************
     *                END CONTROLS
     *******************************************************************************************/
    //reset the tiles to their default colors
    @SuppressWarnings({"rawtypes", "unchecked"})
    public void resetTiles() {
        List<Rectangle> tmp = (List)board.getChildren().filtered((x) ->{
            //only maroon pieces can be valid movements therefore any piece that is not moccasin should be maroon
            if(x.getClass() == Rectangle.class) {
                if(!((Rectangle)x).getFill().equals(Color.MOCCASIN)) return true;
                else return false;
            }
            else return false;
        });
        for(Rectangle r : tmp) r.setFill(Color.MAROON);
    }
    //if a jump is detected, determine if further jumps need be executed
    public void handleJump(Checker checker) {
        //deal with a jump event
        int dX = board.getColumnIndex(checker)-prevI;
        int dY = board.getRowIndex(checker)-prevJ;
        //remove the piece that was jumped over
        board.getChildren().removeAll(board.getCell(Checker.class, (prevI+dX/2), (prevJ+dY/2)));
        //clear previous valid selection
        //limit new valid selection to this piece and only jumps
        absoluteValid.clear();
        absoluteValid.addAll(scanR2(checker));
        if(absoluteValid.isEmpty()) changeTurn();
    }
    //change the turn, progress the game
    public void changeTurn() {
        String setTo = board.getTurn().equals("red") ? "black" : "red";
        board.getTurnProperty().setValue(setTo);
        setValidSelection();
    }
    //crown the piece if need be
    public void checkCrown() {
        int kingColumn = cleared.team.equals("red") ? 7 : 0;
        int j = board.getRowIndex(cleared);
        if(j == kingColumn) {
            cleared.setKing(true);
            System.out.println("PIECE KINGED SUCCESFULLY");
        }
    }
}

main.checkers.Entry

package main.checkers;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;


@SuppressWarnings("restriction")
public class Entry extends Application {

    //create window, add checkerboard, start game loop
    public void start(Stage stage) {
        CheckerBoard board = CheckerBoard.createCheckerBoard();
        Scene scene = new Scene(board, 500, 500);
        board.bindToParent();
        stage.setScene(scene);
        board.setGridLinesVisible(true);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

main.checkers.PointCB

package main.checkers;

import javafx.geometry.Point2D;

//A Point2D linked specific checker 
@SuppressWarnings("restriction")
public class PointCB extends Point2D {
    public final Checker checker;
    PointCB(Checker tmp, int x, int y) {
        super(x, y);
        checker = tmp;
    }
    public boolean equals(Object o) {
        if(o.getClass() == PointCB.class) {


            return super.equals(o) && ((PointCB)(o)).checker == checker;
        }
        else return false;
    }
}

main.checkers.TrueGrid

package main.checkers;

import javafx.scene.layout.GridPane;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.ColumnConstraints;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.DoubleProperty;
import javafx.scene.Node;
import java.util.List;
import java.util.ArrayList;
import javafx.geometry.Point2D;


@SuppressWarnings("restriction")
public class TrueGrid extends GridPane{

    //properties
    private DoubleProperty cellWidth = new SimpleDoubleProperty();
    private DoubleProperty cellHeight = new SimpleDoubleProperty();
    public final int rows;
    public final int columns;

    //constructor
    public TrueGrid(int i, int j) {
        columns = i;
        rows = j;
        initializeColumns(i);
        initializeRows(j);
    }
    //fill the grid with i columns
    private void initializeColumns(int i) {
        for(int c=0; c<i; c++){
            getColumnConstraints().add(new ColumnConstraints());
            getColumnConstraints().get(c).setPercentWidth(100.0/i);
        }
    }
    //fill the grid with j rows
    private void initializeRows(int j) {
        for(int c=0; c<j; c++){
            getRowConstraints().add(new RowConstraints());
            getRowConstraints().get(c).setPercentHeight(100.0/j);
        }
    }
    //bind cell width and height properties to their value within the scene
    public void bindToParent() throws NullPointerException {
        if(getScene() == null) throw new NullPointerException("scene value null! does this TrueGrid belong to a scene?");
        cellWidth.bind(getScene().widthProperty().divide(columns));
        cellHeight.bind(getScene().heightProperty().divide(rows));
    }
    //getter methods
    public DoubleProperty cellWidthProperty() {
        return cellWidth;
    }
    public double cellWidth() {
        return cellWidth.getValue();
    }
    public DoubleProperty cellHeightProperty() {
        return cellHeight;
    }
    public double cellHeight() {
        return cellHeight.getValue();
    }
    //get the child contained at (i,j) coordinate of this grid
    //if T is null no children will be filtered
    public <T extends Node> List<T> getCell(Class<T> conversion, int iIndex, int jIndex) {
        List<T> children = new ArrayList<T>();
        for(Node node : getChildren()) {
            if(conversion.isInstance(node)) {   
                if(getColumnIndex(node) == iIndex) {
                    if(getRowIndex(node) == jIndex) {
                        children.add(conversion.cast(node)); 
                    }
                }
            }
        }
        return children;
    }
    //a wrapper class to allow passing Point2D instead of int coordinates to getCell
    public <T extends Node> List<T> getCell(Class<T> conversion, Point2D p) {
        return getCell(conversion, (int)p.getX(), (int)p.getY());
    }
    //remove all children of a given node type at the coordinates
    public <T extends Node> void removeCell(Class<T> clazz, int i, int j) {
        getChildren().removeAll(getCell(clazz, i, j));
    }

}
\$\endgroup\$
2
\$\begingroup\$

I have no review related to the language, but your comments (in the code) seem superfluous, to me. As I say when I review VBA

Comments - "code tell you how, comments tell you why". The code should speak for itself, if it needs a comment, it might need to be made more clear. If not, the comment should describe why you're doing something rather than how you're doing it (or what it is). Here are a few reasons to avoid comments all together.

e.g.

//properties
    private DoubleProperty cellWidth = new SimpleDoubleProperty();
    private DoubleProperty cellHeight = new SimpleDoubleProperty();
    public final int rows;
    public final int columns;

They are called properties in the code. I feel cluttered just looking at it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.