Actuellement, je travaille sur une IA pour un jeu simple au tour par tour. La façon dont je suis le jeu mis en place est la suivante (en pseudo-code):Boucle de jeu d'un jeu au tour par tour
players = [User, AI];
(for player : players){
player.addEventlistener(MoveListener (moveData)->move(moveData));
}
players[game.getTurn()].startTurn();
la fonction move
:
move(data){
game.doStuff(data);
if(game.isOver())
return;
game.nextTurn();
players[game.getTurn()].startTurn();
}
Il en résulte la récursion suivante:
- début tourner
- joueur/AI fait un mouvement
- la fonction de déplacement est appelée
- le joueur suivant commence leur tour
- ...
Ce répète jusqu'à ce que le jeu est terminé - noter que le jeu est de longueur finie et ne va pas au-delà ~ 50 coups. Maintenant, même si la récursivité est finie, j'obtiens une erreur stackoverflow. Ma question est la suivante: y a-t-il un moyen de régler cela? Y a-t-il quelque chose qui ne va pas avec la récursion? Ou devrais-je implémenter une boucle de jeu à la place? Je comprends comment cela fonctionnerait si AI
s devaient jouer les uns contre les autres, mais comment cela fonctionnerait-il si le programme devait attendre l'entrée de l'utilisateur?
EDIT
Voici les classes appropriées à la récursion:
Connect4
Classe:
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
classe: package 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
Classe:
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);
}
}
}
La AI
classe est fondamentalement la même que celle d'un dépouillé classe UserControlled
, à l'exception de la méthode startTurn
:
int[] columns = game.getAvailableColumns();
move(columns[rng.nextInt(columns.length)]);
L'interface MoveListener
est très simple:
public interface MoveListener {
void onMove(int i);
}
La trace de la pile:
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 (! GameOver) {waitForUserInput(); actAccordingToInput(); makeAIMove(); } ' – luk2302
Cela dépend toujours de ce qui se trouve sur votre pile. Si vous avez plusieurs ou plusieurs objets locaux qui sont alloués en pile, même quelques dizaines de niveaux de récursion peuvent déjà être trop importants. Comme @ luk2302 suggéré, le rendre itératif. Vous devriez toujours choisir la solution itérative, si la profondeur de récursivité n'est pas plafonnée à une limite suffisamment basse. – Ext3h
@ Ext3h la chose est, je n'ai même pas commencé avec le 'AI' lui-même (à partir de maintenant il ne fait que des mouvements aléatoires), le programme entier n'est même pas 400 lignes de code dont la moitié n'est pas liée. Plutôt que d'augmenter la taille de la pile, je voudrais résoudre le problème d'une manière propre afin qu'il ne revienne pas et me mordre – Jonan