[英]Game loop of a turn-based game
目前,我正在為一款簡單的回合制游戲開發AI。 我設置游戲的方式如下(使用偽代碼):
players = [User, AI];
(for player : players){
player.addEventlistener(MoveListener (moveData)->move(moveData));
}
players[game.getTurn()].startTurn();
move
功能:
move(data){
game.doStuff(data);
if(game.isOver())
return;
game.nextTurn();
players[game.getTurn()].startTurn();
}
這導致以下遞歸:
重復直到游戲結束-注意游戲的長度是有限的,並且不會超過50個動作。 現在,即使遞歸是有限的,我也會遇到stackoverflow錯誤。 我的問題是:有什么辦法可以解決這個問題? 遞歸到底有什么問題嗎? 還是應該改為執行游戲循環? 我了解如果AI
相互對戰將如何工作,但是如果程序必須等待用戶輸入,這將如何工作?
編輯
以下是遞歸的相關類:
Connect4
類:
package connect4;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Connect4 extends Application {
Group root = new Group();
GameSquare[][] squares;
GameButton[] buttons;
int currentTurn;
int columns = 7;
int rows = 6;
Text gameState;
Player[] players;
Game game;
@Override
public void start(Stage primaryStage) {
int size = 50;
int padding = 10;
gameState = new Text();
gameState.setX(padding);
gameState.setY((rows+1)*size+(rows+3)*padding);
root.getChildren().add(gameState);
buttons = new GameButton[columns];
for(int i = 0; i < buttons.length; i++){
buttons[i] = new GameButton(i);
buttons[i].setMaxWidth(size);
buttons[i].setMaxHeight(size);
buttons[i].setLayoutX(i*size+(i+1)*padding);
buttons[i].setLayoutY(padding);
buttons[i].setMouseTransparent(true);
buttons[i].setVisible(false);
root.getChildren().add(buttons[i]);
}
players = new Player[2];
players[0] = new UserControlled(buttons);
players[1] = new AI();
MoveListener listener = (int i) -> {move(i);};
for(Player player : players)
player.addListener(listener);
game = new Game(columns, rows, players.length);
squares = new GameSquare[columns][rows];
for(int x = 0; x < columns; x++){
for(int y = 0; y < rows; y++){
squares[x][y] = new GameSquare(
x*size+(x+1)*padding,
(y+1)*size+(y+2)*padding,
size,
size,
size,
size
);
root.getChildren().add(squares[x][y]);
}
}
players[game.getTurn()].startTurn(game);
updateTurn();
updateSquares();
draw(primaryStage);
}
public void move(int i){
game.move(i);
updateSquares();
if(game.isGameOver()){
if(game.isTie()){
tie();
return;
} else {
win();
return;
}
}
updateTurn();
players[game.getTurn()].startTurn(game);
}
private void updateSquares(){
int[][] board = game.getBoard();
for(int x = 0; x < columns; x++){
for(int y = 0; y < rows; y++){
squares[x][y].setOwner(board[x][y]);
}
}
}
private void updateTurn(){
gameState.setText("Player " + game.getTurn() + "'s turn");
}
public static void main(String[] args) {
launch(args);
}
private void draw(Stage primaryStage){
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
private void win(){
gameState.setText("Player " + game.getWinner() + " has won the game!");
}
private void tie(){
gameState.setText("It's a tie!");
}
}
Game
類:包connect4;
public class Game {
private int turn = 0;
private int[][] board;
private int columns;
private int rows;
private int players;
private boolean gameOver = false;
private boolean tie = false;
private int winner = -1;
public Game(int columns, int rows, int playerCount){
this.columns = columns;
this.rows = rows;
board = new int[columns][rows];
for(int x = 0; x < columns; x++){
for(int y = 0; y < rows; y++){
board[x][y] = -1;
}
}
players = playerCount;
}
public int[][] getBoard(){
return board;
}
public int getTurn(){
return turn;
}
private void updateTurn(){
turn++;
if(turn >= players)
turn = 0;
}
public boolean isGameOver(){
return gameOver;
}
private void win(int player){
gameOver = true;
winner = player;
}
public int getWinner(){
return winner;
}
private void tie(){
gameOver = true;
tie = true;
}
public boolean isTie(){
return tie;
}
public void move(int i){
if(gameOver)
return;
if(columnSpaceLeft(i) == 0){
return;
}
board[i][columnSpaceLeft(i)-1] = turn;
checkWin(turn);
checkFullBoard();
if(gameOver)
return;
updateTurn();
}
private void checkFullBoard(){
for(int i = 0; i < columns; i++){
if(columnSpaceLeft(i) != 0)
return;
}
tie();
}
public int columnSpaceLeft(int column){
for(int i = 0; i < board[column].length; i++){
if(board[column][i] != -1)
return i;
}
return board[column].length;
}
public int[] getAvailableColumns(){
int columnCount = 0;
for(int i = 0; i < board.length; i++){
if(columnSpaceLeft(i) != 0)
columnCount++;
}
int[] columns = new int[columnCount];
int i = 0;
for(int j = 0; j < board.length; j++){
if(columnSpaceLeft(i) != 0){
columns[i] = j;
i++;
}
}
return columns;
}
private Boolean checkWin(int player){
//vertical
for(int x = 0; x < columns; x++){
int count = 0;
for(int y = 0; y < rows; y++){
if(board[x][y] == player)
count++;
else
count = 0;
if(count >= 4){
win(player);
return true;
}
}
}
//horizontal
for(int y = 0; y < rows; y++){
int count = 0;
for(int x = 0; x < columns; x++){
if(board[x][y] == player)
count++;
else
count = 0;
if(count >= 4){
win(player);
return true;
}
}
}
//diagonal
for(int x = 0; x < columns; x++){
for(int y = 0; y < rows; y++){
int count = 0;
//diagonaal /
if(!(x > columns-4 || y < 3) && board[x][y] == player){
count ++;
for(int i = 1; i <= 3; i++){
if(board[x+i][y-i] == player){
count++;
if(count >= 4){
win(player);
return true;
}
} else {
count = 0;
break;
}
}
}
//diagonal \
if(!(x > columns-4 || y > rows-4) && board[x][y] == player){
count ++;
for(int i = 1; i <= 3; i++){
if(board[x+i][y+i] == player){
count++;
if(count >= 4){
win(player);
return true;
}
} else {
count = 0;
break;
}
}
}
}
}
return false;
}
}
UserControlled
類:
package connect4;
import java.util.ArrayList;
import java.util.List;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
public class UserControlled implements Player {
private List<MoveListener> listeners = new ArrayList<MoveListener>();
private GameButton[] buttons;
private boolean active = false;
public UserControlled(GameButton[] buttons){
this.buttons = buttons;
}
@Override
public void addListener(MoveListener listener){
listeners.add(listener);
}
@Override
public void startTurn(Game game){
System.out.println(0);
active = true;
for(int i = 0; i < buttons.length; i++){
if(game.columnSpaceLeft(i) != 0){
setButton(i, true);
buttons[i].setOnAction(new EventHandler<ActionEvent>() {
@Override public void handle(ActionEvent e) {
move(( (GameButton) e.getTarget()).getColumn());
}
});
}
}
}
private void move(int i){
if(!active)
return;
active = false;
disableButtons();
for(MoveListener listener : listeners)
listener.onMove(i);
}
private void disableButtons(){
for(int i = 0; i < buttons.length; i++){
setButton(i, false);
}
}
private void setButton(int i, boolean enable){
if(enable){
buttons[i].setMouseTransparent(false);
buttons[i].setVisible(true);
} else {
buttons[i].setMouseTransparent(true);
buttons[i].setVisible(false);
}
}
}
AI
類與精簡的UserControlled
類基本相同,不同之處startTurn
方法:
int[] columns = game.getAvailableColumns();
move(columns[rng.nextInt(columns.length)]);
MoveListener
接口非常簡單:
public interface MoveListener {
void onMove(int i);
}
堆棧跟蹤:
Exception in thread "JavaFX Application Thread" java.lang.StackOverflowError
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:142)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:49)
at javafx.scene.text.Text.setText(Text.java:370)
//note that the three lines above this are different every time
//as the application crashes at a different point
at connect4.Connect4.updateTurn(Connect4.java:107)
at connect4.Connect4.move(Connect4.java:93)
at connect4.Connect4.lambda$start$0(Connect4.java:49)
at connect4.AI.move(AI.java:13)
at connect4.AI.startTurn(AI.java:24)
at connect4.Connect4.move(Connect4.java:94)
at connect4.Connect4.lambda$start$0(Connect4.java:49)
at connect4.AI.move(AI.java:13)
...etc
通常,除非您非常確定自己在做什么,否則不應使用遞歸。
仔細考慮一下,每次調用下一步時,您將保存所有上下文以及堆棧中的所有局部變量。 在游戲中,可能有很多東西。
回合制游戲中常見的游戲循環如下所示:
while(!gameFinished()){
for(player in players){
player.doTurn();
}
}
還要考慮到,遞歸很慢,因為它必須保存所有上下文並且需要時間,因此,通常,在嘗試使用遞歸之前,請三思。
編輯
要處理輸入,您可以使用以下方法:
CompletableFuture.supplyAsync(this::waitUserInput)
.thenAccept(this::processUserInput)
在這里,您可以找到它的工作方式:
http://www.deadcoderising.com/java8-writing-asynchronous-code-with-completablefuture/
這樣,您的代碼將繼續運行,因此請記住,在下一行代碼中,您將沒有輸入。 獲取輸入后,它將調用proccessUserInput方法。
執行此操作的另一種方法是檢查每一幀是否按了任何鍵,也可以。
在這里,您可以找到一種方法:
您應該做的事情取決於項目的大小。 如果您將一直在檢查按鍵,那么最好為此構建某種事件系統。
另一方面,我建議您使用虛幻引擎或Unity之類的游戲引擎。 如果您想使用Java,那么有很多游戲庫可以處理類似這樣的常見問題。
例如:
您可以找到許多使用該庫制作的回合制游戲的教程。
祝好運!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.