diff --git a/src/main/java/dotsandboxes/DotsAndBoxesUI.java b/src/main/java/dotsandboxes/DotsAndBoxesUI.java new file mode 100644 index 0000000000000000000000000000000000000000..e646cc4ee68f674e0c2a6610ea5fd174927eaf41 --- /dev/null +++ b/src/main/java/dotsandboxes/DotsAndBoxesUI.java @@ -0,0 +1,139 @@ +package dotsandboxes; + +import javafx.scene.control.Label; +import javafx.scene.layout.AnchorPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.shape.Rectangle; + +public class DotsAndBoxesUI { + + final int lineLength = 32; + final int margin = 10; + final int gap = 0; + final int dotDiameter = 6; + + final DotsAndBoxesGrid grid; + final AnchorPane anchorPane; + final Label label; + + /** Colours for the different players. Only goes up to 5. */ + final Color[] playerColours = { Color.WHITE, Color.RED, Color.BLUE, Color.GREEN, Color.PURPLE, Color.ORANGE }; + + private void updateLabel() { + label.setTextFill(playerColours[grid.getPlayer()]); + label.setText(String.format("Player %d's turn", grid.getPlayer())); + } + + public DotsAndBoxesUI(final DotsAndBoxesGrid grid) { + this.grid = grid; + anchorPane = new AnchorPane(); + + label = new Label(""); + updateLabel(); + grid.addConsumer((g) -> updateLabel()); + + // Size the anchorPane to just contain the elements + int width = margin + dotDiameter + gap + (grid.width - 1) * (gap + lineLength + gap + dotDiameter) + gap + margin; + int height = margin + dotDiameter + gap + (grid.height - 1) * (gap + lineLength + gap + dotDiameter) + gap + margin; + anchorPane.setPrefSize(width, height); + + + // Lay out the boxes + for (int row = 0; row < grid.height - 1; row++) { + for (int col = 0; col < grid.width - 1; col++) { + final int x = col; + final int y = row; + Rectangle box = new Rectangle(gap, gap, lineLength, lineLength); + box.setFill(Color.WHITE); + + grid.addConsumer((g) -> { + box.setFill(playerColours[g.getBoxOwner(x, y)]); + }); + + AnchorPane.setLeftAnchor(box, gap + dotDiameter + col * (gap + lineLength + gap + dotDiameter) + dotDiameter/2.0); + AnchorPane.setTopAnchor(box, gap + dotDiameter + row * (gap + lineLength + gap + dotDiameter) + dotDiameter/2.0); + anchorPane.getChildren().add(box); + } + } + + // Lay out the horizontals + for (int row = 0; row < grid.height; row++) { + for (int col = 0; col < grid.width - 1; col++) { + Line line = new Line(0, dotDiameter/2.0, lineLength, dotDiameter/2.0); + line.setStrokeWidth(dotDiameter); + line.setStroke(Color.DARKGREY); + + final int x = col; + final int y = row; + grid.addConsumer((g) -> { + if (g.getHorizontal(x, y)) { + line.setStroke(Color.BLACK); + } else { + line.setStroke(Color.LIGHTGRAY); + } + }); + + line.setOnMouseClicked((evt) -> {try { + grid.drawHorizontal(x, y, grid.getPlayer()); + } catch (IllegalStateException ex) { + // do nothing + // This is a little artificial, as normally we'd implement this with a check that the line isn't + // already "drawn" and then not calling the function. But for the exercise, we wanted students + // to write a test that would ensure an exception is thrown, so we're relying on an exception + // being thrown! + }}); + + AnchorPane.setLeftAnchor(line, 0.0 + gap + dotDiameter + col * (gap + lineLength + gap + dotDiameter)); + AnchorPane.setTopAnchor(line, -dotDiameter/2.0 + gap + dotDiameter + row * (gap + lineLength + gap + dotDiameter)); + anchorPane.getChildren().add(line); + } + } + + // Lay out the verticals + for (int row = 0; row < grid.height - 1; row++) { + for (int col = 0; col < grid.width; col++) { + Line line = new Line(-dotDiameter/2.0, 0, -dotDiameter/2.0, lineLength); + line.setStrokeWidth(dotDiameter); + line.setStroke(Color.DARKGREY); + + final int x = col; + final int y = row; + grid.addConsumer((g) -> { + if (g.getVertical(x, y)) { + line.setStroke(Color.BLACK); + } else { + line.setStroke(Color.LIGHTGRAY); + } + }); + + line.setOnMouseClicked((evt) -> {try { + grid.drawVertical(x, y, grid.getPlayer()); + } catch (IllegalStateException ex) { + // do nothing + }}); + + AnchorPane.setTopAnchor(line, 0.0 + gap + dotDiameter + row * (gap + lineLength + gap + dotDiameter)); + AnchorPane.setLeftAnchor(line, -dotDiameter/2.0 + gap + dotDiameter + col * (gap + lineLength + gap + dotDiameter)); + anchorPane.getChildren().add(line); + } + } + + // Lay out the dots + for (int row = 0; row < grid.height; row++) { + for (int col = 0; col < grid.width; col++) { + Circle dot = new Circle(dotDiameter / 2.0); + dot.setFill(Color.YELLOW); + + AnchorPane.setLeftAnchor(dot, gap + col * (gap + lineLength + gap + dotDiameter) + dotDiameter/2.0); + AnchorPane.setTopAnchor(dot, gap + row * (gap + lineLength + gap + dotDiameter) + dotDiameter/2.0); + anchorPane.getChildren().add(dot); + } + } + + } + + + +} diff --git a/src/main/java/dotsandboxes/Main.java b/src/main/java/dotsandboxes/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..cd2bce88d0715271aba46b8947f1d903ec301a1b --- /dev/null +++ b/src/main/java/dotsandboxes/Main.java @@ -0,0 +1,40 @@ +package dotsandboxes; + +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; + +/** Our main class that launches the app. */ +public class Main extends Application { + + DotsAndBoxesGrid grid = new DotsAndBoxesGrid(15, 8, 2); + + @Override + public void start(Stage primaryStage) throws Exception { + primaryStage.setTitle("Dots and Boxes"); + + // FIXME: Update this label to show your name and student number + Label label = new Label("Name: (Your name and student number goes here)"); + + BorderPane borderPane = new BorderPane(); + borderPane.setBottom(label); + Scene scene = new Scene(borderPane, 600, 400); + + DotsAndBoxesUI dbUi = new DotsAndBoxesUI(grid); + borderPane.setCenter(dbUi.anchorPane); + borderPane.setTop(dbUi.label); + + primaryStage.setScene(scene); + + primaryStage.show(); + + // This sets what to do when we close the main window. + // Notice that we are using a "lambda function" (i.e., an anonymously defined function defined within the + // call to setOnCloseRequest). These are very useful in GUI code and we'll probably see a lot of them in the + // project. + primaryStage.setOnCloseRequest((evt) -> System.exit(0)); + } + +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000000000000000000000000000000000000..e51a2313046b8aa9453a946e97237e0e8f298fae --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,19 @@ +/** + * This is a module file. + * + * Modules were introduced in Java 9, allowing modules to express dependencies on other modules and to publish their + * public packages. + * + * Using modules makes things easier when working with JavaFX, because JavaFX is itself published as a module. + * + * Please note: "module" in Java is not quite the same as "dependency" in Gradle. They are similar concepts, but + * "modules" is part of the Java language (but doesn't express how to find them on the internet) whereas + * "dependencies" is part of the Gradle build system and expresses how to find them on the internet (but does not + * specify the module imports to the Java compiler). We need both. + */ +module dotsAndBoxes { + requires javafx.graphics; + requires javafx.controls; + requires org.apache.logging.log4j; + exports dotsandboxes; +} \ No newline at end of file diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml new file mode 100644 index 0000000000000000000000000000000000000000..fc48a88519bd2c9dc4af3e86af8e2ce0c18b2486 --- /dev/null +++ b/src/test/resources/log4j2.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration status="WARN"> + <Appenders> + <Console name="Console" target="SYSTEM_OUT"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> + </Console> + </Appenders> + <Loggers> + <Root level="debug"> + <AppenderRef ref="Console"/> + </Root> + </Loggers> +</Configuration> \ No newline at end of file