2017-08-27 12 views
1

現在、私は単純なターンベースのゲームのために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(); 
    } 
    

    turn

  • player/AIが移動する
  • 移動機能が呼び出される
  • 次のプレイヤーは自分の順番
  • を開始...

ゲームオーバーになるまで、これが繰り返される - ゲームは有限の長さのものであり、〜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クラス:ここでEDIT

は再帰に関連するクラスです パッケージ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クラス:MoveListenerインターフェースは非常に簡単である

int[] columns = game.getAvailableColumns(); 
move(columns[rng.nextInt(columns.length)]); 

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クラスはstartTurn方法を除いて、基本的に剥ぎ取らUserControlledクラスと同じです

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 
+1

while(!gameOver){waitForUserInput(); actAccordingToInput(); makeAIMove(); } ' – luk2302

+0

これは常にあなたのスタックにあるものによって決まります。多くのまたは巨大なローカルオブジェクトがスタックに割り当てられている場合、数十回の再帰レベルですらあまりにも多すぎる可能性があります。 @ luk2302が示唆するように、反復的にしてください。再帰の深さが十分に低い限界で制限されていない場合は、反復解を常に選択する必要があります。 – Ext3h

+0

@ Ext3h事は、私はAI自身で始まったことさえありません(今のところ、ランダムな動きをしています)、プログラム全体では400行のうち半分が関連していないコードもありません。スタックサイズを増やすのではなく、問題を解決して元に戻りません。 – Jonan

答えて

1

、あなたはあなたが何をしているかについてかなり確信している以外、再帰を使用しないでください。

これを考えると、次のステップを呼び出すたびに、すべてのローカル変数がスタックに保存されます。ゲームでは、たくさんのものになる可能性があります。

共通ゲームループターンベースのゲームのようなものになるだろうに:それはすべてのコンテキストを保存する必要があり、それは時間がかかるので

while(!gameFinished()){ 
    for(player in players){ 
     player.doTurn(); 
    } 
} 

は、再帰が遅いこと、あまりにも考慮してください、そう、通常、再帰を使用する前に3回考えます。あなたはそれがどのように機能するかを見つけることができるここで

CompletableFuture.supplyAsync(this::waitUserInput) 
      .thenAccept(this::processUserInput) 

:あなたはこのようなものを使用することができ、入力プロセスに

EDIT

これにより

http://www.deadcoderising.com/java8-writing-asynchronous-code-with-completablefuture/

を、あなたのコードを保持します実行しているので、次のコード行では入力がないことに注意してください。入力を取得すると、proccessUserInputメソッドが呼び出されます。

これを行う別の方法は、キーが押されたかどうかをすべてのフレームで確認することです。これも問題ありません。

ここであなたがそれを行うための方法を見つけることができます。

How do I check if the user is pressing a key?

あなたは物事を行うべき方法は、プロジェクトのサイズによって異なります。あなたがキープレスを常時チェックしているのであれば、何らかのイベントシステムを構築することをお勧めします。

一方、私はUnrealやUnityのようなゲームエンジンを使用することをお勧めします。 Javaを使い続けるには、このような一般的な問題の多くを処理するゲーム用ライブラリがたくさんあります。例えば

https://www.lwjgl.org/

あなたがそのライブラリで作られたターンベースのゲームのチュートリアルをたくさん見つけることができます。

幸運を祈る!

+0

ループを使用して、プレイヤーがAIでない場合はボタンを押す)? – Jonan

+0

まず、少なくとも2つのスレッドが必要です.1つはすべてのグラフィックスを処理するため、常に実行され、もう1つのスレッドは実際のゲームコードを持つことができます。 したがって、ユーザ入力を待っている間にそのスレッドをフリーズすることができます。おそらくタイムアウトを設定します。 Javaでは、コンソールのユーザー入力を待つスキャナがあります。そのようなものがあります。 コールバックのように想像してみてください。 – leoxs

+0

独自の入力処理システムを作成する場合、実際に複数のスレッドを持つ必要はありません。 「OnButtonReleased」や「ButtonHeld」を実装する必要がある場合は、各フレームのすべてのキーの状態をポーリングして保存します(以前の状態を保存することもできます)。 – UnholySheep

関連する問題