package aima.gui.fx.applications.search.games;
import java.util.Observable;
import aima.gui.fx.framework.IntegrableApplication;
import javafx.beans.binding.Bindings;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.RowConstraints;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
/**
* Starter of the JavaFX version of the Connect Four game with integrated
* artificial intelligence player. It uses Minimax search with Alpha-Beta
* pruning and an iterative deepening strategy to meet the specified time
* constraints.
*
* @author Ruediger Lunde
*/
public class ConnectFourApp extends IntegrableApplication {
public static void main(String[] args) {
launch(args);
}
private ConnectFourModel model;
// create attributes for all parts of the application which need to be
// accessed by listeners, inner classes, and helper methods
private Button clearBtn;
private ComboBox<String> strategyCombo;
private ComboBox<String> timeCombo;
private Button proposeBtn;
private Label statusBar;
private Button[] colBtns;
private Circle[] disks;
@Override
public String getTitle() {
return "Connect Four App";
}
@Override
public Pane createRootPane() {
model = new ConnectFourModel();
BorderPane root = new BorderPane();
ToolBar toolBar = new ToolBar();
toolBar.setStyle("-fx-background-color: rgb(0, 0, 200)");
clearBtn = new Button("Clear");
clearBtn.setOnAction(ev -> model.initGame());
strategyCombo = new ComboBox<>();
strategyCombo.getItems().addAll("Iterative Deepening Alpha-Beta", "Advanced Alpha-Beta");
strategyCombo.getSelectionModel().select(0);
timeCombo = new ComboBox<>();
timeCombo.getItems().addAll("2sec", "4sec", "6sec", "8sec");
timeCombo.getSelectionModel().select(1);
proposeBtn = new Button("Propose Move");
proposeBtn.setOnAction(ev -> model.proposeMove((timeCombo.getSelectionModel().getSelectedIndex() + 1) * 2,
strategyCombo.getSelectionModel().getSelectedIndex()));
toolBar.getItems().addAll(clearBtn, new Separator(), strategyCombo, timeCombo, proposeBtn);
root.setTop(toolBar);
final int rows = model.getRows();
final int cols = model.getCols();
BorderPane boardPane = new BorderPane();
ColumnConstraints colCons = new ColumnConstraints();
colCons.setPercentWidth(100.0 / cols);
GridPane btnPane = new GridPane();
GridPane diskPane = new GridPane();
btnPane.setHgap(10);
btnPane.setPadding(new Insets(10, 10, 0, 10));
btnPane.setStyle("-fx-background-color: rgb(0, 50, 255)");
diskPane.setPadding(new Insets(10, 10, 10, 10));
diskPane.setStyle("-fx-background-color: rgb(0, 50, 255)");
colBtns = new Button[cols];
for (int i = 0; i < cols; i++) {
Button colBtn = new Button("" + (i + 1));
colBtn.setId("" + (i + 1));
colBtn.setMaxWidth(Double.MAX_VALUE);
colBtn.setOnAction(ev -> {
String id = ((Button) ev.getSource()).getId();
int col = Integer.parseInt(id);
model.makeMove(col - 1);
});
colBtns[i] = colBtn;
btnPane.add(colBtn, i, 0);
GridPane.setHalignment(colBtn, HPos.CENTER);
btnPane.getColumnConstraints().add(colCons);
diskPane.getColumnConstraints().add(colCons);
}
boardPane.setTop(btnPane);
diskPane.setMinSize(0, 0);
diskPane.setPrefSize(cols * 100, rows * 100);
RowConstraints rowCons = new RowConstraints();
rowCons.setPercentHeight(100.0 / rows);
for (int i = 0; i < rows; i++)
diskPane.getRowConstraints().add(rowCons);
disks = new Circle[rows * cols];
for (int i = 0; i < rows * cols; i++) {
Circle disk = new Circle(30);
disk.radiusProperty().bind(Bindings.min(Bindings.divide(diskPane.widthProperty(), cols * 3),
Bindings.divide(diskPane.heightProperty(), rows * 3)));
disks[i] = disk;
diskPane.add(disk, i % cols, i / cols);
GridPane.setHalignment(disk, HPos.CENTER);
}
boardPane.setCenter(diskPane);
root.setCenter(boardPane);
statusBar = new Label();
statusBar.setMaxWidth(Double.MAX_VALUE);
statusBar.setStyle("-fx-background-color: rgb(0, 0, 200); -fx-font-size: 20");
root.setBottom(statusBar);
model.addObserver(this::update);
return root;
}
@Override
public void initialize() {
update(null, null);
}
@Override
public void cleanup() {
// Nothing to do here...
}
/** Updates the view after the model state has changed. */
private void update(Observable o, Object arg) {
final int cols = model.getCols();
for (int i = 0; i < disks.length; i++) {
String player = model.getPlayerAt(i / cols, i % cols);
Color color = Color.DARKBLUE;
if (player != null)
color = player.equals("red") ? Color.RED : Color.YELLOW;
else {
for (String p : model.getPlayers())
if (model.isWinPositionFor(i / cols, i % cols, p))
color = Color.BLACK;
}
disks[i].setFill(color);
}
String statusText;
if (!model.isGameOver()) {
String toMove = (String) model.getPlayerForNextMove();
statusText = "Next move: " + toMove;
statusBar.setTextFill(toMove.equals("red") ? Color.RED : Color.YELLOW);
} else {
String winner = model.getWinner();
if (winner != null)
statusText = "Color " + winner + " has won. Congratulations!";
else
statusText = "No winner :-(";
statusBar.setTextFill(Color.WHITE);
}
if (model.searchMetrics != null)
statusText += " " + model.searchMetrics;
statusBar.setText(statusText);
}
}