JavaFX 我怎么能在这种情况下检查是否获胜?
JavaFX how could I check for winning in this occasion?
我会尽力解释我的问题。我总是很高兴找到有耐心的人可以帮助我改进并摆脱困境。
基本上我正在尝试构建一个 javafx tic-tac-toe 游戏,因为我觉得即使是像这样简单的事情我也可以学习。
所以我有一个启动程序的主 App.java 文件,并且有一些方法可以正确设置我要使用的 fxml 文件并对其进行更改。这样一来,如果将来我想为玩家添加配置文件或在本地与 AI 或两个玩家进行单人游戏的可能性,我可以轻松创建另一个 fxml 文件来处理其他场景。
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
/**
* JavaFX App
*/
public class App extends Application {
private static Scene scene;
@Override
public void start(Stage stage) throws IOException {
scene = new Scene(loadFXML("primary"));
stage.setScene(scene);
stage.show();
}
static void setRoot(String fxml) throws IOException {
scene.setRoot(loadFXML(fxml));
}
private static Parent loadFXML(String fxml) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"));
return fxmlLoader.load();
}
public static void main(String[] args) {
launch();
}
}
然后我有一个 PrimaryController.java 文件,基本上想用它来用节点填充场景。该结构是一个 AnchorPane,里面有一个 GridPane,我通过代码填充 GridPane,方法是向其中添加 MyPanes 元素,这些元素是对 StackPanes 进行了一些修改。
这里是控制器:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.GridPane;
public class PrimaryController implements Initializable {
@FXML
public GridPane grid;
@Override
public void initialize(URL location, ResourceBundle resources) {
for (int i = 0; i<3; i++){
for (int j = 0; j <3;j++){
MyTiles tile = new MyTiles();
grid.add(tile,j,i);
}
}
}
}
这里是 MyTile class
import javafx.geometry.Pos;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
public class MyTiles extends StackPane {
private static boolean turnOne = true;
private Text text = new Text(" ");
public MyTiles(){
setPrefSize(200,200);
Rectangle rectangle = new Rectangle(200,200);
rectangle.setFill(null);
rectangle.setStroke(Color.BLACK);
text.setFont(Font.font(72));
setAlignment(Pos.CENTER);
getChildren().addAll(rectangle,text);
setOnMouseClicked(event -> {
if (text.getText().equals(" ")){
if (turnOne){
drawX(text);
turnOne = false;
}else if (!turnOne){
drawY(text);
turnOne = true;
}
}else{
return;
}
});
}
public String getTheText(){
return text.getText();
}
private void drawX(Text text){
text.setStroke(Color.BLUE);
text.setFill(Color.LIGHTBLUE);
text.setText("x");
}
private void drawY(Text text){
text.setStroke(Color.RED);
text.setFill(Color.PINK);
text.setText("o");
}
}
现在开始提问:你知道我可以用什么方法检查是否中奖吗?我的意思是我知道玩家获胜背后的逻辑,但在我的脑海中我必须:
- 访问 GridPane 内的所有 MyTiles 对象
- 检查他们的文字是什么
- 检查是否有获胜组合。
我不知道该怎么做,有人可以给我一些提示、建议和示例吗?
也许在单独的文件中做事的策略不是最好的,但我想尝试是否有办法不把所有东西都放在主 App.java 文件中。
非常感谢任何帮助。我希望这个问题足够清楚!
再次感谢!
您应该将游戏状态分解为独立于用户界面本身的独立 classes。 (在 UI 开发术语中,这称为 "Model" 并且是模型-视图-控制器架构的一部分)。 JavaFX 提供了一些属性和绑定 API,使您的视图能够相对容易地观察模型并与其保持同步。
Tic-tac-toe 游戏的模型应该有每个方块的属性数组或列表,显示哪个玩家拥有 "occupied" 该方块,以及其他基本游戏状态(下一个玩家玩的是哪个玩家) ,有没有人赢了,游戏结束了,等等)。当前玩家应该有 API 才能在给定的空方格中移动。由于游戏状态是已进行移动的函数,因此最好将属性公开为只读,并在进行移动时在内部更新它们。
然后您可以实施算法以直接在模型中检查获胜者。使用 JavaFX 绑定,您可以确保只要其中一个方块的状态发生变化,它就会更新。
这是一个相当基本的实现:
首先是玩家占据一个方格的简单枚举:
public enum Player { O, X, NONE }
然后是主"model"class:
import java.util.ArrayList;
import java.util.List;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
public class TicTacToe {
private final List<ReadOnlyObjectWrapper<Player>> squares ;
private final ReadOnlyObjectWrapper<Player> winner ;
private final ReadOnlyObjectWrapper<Player> currentPlayer ;
private final ReadOnlyBooleanWrapper gameOver ;
public final static int SIZE = 3 ;
public TicTacToe() {
squares = new ArrayList<>();
for (int i = 0 ; i < SIZE*SIZE ; i++) {
squares.add(new ReadOnlyObjectWrapper<>(Player.NONE));
}
Observable[] squareArray = squares.toArray(new Observable[SIZE*SIZE]);
winner = new ReadOnlyObjectWrapper<>(Player.NONE);
winner.bind(Bindings.createObjectBinding(this::checkForWinner, squareArray));
currentPlayer = new ReadOnlyObjectWrapper<>(Player.O);
gameOver = new ReadOnlyBooleanWrapper() ;
gameOver.bind(new BooleanBinding() {
{
bind(winner);
bind(squareArray);
}
@Override
protected boolean computeValue() {
return checkGameOver();
}
});
}
public ReadOnlyObjectProperty<Player> squareProperty(int square) {
return squares.get(square).getReadOnlyProperty();
}
public final Player getSquare(int square) {
return squareProperty(square).get();
}
public ReadOnlyObjectProperty<Player> winnerProperty() {
return winner.getReadOnlyProperty();
}
public final Player getWinner() {
return winnerProperty().get();
}
public ReadOnlyObjectProperty<Player> currentPlayerProperty() {
return currentPlayer.getReadOnlyProperty();
}
public final Player getCurrentPlayer() {
return currentPlayerProperty().get();
}
public ReadOnlyBooleanProperty gameOverProperty() {
return gameOver.getReadOnlyProperty();
}
public final boolean isGameOver() {
return gameOverProperty().get();
}
public boolean isEmpty(int square) {
return getSquare(square) == Player.NONE ;
}
public void move(int square) {
squares.get(square).set(currentPlayer.get());
currentPlayer.set(opponent(currentPlayer.get()));
}
public void reset() {
squares.forEach(s -> s.set(Player.NONE));
currentPlayer.set(Player.O);
}
private boolean checkGameOver() {
if (getWinner() != Player.NONE) return true ;
return squares.stream()
.map(ReadOnlyObjectProperty::get)
.filter(Player.NONE::equals)
.findAny()
.isEmpty();
}
private Player checkForWinner() {
// To do: if either player has won, return that player.
// If no player has won (yet) return Player.NONE
return Player.NONE ;
}
private Player opponent(Player player) {
// Note Java 14 is required for switch expressions
return switch(player) {
case NONE -> Player.NONE ;
case O -> Player.X ;
case X -> Player.O ;
};
}
}
现在很容易将 UI 绑定到该模型。例如:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.171"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.jamesd.examples.tictactoe.TicTacToeController">
<top>
<VBox styleClass="controls">
<Label fx:id="winner" />
<Button fx:id="reset" onAction="#reset" text="Reset" />
</VBox>
</top>
<center>
<GridPane fx:id="board" styleClass="board" alignment="CENTER">
</GridPane>
</center>
</BorderPane>
和控制器:
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
public class TicTacToeController {
private final TicTacToe game = new TicTacToe() ;
@FXML
private GridPane board ;
@FXML
private Label winner ;
@FXML
private Button reset ;
public void initialize() {
for (int row = 0 ; row < TicTacToe.SIZE ; row++) {
for (int column = 0 ; column < TicTacToe.SIZE ; column++) {
board.add(createTile(row, column), column, row);
}
}
winner.textProperty().bind(game.winnerProperty().asString("Winner: %s"));
reset.disableProperty().bind(game.gameOverProperty().not());
}
@FXML
private void reset() {
game.reset();
}
public Pane createTile(int row, int column) {
StackPane pane = new StackPane();
pane.getStyleClass().add("square");
Label o = new Label("O");
Label x = new Label("X");
pane.getChildren().addAll(o, x);
int square = row * TicTacToe.SIZE + column;
ReadOnlyObjectProperty<Player> player = game.squareProperty(square) ;
o.visibleProperty().bind(player.isEqualTo(Player.O));
x.visibleProperty().bind(player.isEqualTo(Player.X));
pane.setOnMouseClicked(e -> {
if (game.isEmpty(square) && ! game.isGameOver()) {
game.move(square);
}
});
return pane ;
}
}
为了完整起见,样式 classes 挂接到外部 CSS 文件:
.board {
-fx-padding: 2 ;
}
.square {
-fx-background-color: black, -fx-background ;
-fx-background-insets: 0, 1;
-fx-min-width: 200 ;
-fx-min-height: 200 ;
}
.square .label {
-fx-font-size:108;
-fx-font-family: comic-sans ;
-fx-text-fill:#00b041 ;
}
.controls {
-fx-spacing: 5 ;
-fx-padding: 5 ;
-fx-alignment: center ;
}
并且应用程序启动 class 看起来像
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("TicTacToeGrid.fxml"));
Scene scene = new Scene(loader.load());
scene.getStylesheets().add(getClass().getResource("tictactoe.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
我会尽力解释我的问题。我总是很高兴找到有耐心的人可以帮助我改进并摆脱困境。 基本上我正在尝试构建一个 javafx tic-tac-toe 游戏,因为我觉得即使是像这样简单的事情我也可以学习。
所以我有一个启动程序的主 App.java 文件,并且有一些方法可以正确设置我要使用的 fxml 文件并对其进行更改。这样一来,如果将来我想为玩家添加配置文件或在本地与 AI 或两个玩家进行单人游戏的可能性,我可以轻松创建另一个 fxml 文件来处理其他场景。
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
/**
* JavaFX App
*/
public class App extends Application {
private static Scene scene;
@Override
public void start(Stage stage) throws IOException {
scene = new Scene(loadFXML("primary"));
stage.setScene(scene);
stage.show();
}
static void setRoot(String fxml) throws IOException {
scene.setRoot(loadFXML(fxml));
}
private static Parent loadFXML(String fxml) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"));
return fxmlLoader.load();
}
public static void main(String[] args) {
launch();
}
}
然后我有一个 PrimaryController.java 文件,基本上想用它来用节点填充场景。该结构是一个 AnchorPane,里面有一个 GridPane,我通过代码填充 GridPane,方法是向其中添加 MyPanes 元素,这些元素是对 StackPanes 进行了一些修改。 这里是控制器:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.GridPane;
public class PrimaryController implements Initializable {
@FXML
public GridPane grid;
@Override
public void initialize(URL location, ResourceBundle resources) {
for (int i = 0; i<3; i++){
for (int j = 0; j <3;j++){
MyTiles tile = new MyTiles();
grid.add(tile,j,i);
}
}
}
}
这里是 MyTile class
import javafx.geometry.Pos;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
public class MyTiles extends StackPane {
private static boolean turnOne = true;
private Text text = new Text(" ");
public MyTiles(){
setPrefSize(200,200);
Rectangle rectangle = new Rectangle(200,200);
rectangle.setFill(null);
rectangle.setStroke(Color.BLACK);
text.setFont(Font.font(72));
setAlignment(Pos.CENTER);
getChildren().addAll(rectangle,text);
setOnMouseClicked(event -> {
if (text.getText().equals(" ")){
if (turnOne){
drawX(text);
turnOne = false;
}else if (!turnOne){
drawY(text);
turnOne = true;
}
}else{
return;
}
});
}
public String getTheText(){
return text.getText();
}
private void drawX(Text text){
text.setStroke(Color.BLUE);
text.setFill(Color.LIGHTBLUE);
text.setText("x");
}
private void drawY(Text text){
text.setStroke(Color.RED);
text.setFill(Color.PINK);
text.setText("o");
}
}
现在开始提问:你知道我可以用什么方法检查是否中奖吗?我的意思是我知道玩家获胜背后的逻辑,但在我的脑海中我必须: - 访问 GridPane 内的所有 MyTiles 对象 - 检查他们的文字是什么 - 检查是否有获胜组合。
我不知道该怎么做,有人可以给我一些提示、建议和示例吗?
也许在单独的文件中做事的策略不是最好的,但我想尝试是否有办法不把所有东西都放在主 App.java 文件中。
非常感谢任何帮助。我希望这个问题足够清楚! 再次感谢!
您应该将游戏状态分解为独立于用户界面本身的独立 classes。 (在 UI 开发术语中,这称为 "Model" 并且是模型-视图-控制器架构的一部分)。 JavaFX 提供了一些属性和绑定 API,使您的视图能够相对容易地观察模型并与其保持同步。
Tic-tac-toe 游戏的模型应该有每个方块的属性数组或列表,显示哪个玩家拥有 "occupied" 该方块,以及其他基本游戏状态(下一个玩家玩的是哪个玩家) ,有没有人赢了,游戏结束了,等等)。当前玩家应该有 API 才能在给定的空方格中移动。由于游戏状态是已进行移动的函数,因此最好将属性公开为只读,并在进行移动时在内部更新它们。
然后您可以实施算法以直接在模型中检查获胜者。使用 JavaFX 绑定,您可以确保只要其中一个方块的状态发生变化,它就会更新。
这是一个相当基本的实现:
首先是玩家占据一个方格的简单枚举:
public enum Player { O, X, NONE }
然后是主"model"class:
import java.util.ArrayList;
import java.util.List;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
public class TicTacToe {
private final List<ReadOnlyObjectWrapper<Player>> squares ;
private final ReadOnlyObjectWrapper<Player> winner ;
private final ReadOnlyObjectWrapper<Player> currentPlayer ;
private final ReadOnlyBooleanWrapper gameOver ;
public final static int SIZE = 3 ;
public TicTacToe() {
squares = new ArrayList<>();
for (int i = 0 ; i < SIZE*SIZE ; i++) {
squares.add(new ReadOnlyObjectWrapper<>(Player.NONE));
}
Observable[] squareArray = squares.toArray(new Observable[SIZE*SIZE]);
winner = new ReadOnlyObjectWrapper<>(Player.NONE);
winner.bind(Bindings.createObjectBinding(this::checkForWinner, squareArray));
currentPlayer = new ReadOnlyObjectWrapper<>(Player.O);
gameOver = new ReadOnlyBooleanWrapper() ;
gameOver.bind(new BooleanBinding() {
{
bind(winner);
bind(squareArray);
}
@Override
protected boolean computeValue() {
return checkGameOver();
}
});
}
public ReadOnlyObjectProperty<Player> squareProperty(int square) {
return squares.get(square).getReadOnlyProperty();
}
public final Player getSquare(int square) {
return squareProperty(square).get();
}
public ReadOnlyObjectProperty<Player> winnerProperty() {
return winner.getReadOnlyProperty();
}
public final Player getWinner() {
return winnerProperty().get();
}
public ReadOnlyObjectProperty<Player> currentPlayerProperty() {
return currentPlayer.getReadOnlyProperty();
}
public final Player getCurrentPlayer() {
return currentPlayerProperty().get();
}
public ReadOnlyBooleanProperty gameOverProperty() {
return gameOver.getReadOnlyProperty();
}
public final boolean isGameOver() {
return gameOverProperty().get();
}
public boolean isEmpty(int square) {
return getSquare(square) == Player.NONE ;
}
public void move(int square) {
squares.get(square).set(currentPlayer.get());
currentPlayer.set(opponent(currentPlayer.get()));
}
public void reset() {
squares.forEach(s -> s.set(Player.NONE));
currentPlayer.set(Player.O);
}
private boolean checkGameOver() {
if (getWinner() != Player.NONE) return true ;
return squares.stream()
.map(ReadOnlyObjectProperty::get)
.filter(Player.NONE::equals)
.findAny()
.isEmpty();
}
private Player checkForWinner() {
// To do: if either player has won, return that player.
// If no player has won (yet) return Player.NONE
return Player.NONE ;
}
private Player opponent(Player player) {
// Note Java 14 is required for switch expressions
return switch(player) {
case NONE -> Player.NONE ;
case O -> Player.X ;
case X -> Player.O ;
};
}
}
现在很容易将 UI 绑定到该模型。例如:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.171"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.jamesd.examples.tictactoe.TicTacToeController">
<top>
<VBox styleClass="controls">
<Label fx:id="winner" />
<Button fx:id="reset" onAction="#reset" text="Reset" />
</VBox>
</top>
<center>
<GridPane fx:id="board" styleClass="board" alignment="CENTER">
</GridPane>
</center>
</BorderPane>
和控制器:
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
public class TicTacToeController {
private final TicTacToe game = new TicTacToe() ;
@FXML
private GridPane board ;
@FXML
private Label winner ;
@FXML
private Button reset ;
public void initialize() {
for (int row = 0 ; row < TicTacToe.SIZE ; row++) {
for (int column = 0 ; column < TicTacToe.SIZE ; column++) {
board.add(createTile(row, column), column, row);
}
}
winner.textProperty().bind(game.winnerProperty().asString("Winner: %s"));
reset.disableProperty().bind(game.gameOverProperty().not());
}
@FXML
private void reset() {
game.reset();
}
public Pane createTile(int row, int column) {
StackPane pane = new StackPane();
pane.getStyleClass().add("square");
Label o = new Label("O");
Label x = new Label("X");
pane.getChildren().addAll(o, x);
int square = row * TicTacToe.SIZE + column;
ReadOnlyObjectProperty<Player> player = game.squareProperty(square) ;
o.visibleProperty().bind(player.isEqualTo(Player.O));
x.visibleProperty().bind(player.isEqualTo(Player.X));
pane.setOnMouseClicked(e -> {
if (game.isEmpty(square) && ! game.isGameOver()) {
game.move(square);
}
});
return pane ;
}
}
为了完整起见,样式 classes 挂接到外部 CSS 文件:
.board {
-fx-padding: 2 ;
}
.square {
-fx-background-color: black, -fx-background ;
-fx-background-insets: 0, 1;
-fx-min-width: 200 ;
-fx-min-height: 200 ;
}
.square .label {
-fx-font-size:108;
-fx-font-family: comic-sans ;
-fx-text-fill:#00b041 ;
}
.controls {
-fx-spacing: 5 ;
-fx-padding: 5 ;
-fx-alignment: center ;
}
并且应用程序启动 class 看起来像
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("TicTacToeGrid.fxml"));
Scene scene = new Scene(loader.load());
scene.getStylesheets().add(getClass().getResource("tictactoe.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}