2

I tried to recreate Connect Four, and I succeeded. But I wanted to give the player an indication of where the winning four discs were, by switching the color every so often. I am new to threads and the concept of time in programming.

I also succeeded in giving the user this indication, but after I close the application, the console still gives output, also when I use setOnCloseRequest.

A few other questions:
1: for colors I used html names, is it better to use a hex triplet or no preference.
2: To stop the grid and other elements from hugging the left side of the screen, I added a border with the same color as the background, is there a better way of doing this?
3: I did not create a method for translating the keycode to an integer but instead created in the init function. I did this because I don't know how to pass the keyevent. How to do this?

Here is the code:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class FourInARow extends Application {

GridPane boardGrid = new GridPane();

Label[][] labels = new Label[7][7];
Label statusLabel = new Label();
int[][] cell = new int[7][6];

int player = 0;
int won = 0;

String baseStyle = "-fx-background-radius: 40; -fx-min-width: 80; -fx-min-height: 80; -fx-alignment: center; -fx-border-width: 2; -fx-border-color: #000000;-fx-background-color: ";

ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();

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

private void init(Stage window){

    createLabels();
    startGame();

    Label above = new Label("Try to connect four discs in a row!");
    above.setStyle("-fx-font-size: 30; -fx-alignment: center; -fx-min-width: 600");
    boardGrid.setStyle("-fx-background-color: silver;-fx-border-color: #F4F4F4;-fx-border-width: 0 20 0 20");
    Button newGame = new Button("New Game");
    newGame.setStyle("-fx-min-width: 100;-fx-font-size:20");
    newGame.setOnAction(e -> startGame());
    statusLabel.setStyle("-fx-font-size: 30;-fx-alignment: center; -fx-min-width: 300;");
    HBox below = new HBox();
    below.setStyle("-fx-border-width: 0 0 0 20;-fx-border-color: #F4F4F4");
    below.getChildren().addAll(newGame, statusLabel);
    VBox layout = new VBox();
    layout.getChildren().addAll(above, boardGrid, below);
    Scene scene = new Scene(layout, 600, 620);
    scene.setOnKeyPressed(e -> {
        if (won == 0) {
            try {
                String k = e.getCode().toString();
                int l = k.length();
                int col = Integer.parseInt(k.substring(l - 1, l)) - 1;
                placeDisc(col, player);
                switchPlayer();
                updateScreen();
            } catch (NumberFormatException | ArrayIndexOutOfBoundsException error) {
                System.out.println("error: " + error);
            }
        }
    });
    window.setScene(scene);
    window.setTitle("Connect Four");

    threadThing();
}

private void threadThing() {
    service.scheduleAtFixedRate(() -> {
        try {
            wonStyle();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, 0, 1, TimeUnit.SECONDS);
}

private void startGame() {
    cell = new int[7][6];
    won = player = 0;
    statusLabel.setText("");
    updateScreen();
}

private void updateScreen() {
    for (int i = 0; i < 7; i++) {
        for (int j = 0; j < 6; j++) {
            labels[i][j].setStyle(baseStyle + addStyle(cell[i][j]));
        }
        labels[i][6].setText(Integer.toString(i+1));
        labels[i][6].setStyle("-fx-alignment: center;-fx-min-width: 80;-fx-background-color: #F4F4F4;-fx-font-size: 30;");
    }

    switch(won) {
        case 1: statusLabel.setText("Blue has won!");break;
        case 2: statusLabel.setText("Yellow has won!");break;
    }
}

private String addStyle(int cell) {
    String style = "silver";
    switch(cell){
        case 1: style = "blue"; break;
        case 2: style = "yellow"; break;
        case 3: style = "darkblue"; break;
        case 4: style = "gold;"; break;
    }
    return style;
}

private void placeDisc(int col, int player) {
    for (int i = 5; i >= 0 ; i--) {
        if(cell[col][i] == 0){
            cell[col][i] = 1;
            if(player == 1) cell[col][i] = 2;
            break;
        }else{
            if(i==0) switchPlayer();
        }
    }
    checkWon();
}

private void checkWon() {
    for (int i = 0; i < 7; i++) {
        for (int j = 0; j < 6; j++) {
            if (cell[i][j] != 0) {
                try {
                    if (cell[i][j] == cell[i][j + 1] && cell[i][j] == cell[i][j + 2] && cell[i][j] == cell[i][j + 3]) {
                        won = cell[i][j];
                        cell[i][j] = cell[i][j + 1] = cell[i][j + 2] = cell[i][j + 3] = cell[i][j] + 2;
                    }
                }catch(ArrayIndexOutOfBoundsException error) {}
                try {
                    if (cell[i][j] == cell[i + 1][j] && cell[i][j] == cell[i + 2][j] && cell[i][j] == cell[i + 3][j]) {
                        System.out.println("Horizontal win");
                        won = cell[i][j];
                        cell[i][j] = cell[i + 1][j] = cell[i + 2][j] = cell[i + 3][j] = cell[i][j] + 2;
                    }
                }catch(ArrayIndexOutOfBoundsException error) {}
                try {
                    if (cell[i][j] == cell[i + 1][j + 1] && cell[i][j] == cell[i + 2][j + 2] && cell[i][j] == cell[i + 3][j + 3]) {
                        won = cell[i][j];
                        cell[i][j] = cell[i + 1][j + 1] = cell[i + 2][j + 2] = cell[i + 3][j + 3] = cell[i][j] + 2;
                    }
                }catch(ArrayIndexOutOfBoundsException error) {}
                try {
                    if (cell[i][j] == cell [i + 1][j - 1] && cell[i][j] == cell[i + 2][j - 2] && cell[i][j] == cell[i + 3][j - 3]) {
                        won = cell[i][j];
                        cell[i][j] = cell[i + 1][j - 1] = cell[i + 2][j - 2] = cell[i + 3][j - 3] = cell[i][j] + 2;
                    }
                }catch(ArrayIndexOutOfBoundsException error) {}
            }
        }
    }
}

private void switchPlayer() {
    if(player == 0) player = 2;
    player--;
}

private void createLabels() {
    for (int i = 0; i < 7; i++) {
        for (int j = 0; j < 7; j++) {
            labels[i][j] = new Label();
            boardGrid.add(labels[i][j], i, j);
        }
    }
}

private void wonStyle() throws InterruptedException {
    System.out.println("Test");
    boolean run = false;
    for (int i = 0; i < 7; i++) {
        for (int j = 0; j < 6; j++) {
            if(cell[i][j] > 2 && !run){
                Thread.sleep(500);
                addStyleFlicker();
                run = true;
            }
        }
    }
}

private void addStyleFlicker() throws InterruptedException {
    String[] styleOne = {"-fx-background-radius: 40; -fx-min-width: 80; -fx-min-height: 80; -fx-alignment: center; -fx-border-width: 2; -fx-border-color: #000000;-fx-background-color: blue;",
                         "-fx-background-radius: 40; -fx-min-width: 80; -fx-min-height: 80; -fx-alignment: center; -fx-border-width: 2; -fx-border-color: #000000;-fx-background-color: darkblue;"};
    String[] styleTwo = {"-fx-background-radius: 40; -fx-min-width: 80; -fx-min-height: 80; -fx-alignment: center; -fx-border-width: 2; -fx-border-color: #000000;-fx-background-color: yellow;",
                         "-fx-background-radius: 40; -fx-min-width: 80; -fx-min-height: 80; -fx-alignment: center; -fx-border-width: 2; -fx-border-color: #000000;-fx-background-color: gold;"};
    for (int i = 0; i < 7; i++) {
        for (int j = 0; j < 6; j++) {
            if(cell[i][j] == 3){
                labels[i][j].setStyle(styleOne[0]);
            }else if(cell[i][j] == 4){
                labels[i][j].setStyle(styleTwo[0]);
            }
        }
    }
    Thread.sleep(500);
    for (int i = 0; i < 7; i++) {
        for (int j = 0; j < 6; j++) {
            if(cell[i][j] == 3){
                labels[i][j].setStyle(styleOne[1]);
            }else if(cell[i][j] == 4) {
                labels[i][j].setStyle(styleTwo[1]);
            }
        }
    }
}

@Override
public void start(Stage window) throws Exception {
    init(window);
    window.show();
}
}
The Coding Wombat
  • 805
  • 1
  • 10
  • 29
  • possible duplicate of [How to close a javafx application?](http://stackoverflow.com/questions/12153622/how-to-close-a-javafx-application) – Ricky Mutschlechner Apr 24 '15 at 13:35
  • I've viewed that post but didn't manage to find a solution. – The Coding Wombat Apr 24 '15 at 13:36
  • 1
    Create the executor with a thread factory that creates daemon threads. (Will post an answer with code if no one else does before I get back to my computer. ) – James_D Apr 24 '15 at 13:39
  • I suspect you need to shut down your executor, and make sure that your Runnable implementation within that resets the interrupted flag once an InterruptedException is raised/thrown – Brian Agnew Apr 24 '15 at 13:40
  • http://stackoverflow.com/a/31519051/3796962 -- Please find this link, it may be help you. Thanks.. – Om Prakash May 12 '16 at 11:04

2 Answers2

5

Per the Java API docs on the Thread class...

The Java Virtual Machine continues to execute threads until either of the following occurs:

  • The exit method of class Runtime has been called and the security manager has permitted the exit operation to take place.
  • All threads that are not daemon threads have died, either by returning from the call to the run method or by throwing an exception that propagates beyond the run method.

So, you need that Executor to create Daemon threads that will die when the JVM is ready to exit. You do this with a custom thread factory. Here's a really simple example:

Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
        
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        }
    });
Community
  • 1
  • 1
David
  • 2,602
  • 1
  • 18
  • 32
  • That also worked, thanks David, is one of both answers better than the other? Or more suitable for this case? – The Coding Wombat Apr 24 '15 at 14:00
  • 2
    I think they're both valid, but System.exit(0) IMO is a bandaid over the real cause which is the non-daemon thread keeping the JVM from exiting. In any event, it's good to know about daemon threads so you can use them in situations where calling System.exit might not be a great idea, like web applications or libraries that third parties might depend on. – David Apr 24 '15 at 14:08
  • @RichardKoetchruyter Gotta agree with David. This answer encourages better practices. Your application is still running due to another thread still being alive. Setting that thread to `daemon` ensures the VM can still exit even though that thread may still be running. `System.exit` is more of a cheap fix, basically stating "I don't know what's keeping my VM running, so exit everything". This answer states "this thread will not cause the VM to continue running" – Vince Apr 24 '15 at 14:15
  • What's the difference between calling Runtime,getRuntime().exit(0) and System.exit(0)? Is the former better practice? – DwarDoh Oct 26 '18 at 22:25
  • From what I can tell, System.exit() calls Runtime.getRuntime().exit(), so it shouldn't matter. There's also a Runtime.getRuntime().halt() option, which is more akin to a force-kill (think `kill -9` on the terminal). It won't run finalizers, it won't close resources, and it won't run shutdown hooks. – David Oct 30 '18 at 12:46
2

Overriding the stop() method from the Application class will allow you to close your console app:

@Override
public void stop() {
    System.exit(0);
}
blacktide
  • 10,654
  • 8
  • 33
  • 53
  • I guess kill -9 also works, but I'd strongly suggest to stick to gracefully sopping all threads. – IceGlow Jan 12 '16 at 13:45