Tic-Tac-Toe in Java (Tic-Tac-Toe in Java), Lektion, Seite 723141
https://www.purl.org/stefan_ram/pub/tictactoe_java (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram

Tic-Tac-Toe  in Java

Dieses ist ein unfertiges Programm, welches gerade die wesentlichen Funktionen zeigt, aber ohne eine schöne Benutzeroberfläche zu haben.

Das Erstellen des Programms hat zirka 5 Stunden gedauert, davon zirka 2 Stunden für die Suche eines Fehlers in der KI.

Gestaltungsprinzipien waren

Mögliche Übungsaufgaben sind:

Main.java

/* begin model */

class Player

{ public static Player O = new Player( -1 );

public static Player X = new Player( 1 );

protected int state;

public Player( final int state ){ this.state = state; }

public Player( final char symbol ){ this.state = symbol == 'O' ? -1 : +1; }

protected Player(){ this.state = 0; }

public int value() { return state; }

public Player other(){ return state == -1 ? X : O; }

public static Player valueOf( final int state ){ return state == -1 ? O : X; }

public static Player valueOf( final char symbol ){ return symbol == 'O' ? O : X; }

public char symbol(){ return state == -1 ? 'O' : 'X'; }}

class Square extends Player

{ public static Square O = new Square( -1 );

public static Square X = new Square( 1 );

public static Square Z = new Square( 0 );

private Square( final int state ){ this.state = state; }

public char symbol(){ return state == 0 ? '#' : super.symbol(); }

public void print(){ java.lang.System.out.print( symbol() ); }

public static Player valueOf( final Player player )

{ return player.value() == -1 ? O : player.value() == 1 ? X : Z; }}

class Result extends Player

{ public static Result tie = new Result( 0 );

private Result( final int state ){ this.state = state; }}

class Winner

{ Player player;

int row;

int col;

public Winner( final Player player, final int row, final int col )

{ this.player = player; this.row = row; this.col = col; }}

class Evaluation

{ double value;

Winner winner;

boolean running;

Board board;

public Evaluation( final double value, final Winner winner, final boolean running, final Board board )

{ this.value = value; this.winner = winner; this.running = running; this.board = board; }}

class Board

{ Player[] state =

{ Square.Z, Square.Z, Square.Z,

Square.Z, Square.Z, Square.Z,

Square.Z, Square.Z, Square.Z };

static private boolean valid( final int value )

{ return value >= 0 && value <3; }

static private boolean valid( final int row, final int col )

{ return valid( row )&& valid( col ); }

public Square get( final int row, final int col )

{ return valid( row, col )?( Square )state[ row * 3 + col ]:Square.Z; }

public void put( final int row, final int col, final Player player )

{ assert row >= 0: "row > 0";

assert row < 3: "row <= 3";

assert col >= 0: "col > 0";

assert col < 3: "col <= 3";

assert state[ row * 3 + col ]== Square.Z: "state[ row * 3 + col ]== Square.Z";

state[ row * 3 + col ]= Square.valueOf( player ); }

public int sum( final int row, final int col, final int dr, final int dc )

{ int sum =

get( row + 0 * dr, col + 0 * dc ).value() +

get( row + 1 * dr, col + 1 * dc ).value() +

get( row + 2 * dr, col + 2 * dc ).value();

return sum; }

public Winner winner()

{ for( int r = 0; r < 3; ++r )for( int c = 0; c < 3; ++c )

for( int dr = -1; dr < 2; ++dr )for( int dc = -1; dc < 2; ++dc )

if( Math.abs( dr )+Math.abs( dc ) != 0 && Math.abs( sum( r, c, dr, dc ))> 2 )

{ return new Winner( get( r, c ), r, c ); }

return null; }

public boolean full()

{ for( int r = 0; r < 3; ++r )for( int c = 0; c < 3; ++c )

if( get( r, c )== Square.Z )return false;

return true; }

public void print()

{ for( int r = 0; r < 3; ++r )

{ for( int c = 0; c < 3; ++c )get( r, c ).print();

java.lang.System.out.println(); }}

public Board(){}

public Board( final Board other )

{ java.lang.System.arraycopy( other.state, 0, this.state, 0, 9 ); }}

/* end model */

public final class Main extends javafx.application.Application

{

/* begin model */

public static Evaluation findworst( final java.util.List<Evaluation> list )

{ assert list.size() > 0: "list.size() > 0";

double min = java.lang.Double.POSITIVE_INFINITY;

final java.util.List<Evaluation> worst = new java.util.ArrayList<Evaluation>();

for( Evaluation e : list )if( e.value < min )min = e.value;

for( Evaluation e : list )if( e.value == min )worst.add( e );

assert worst.size() > 0: "worst.size() > 0";

Evaluation b = worst.get( ( int )( java.lang.Math.random() * worst.size() ));

return b; }

public static Evaluation playonce( Player p, Board b )

{ final Evaluation result;

final Winner winner = b.winner();

if( winner != null )

{ final Player player = winner.player;

if( player.value() == p.value() )

result = new Evaluation( 1, winner, false, b );

else

result = new Evaluation( -1, winner, false, b ); }

else if( b.full() )

{ result = new Evaluation( 0, null, false, b ); }

else

{ final java.util.List<Evaluation> list = new java.util.ArrayList<Evaluation>();

final Player o = p.other();

for( int r = 0; r < 3; ++r )for( int c = 0; c < 3; ++c )

if( b.get( r, c )== Square.Z )

{ final Board bb = new Board( b );

bb.put( r, c, p );

final Evaluation e = playonce( o, bb ); /* change POV */

e.running = true; e.board = bb; list.add( e ); }

final Evaluation worst = findworst( list );

final Evaluation best = worst; best.value = -worst.value; /* change POV */

result = best; }

return result; }

/* end model */

/* begin UI */

final javafx.scene.layout.GridPane gridPane = new javafx.scene.layout.GridPane();

private javafx.beans.property.DoubleProperty fontSize = new javafx.beans.property.SimpleDoubleProperty(10);

javafx.scene.Scene scene ;

void button( int row, int col )

{ final javafx.scene.control.Button button = new javafx.scene.control.Button( " " );

button.setMinSize( 1, 1 ); /* Unclamp the max size of for building resizable gui */

button.setMaxSize( java.lang.Double.MAX_VALUE, java.lang.Double.MAX_VALUE ); /* Unclamp the max size of for building resizable gui */

//button.setPrefSize(java.lang.Double.MAX_VALUE, java.lang.Double.MAX_VALUE);

fontSize.bind( scene.widthProperty().add( scene.heightProperty() ).divide( 20 ) );

java.lang.System.out.println( "fontSize.asString() = " + fontSize.asString() );

button.styleProperty().bind( javafx.beans.binding.Bindings.concat

( "-fx-font-size: ", fontSize.asString(), ";", "-fx-font-family: ", "'Consolas', monospace", ";" ));

button.setOnAction( this::handler );

gridPane.add( button, col, row );

}

@java.lang.Override

public void start( final javafx.stage.Stage stage )

{ try

{ scene = new javafx.scene.Scene( gridPane );

for( int row = 0; row < 3; ++row )for( int col = 0; col < 3; ++col )button( row, col );

button( 0, 3 );

gridPane.setMinSize( 1, 1 ); /* Unclamp the max size for resizable gui */

gridPane.setMaxSize( java.lang.Double.MAX_VALUE, java.lang.Double.MAX_VALUE ); /* Unclamp the max size for resizable gui */

stage.setScene( scene );

final javafx.geometry.Rectangle2D bounds = javafx.stage.Screen.getPrimary().getVisualBounds();

stage.setWidth(bounds.getWidth()/2);

stage.setHeight(bounds.getHeight()/2);

stage.setX(bounds.getMinX()+bounds.getWidth()/4);

stage.setY(bounds.getMinY()+bounds.getHeight()/4);

stage.show();

stage.show(); }

catch(Exception e)

{ e.printStackTrace(); }}

/* end UI */

/* begin mixed */

final Player X = Player.valueOf( 'X' );

final Player O = Player.valueOf( 'O' );

Player p = O;

public void printdiag( final Player p, final Board b, final Evaluation evaluation )

{ if( !evaluation.running )

{ java.lang.System.out.println

( evaluation.value == 1 ? "Player " + p.symbol() + " won. " :

evaluation.value == -1 ? "Player " + p.other().symbol() + " won. " :

evaluation.value == 0 ? "Tie. " : "" );

java.lang.System.out.println();

b.print(); }}

public void handler( final javafx.event.ActionEvent evt )

{

javafx.scene.control.Button clickedButton =( javafx.scene.control.Button )evt.getTarget();

clickedButton.setText( "X" );

javafx.collections.ObservableList<javafx.scene.Node> list = gridPane.getChildren();

/* copy from the gridPane to the board */

Board b = new Board(); int row = 0; int col = 0;

for( final javafx.scene.Node node : list )

{ javafx.scene.control.Button button = ( javafx.scene.control.Button )node;

b.put( row, col++, button.getText().equals( "X" )? X : button.getText().equals( "O" )? O : Square.Z );

if( col == 3 && row == 2 )break;

if( col == 3 ){ row++; col = 0; }}

java.lang.System.out.println();

b.print();

java.lang.System.out.println();

/* calculate next board */

p = X;

Evaluation evaluation = null;

evaluation = playonce( X, b ); /* just detect win/loss/tie */ printdiag( X, b, evaluation );

//printdiag( b, evaluation );

evaluation = playonce( O, b );

b = evaluation.board;

b.print();

evaluation = playonce( O, b ); /* just detect win/loss/tie */ printdiag( O, b, evaluation );

/* copy from the board to the gridPane */

row = 0; col = 0;

for( final javafx.scene.Node node : list )

{ javafx.scene.control.Button button = ( javafx.scene.control.Button )node;

final char ch = b.get( row, col++ ).symbol();

button.setText( "" +( ch == '#' ? ' ' : ch ));

if( col == 3 ){ row++; col = 0; }}}}

Usenet-Posting 20160409204458+0100

From: ram@zedat.fu-berlin.de (Stefan Ram)

Die Rekursion findet in meiner KI bei »playonce« statt, das

versuchsweise einen Zug macht, und dann »playonce« aufruft,

um zu evaluieren, wie gut dieser ist. Die Rekursion endet

bei mir, wenn ein Gewinner feststeht (»winner != null«) oder

kein Platz auf dem Spielbrett mehr frei ist (»b.full()«).

Dabei wird bei jeder neuen Rekursionstiefe von »playonce«

die Perspektive gewechselt, um auch die Züge des Gegners zu

spielen. Daher ist es in dem untenstehenden Pseudocode

nötig das /Negative/ der Bewertung des nächsten Zuges

zu verwenden, weil diese aus der Sicht des /Gegners/ erfolgt.

Dies könnte man als einen »brute-force« Algorithmus

klassifizieren, der alle Spielmöglichkeiten des Spielers

und des Gegners durchspielt, um dann alle Stellungen

bewerten zu können.

Mein Rekursionsschema sieht also so aus:

Bewerte einen Zug:

wenn kein Zug mehr moeglich, dann

Ergebnis = Bewertung der jetzigen Stellung

sonst

für alle Möglichkeiten für den nächsten Zug:

mache diesen nächsten Zug versuchsweise

Ermittle seine Bewertung (durch Rekursion)

Ergebnis = das Negative der Zusammenfassung der Bewertungen

Dabei ist es für die Beendigung der Rekursion wichtig,

daß es einen Spielzustand gibt, bei dem kein weiterer

Zug mehr möglich ist, dieser nach endlich vielen Zügen

erreicht wird, und jede neue Rekursionstiefe einen

Schritt in die Richtung zu jenem Endzustand geht.

Man muß sich auch überlegen, welche Variablen für mehrere

Rekursionstiefen geteilt werden können und welche bei

jeder Rekursion neu anzulegen sind. Im Zweifelsfalle

sollte man alle veränderbaren Zustände bei jeder Rekursion,

bei der sie verändert werden könnten, erst einmal

als Kopie der vorigen Zustände neu anlegen und dann am

Ende des Rekursionsschritts sicherstellen, daß der

vorige Zustand wiederhergestellt ist.

Nun, dieser Code von mir ist bei weitem nicht perfekt,

ich habe ihn ja damals in kurzer Zeit geschrieben und

ich habe noch eine große Aufgabenliste, was ich daran

noch alles verbessern will, wozu ich aber noch nicht

gekommen bin.

Aussprachehinweise
super ˈsupɚ (sd)

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram723141 stefan_ram:723141 Tic-Tac-Toe in Java Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd723141, slrprddef723141, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram.
https://www.purl.org/stefan_ram/pub/tictactoe_java