Inversión del conductor

Tengo un juego de GUI, que es manejado por el usuario cada vez que hace clic en el mouse. Cada vez que un usuario hace clic en un cuadrado en un tablero, el estado del tablero se actualiza (recalculamos el puntaje, el jugador para hacer el siguiente movimiento y los movimientos legales que puede hacer) y se vuelve a pintar. Tanto el clic del mouse, el recálculo del estado y la pintura se manejan en el hilo de la GUI. Ahora, supongamos que quiero entrenar a AI para jugar sin GUI. Es decir, el motor del juego debería consumir el próximo movimiento simplemente llamando a la function makeMove de AI en un hilo. Esto permitiría jugar millones de juegos por segundo automáticamente. La GUI puede simplemente capturar algunos estados arbitrarios una y otra vez. ¿Cómo cambias a esta estrategia?

Debería intentar refactorizar su juego para separar el Modelo (el estado abstracto del juego) de su Vista (la representación gráfica del estado del juego) y sus Controladores (cualquier cosa que pueda interactuar con el juego). Esta architecture se conoce generalmente como Modelo – Vista – Patrón de controller .

Actualmente, su vista sería la interfaz gráfica de usuario, que analiza el model y visualiza su estado para el usuario. Una vista adicional de la IA no necesitaría una representación gráfica. Simplemente analizaría el model directamente.

Su controller actual también es la interfaz gráfica de usuario. Cuando tienes una implementación ingenua, puede manipular el model directamente. Esta no es una clara separación de preocupaciones. Para modificar el estado del juego, debe llamar a los methods del model. Los methods del model no deben referirse a la GUI (como en skipButtonPressed() ) sino a la intención que el jugador real / virtual desea realizar ( skipTurn() ).

Esto le permitiría replace el controller GUI por un controller AI que también llama methods directamente en el model cuando quiere hacer algo. El model en sí no debería saber (ni preocuparse) si el método-llamada proviene de una GUI, de una IA, una interfaz de networking o lo que sea.

La propuesta de Philip de separar el layout en juego / vista / controller parece plausible. Sin embargo, propone múltiples hilos, lo que parece una exageración. Descubrí que el uso de loops secundarios permite evitar el multihilo innecesario (no veo trabajos útiles que esos hilos puedan hacer, estoy discutiendo un flujo de control lineal).

Diseñamos un model de juego y comenzamos un juego desde nuestro hilo principal

 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 Game.play solicitan que el usuario se mueva hasta que alguien gane o el juego finalice mediante una respuesta null (esto es útil si el usuario desea reiniciar el juego). El controller / visor debe implementar dos methods

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

Son muy exigentes en el caso de los juegos de console (utilicé un cuadrado random para move y el estado impreso en la console, ignorando GameOver). La GUI es más compleja. Al principio, comienzas el bucle game.play (es mi 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(); } }); } 

La llamada game.play se aplaza hasta más tarde para el método principal de Applet, init() , podría tener la oportunidad de finalizar.

Aquí está la implementación del movimiento en dos fases (y la presentación del estado, por cierto)

 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() { ... 

Verá que Game.move() comienza a bloquearse hasta que se inicializa la response (por Applet.move (respuesta), que se llama por clic del usuario). Los clics son manejados por los oyentes de control, creados en 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())); } } 

Usted ve que onclick, podemos reiniciar el juego o responder. En cualquier caso, enviamos una respuesta al motor del juego, su bucle de reproducción, por el método de move(response) que memoriza la respuesta para que el retorno debajo de loop.enter pueda ver.

El efecto neto es que podemos tener un motor de juego al estilo de la console, que inicializa el model del juego y conduce el juego por un bucle, que solicita el siguiente move del jugador en cada iteración. Las aplicaciones de console calculan la respuesta de inmediato. La GUI está bloqueando mientras se bombea el bucle de posts de la GUI hasta que el usuario click una opción. Los hilos / synchronization son innecesarios para dicho flujo de control lineal. Gracias Java1.7 por agilizar la implementación mediante el ciclo de post secundario.