0
\$\begingroup\$

I have a GUI game, which is driven by user every time it clicks the mouse. Every time user clicks a square on a board, the board state is updated (we re-compute the score, the player to make next move and legal movements it can make) and repainted. Both mouse click, state recomputation and painting are handled in the GUI thread. Now, suppose that I want to train AI to play without GUI. That is, game engine should consume next move by simply calling AI's makeMove function in one thread. This would allow to play millions of games per second automatically. GUI may just screenshot some arbitrary states time after time. How do you switch to this strategy?

\$\endgroup\$
2
  • 1
    \$\begingroup\$ It is very difficult to answer this question as the answers it would produce would be too broad. I would suggest making your question more specific. Maybe post some code that you are targeting with this GUI/AI Update. What have you tried already? \$\endgroup\$ May 2, 2014 at 12:20
  • \$\begingroup\$ I have noticed that the same question has appeared in stackoverflow.com/questions/23802168 \$\endgroup\$
    – Val
    May 22, 2014 at 12:37

2 Answers 2

3
\$\begingroup\$

You should try to refactor your game to separate the Model (the abstract state of the game) from its View (the graphical representation of the game-state) and its Controllers (anything which can interact with the game). This architecture is usually referred to as the Model - View - Controller Pattern.

Currently, your view would be the graphical user interface, which parses the model and visualizes its state to the user. An additonal view of the AI wouldn't need a graphical representation. It would just parse the model directly.

Your current controller is also the graphical user interface. When you have a naive implementation, it might manipulate the model directly. This isn't a clear separation of concerns. In order to modify the game-state, it should call methods on the model. The methods of the model shouldn't refer to the GUI (as in skipButtonPressed()) but rather to the intention the real/virtual player wants to perform (skipTurn()).

This would enable you to replace the GUI-controller with an AI-controller which also calls methods directly on the model when it wants to do something. The model itself shouldn't know (or care) whether the method-call comes from a GUI, from an AI, a network interface or whatever.

\$\endgroup\$
2
  • \$\begingroup\$ Ok, I see that you propose a low-level controller driving the game instead of game (engine) polling the controllers. Could you propose mapping of the design onto the computer threads? \$\endgroup\$
    – Val
    May 2, 2014 at 14:01
  • \$\begingroup\$ @Val Theoretically each model, view and controller could operate in their own thread. But make sure you use the appropriate synchronization features to prevent race conditions. \$\endgroup\$
    – Philipp
    May 2, 2014 at 14:05
0
\$\begingroup\$

The Philip's proposal to separate design into game/view/controller seems plausible. He however proposes muliple threads, which seems an overkill. I have discovered that using secondary loops permits to avoid unnecessary multithreading (I see no useful jobs those threads could do -- I am discussing a linear flow of control).

We design a game model, and start a game from our main thread

void play() {
    initializeSquaresAndPlayers()
    while (true) {
        foreach(player) if wins(player) {
            updateStatus(player + " wins", true)
            break
        }
        updateStatus(status(), false);
        Point square = move(); // request user choice
        if (square == null) {
            updateStatus("current game terminated", true);
            return; 
        }
        go(square); // update game state
    }
}

Game.play loops requesting user move until somebody wins or game is terminated by null response (that is useful if user wants to restart the game). The Controller/Viewer must implement two methods

abstract Point move();
abstract void updateStatus(Stirng status, boolean gameOver);

They are strightforward in case of console game (I used random square for move and printed status into console, ignoring gameOver). The gui is more complex. At first, you start the game.play loop by (that is my applet)

public void init () {
    createControls();
    restart();
}

// restart is called every time user presses "New Game" button
private void restart() {
    setControlsEnabled(true);
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            game.play();
        }
    });
}

The game.play call is deferred until later for the Applet's main method, init(), could have chance to finish.

Here is implementation of two-phase move (and presentation of status, by the way)

class GuiGame extends Game {
    Point move() {
            secondaryLoop =  
                    Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();  
            secondaryLoop.enter(); // will block until loop.exit
            return response; // in Applet.move
}
    @Override
    void updateStatus(String status, boolean gameOver) {
        statusLabel.setText(status);
        canvas.setEnabled(!gameOver);
    }
}

class MyApplet extends Applet {

    void move(Point square) { // complete the move
        response = square;
        secondaryLoop.exit();
    }

    void init() { ...

You see that Game.move() is starts blocking until response is initialized (by Applet.move(response), which is called by user click). The clicks are handeled by control listeners, created in Applet.init()

public void mousePressed(MouseEvent arg0) {
    if (arg0.getSource()== newGame) {
        move(null); // terminate current game
        restart();
    }

    if (canvas.isEnabled() && arg0.getSource() == canvas) {
        move(new Point(canvas.getWidth(), canvas.getHeight()));
    }

}

You see that by clicking, we can either restart the game or respond. In either case we send response to the game engine, its play loop, by move(response) method that memorizes the response so that return below loop.enter could see.

The net effect is that we can have a console-style game engine, which initializes the game model and drives the game by a loop, which requests player's next move every iteration. Console applications compute the response immediately. GUI is blocking while pumping GUI message loop until human clicks a choice. Threads/synchronization are unnecessary indeed for such linear control flow. Thank you Java1.7 for streamlining the implementation using secondary message loop.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .