您可以在串联的字符串绑定上创建一个侦听器,然后使用它来监视所有按钮的串联文本。
例如:
StringExpression state = Bindings.concat(
board.getChildren().stream()
.map(node -> ((Button) node).textProperty())
.toArray()
);
state.addListener((observable, oldState, newState) ->
handleStateChange(newState)
);
这可能比必要的要复杂一些。对于基本解决方案,对按钮单击做出反应并在循环中更新状态(如valentin's solution)就可以了,而不是使用绑定。但是绑定确实提供了一种替代解决方案,保留了您原来的连接按钮文本的概念。
代表newState 的字符串值的示例是一个九字符的字符串:
"XO XO X"
在这个例子中,未玩过的方格是空格,而 X 刚刚以从左上角到右下角的对角线获胜。
上下文示例
- 使用 James 的建议,即检查线条以进行平局检测。
- 使用 Java 17 功能。
应用代码
import javafx.application.*;
import javafx.beans.binding.*;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.TilePane;
import javafx.stage.*;
public class NoughtsAndCrosses extends Application {
private static final int NUM_SQUARES = 9;
private static final String X = "X";
private static final String O = "O";
private static final String TIE = "-";
private static final String NONE = " ";
private String nextPlayer = X;
private Scene scene;
private static final int[][] LINES = {
{1,2,3}, {4,5,6}, {7,8,9}, // horizontal
{1,4,7}, {2,5,8}, {3,6,9}, // vertical
{1,5,9}, {3,5,7} // diagonal
};
public void start(Stage stage) {
scene = new Scene(newGame());
stage.setScene(scene);
stage.setTitle("Noughts and Crosses");
stage.show();
}
private Parent newGame() {
TilePane board = createBoard();
if (scene != null) {
scene.setRoot(board);
}
return board;
}
private TilePane createBoard() {
TilePane board = new TilePane(10, 10);
board.setStyle("-fx-base: antiquewhite; -fx-font-size: 30; -fx-font-weight: bold;");
board.setPadding(new Insets(10));
board.setPrefColumns(3);
board.setMinSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
board.setMaxSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
for (int i = 0; i < NUM_SQUARES; i++) {
board.getChildren().add(createSquare());
}
StringExpression state = Bindings.concat(
board.getChildren().stream()
.map(node -> ((Button) node).textProperty())
.toArray()
);
state.addListener((observable, oldState, newState) ->
handleStateChange(newState)
);
return board;
}
private Button createSquare() {
final Button square = new Button(NONE);
square.setMinSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
square.setPrefSize(65, 65);
square.setMaxSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
square.setOnAction(e -> takeTurn(square));
return square;
}
private void takeTurn(Button square) {
square.setText(nextPlayer);
nextPlayer = X.equals(nextPlayer) ? O : X;
}
private void handleStateChange(String state) {
int nTies = 0;
for (int[] line : LINES) {
String result = checkResult(line, state);
switch (result) {
case X -> { endGame(X); return; }
case O -> { endGame(O); return; }
case TIE -> nTies++;
}
}
if (nTies == LINES.length) {
endGame(TIE);
}
}
private String checkResult(int[] line, String state) {
int numX = 0, numO = 0;
for (int j : line) {
String cellState = state.substring(j - 1, j);
switch (cellState) {
case X -> numX++;
case O -> numO++;
}
}
if (numX == 3) {
return X;
}
if (numO == 3) {
return O;
}
if (numX > 0 && numO > 0) {
return TIE;
}
return NONE;
}
private void endGame(String result) {
String msg = switch (result) {
case X -> X + " won";
case O -> O + " won";
case TIE -> "Tie";
default -> "unexpected result";
};
Alert resultDialog = new Alert(
Alert.AlertType.CONFIRMATION,
"""
Play again?
%s will start first.
""".formatted(X.equals(nextPlayer) ? O : X)
);
resultDialog.setHeaderText(msg);
resultDialog.setTitle("Game Over");
resultDialog.initOwner(
scene.getWindow()
);
// we do this in a run later as we want the board state to update for
// the last interaction before displaying the result dialog.
Platform.runLater(() ->
resultDialog.showAndWait()
.filter(response -> response == ButtonType.OK)
.ifPresentOrElse(
response -> newGame(),
Platform::exit
)
);
}
public static void main(String[] args) {
launch(args);
}
}