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));
 }
}
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Feb 20, 2018 at 4:16
\$\endgroup\$

1 Answer 1

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.

answered Feb 21, 2018 at 1:04
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.