Mühle Objektorientiert

TurnType und getMoveFor sind eigentlich recht einfach. Ein Spieler (Player) bleibt ein Spieler - für alle Spieler gelten die selben Regeln, z.B. Punkteverteilung und Arsenal an Steinen, die er noch legen kann. Aber nicht jeder Spieler zieht gleich - der Erste (HUMAN) überlegt selbst, der zweite (KI) lässt einen Computer “überlegen” und der dritte (NETWORK) schließlich “befragt das Publikum” (:D) bzw. einen Spieler, der an einem anderen Rechner sitzt. All diese TurnTypes weisen unterschiedlich viel und sogar vollkommen unterschiedlichen Aufwand auf. HUMAN benötigt z.B. eine Eingabe-Maske, KI eine KI-Algorhitmus und NETWORK die Implementation eines Netzwerks. Sicher ist nicht einer dieser TurnTypes mit nur einer Methode abgehandelt - geschweige denn mit nur einer Klasse. Das Enum oder das Interface TurnTypes kann dann noch mal seinerseits zu festgelegten Paketen delegieren und das alles ohne das man auch nur eine einzige Kleinigkeit an der Player-Klasse ändern muss. Welche Vorteile das alles sonst noch so hat, ist mehr oder weniger uninteressant - interessant ist nur die Tatsache, dass der Code unabhängig vom Aufwand schön übersichtlich bleibt.

(Vielleicht sollte man erwähnen, dass die Frage, „wie sehr sich das lohnt“, auch davon abhängt, was ein „Player“ darüber hinaus noch ist. Bei

class Player {
    TurnType turnType;

    // That's it
}

würde das ganze etwas ad absurdum geführt :smiley: Aber es ist ja damit zu rechnen, dass noch sowas wie Farbe oder Anzahl Steine usw. dazukommt :slight_smile: )

[quote=FranzFerdinand]Aber um auf das eigentliche zurückzukommen: Muss ich dann nicht trotzdem eine ewige Auflistung von Nachbarzuordnungen machen, wie befürchtet? Dadurch hab ich mit schicker aussehendem Code (und effizienter) das gleiche Problem:[/quote]Dies Zuordnung musst Du immer machen.

Wenn Du mit einem Array als Spielfläche arbeitest ist diese Zuordnung zwar implizit, dafür müssen alle, die aktuelle Geometrie des Spielfeldes kennen.
Wenn Du die Spielfeldgeometrie über die “Nachbarschaft” der Felder erzeugst ist die tatsächliche Geometrie nur für die Initialisierung (wo die Nachbarn gesetzt werden) und die Auswertung (welche Züge sind erlaubt, wann ist ein Ereignis (Mühe) eingetreten…) interessant.

In um den Tippaufwand bei der Initialisierung zu verringern muss man eben etwas Hirnschmalz in eine clevere Logik stecken: (wie immer ungetestet…)```class InitFlatRegularBoard implements Ruleset.BoardGeometry {
privat final static int FIELD_COUNT 3*8; // 3 “Ebenen” je “Ebene” 4 Ecken und 4 Mittelfelder
@Override
public Collection initializeBoard(){
List gameBoard = new ArrayList<>();
for (int i =0; i< FIELD_COUNT; i++)
gameBoard.add(new Field());

 // in jeder Ebene ist innerhalb dieser das gleiche zu tun
 for (int i =0; i<     3; i++){
   // Ebenenfelder verbinden
   Direction d = Direction.EAST;
   for(int j =0; j<Direction.values().length; j++ ){
     Field corner = gameBoard.get(i*8);
     Field right =gameBoard.get(i*8+2*d.ordinal());
     Field left = gameBoard.get(i*8+2*((d.ordinal()+1)%Direction.values().length));
     corner.setNeigbour(d, right);
     right.setNeigbour(d.opposit(), corner);
     corner.setNeigbour(d.clockWise(), left);
     left.setNeigbour(d.counterClockWise(),corner);

    Field middle = gameBoard.get(i*8+1);
    Field nextCorner =  gameBoard.get(i*8+2);
    middle.setNeigbour(d, nextCorner);

    d= d.clockWise();
   }


}

// Mitten verbinden
// felder 9, 11, 13 und 15 sind die Mitten der mittleren Ebene
Direction d = Direction.EAST.clockWise();
for(int i =0; i<Direction.values().length; i++ ){
Field middleOuter = gameBoard.get(2i);
Field middleCenter = gameBoard.get(2
i+8);
Field middleInner = gameBoard.get(2*i+16);
middleCenter.setNeigbour(d, middleInner);
middleInner.setNeigbour(d.opposit(), middleCenter);
middleCenter.setNeigbour(d.opposit(), middleOuter);
middleOuter.setNeigbour(d, middleCenter);
d= d.clockWise():
}
return gameBoard;
}
}```

bye
TT

Hallöle,
ich danke nochmals für die Antworten.
Ich habe mal die Idee von @ionutbaiu aufgeschnappt und habe ein Feld gebaut, das drei Koordinaten hat. d, x und y, alle von 0 bis 2. d ist die Dimension, die außen 0 bis innen 2 meint (die Quadrate). Ansonsten hat jedes Quadrat von (0,0) bis (2,2) alle Koordinaten außer (1,1) in der Mitte.
Ich repräsentiere das so:

class Field {
    var pos : (Int, Int, Int)
    var neighbour = [(Int, Int, Int)]()//[Field]()
    
    init(d: Int, x: Int, y: Int) {
        pos = (d, x, y)
    }
}

Hier werden die Koordinaten wieder als dreifaches Tupel gespeichert. Die Nachbaren sind ein Array aus solchen Tupeln. Alternativ könnte man auch direkt einen Field-Array nehmen, wie rechts kommentiert. Ich hab nur gerade irgendwie ein Designproblem, wie ich in einem Array dann die Felder wieder finde, ohne eine for-Schleife durchgehen zu lassen, die Koordinaten abzufragen und dann bei der richtigen Koordinate und damit dem richtigen Feld Alarm zu schlagen …

Das Board wird dann folgende Inhalte haben:

var boardArr = [Field]()
    
    init() {
        for d in 0...2 {
            for x in 0...2 {
                for y in 0...2 {
                    if !(x==1 && y==1) {
                        boardArr.append(Field(d: d, x: x, y: y))
                    }
                }
            }
        }
    }

Der Board-Array hat alle 24 Felder, die in drei for-Schleifen zusammengebaut werden, wobei natürlich x==1&&y==1 nicht denkbar ist.

Nun kann ich eine Methode addNeighbour oder ähnliches bauen und die hinzufügen. Ich bin nur noch gerade am Basteln des effektivsten Algorithmus dafür, der am Ende nicht länger aussieht als das Original mit manuellen Zuweisungen.

Vielleicht hab ich in 1 bis 2 Tagen schon einen Prototypen davon zusammen gebaut.

(Ideen und Vorschläge gerne als Javacode, Swift ist wie gesagt nicht relevant,…)

Ganz ohne manuelle Zuweisungen wird es nicht gehen. Schließlich ist die Festlegung, wer wessen Nachbar ist eine semantische. Die kann nur von einem Menschen kommen (zumindest auf absehbare Zeit).

Wenn man sich aber klar macht, dass das Board achsialsymetrisch ist und die Zuweisungen innerhalb der Ringe für jeden Ring gleich sind braucht man nur noch 1/12-tel der Zuweisungen + die 8 Verbindungen zwischen den Ebenen selbst zu machen und auch letzter kann man in eine Schleife mit 2 Zuweisungen legen.

BTW: mein Post #24 gesehen?

bye
TT

[QUOTE=Timothy_Truckle;138029]Ganz ohne manuelle Zuweisungen wird es nicht gehen. Schließlich ist die Festlegung, wer wessen Nachbar ist eine semantische. Die kann nur von einem Menschen kommen (zumindest auf absehbare Zeit).

TT[/QUOTE]

Ahrgh. Die Ecken der Quadrate sind nicht miteinander verbunden! Sonst könnte man da was bauen.

Doch, man muss eine Fall-Unterscheidung machen.

Wen man einen Punkt p nimmt, dann kann man alle anderen Punkte durchgehen.
Ist die Koordinate d geich p.d, dann muss sich eine und nur eine der anderen Koordinaten um abs(1) von p unterscheiden.

Unterscheidet sich die Koordinate d um abs(1) von p.d, dann müssen die anderen Koordinaten gleich sein und eine der anderen Koordinaten muss den Wert 1 haben.

Dass könnte man doch mit einfachen logischen Operatoren ausdrücken können um dann damit alle Positionen zu filtern.

Also… bei Mühle gibt es 24 Felder, die alle mehr oder weniger unterschiedlich miteinander verbunden sind. Ich wüsste nicht, dass es zum Verbinden der Felder lt. Spielregeln einen Algo gibt. Da bleibt dann wohl nur, die 24 Felder zu definieren und entsprechende Instanzen aus der Menge aller Felder händisch hinzuzufügen.

01       02       03
   09    10    11
      17 18 19
08 16 24    20 12 04
      23 22 21
   15    14    13
07       06       05

01 -> 02, 08
02 -> 01, 03, 10
03 -> 02, 04
04 -> 03, 05, 12

Hab da mal ein Beispiel gemacht wie man da Nachbarn finden kann.



import java.util.ArrayList;
import java.util.List;

/**
 * Created by ionutbaiu on 19.08.16.
 */
public class Field {
    private final int d, x, y;

    public Field(int d, int x, int y) {
        this.d = d;
        this.x = x;
        this.y = y;
    }

    public int getD() {
        return d;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Field field = (Field) o;

        if (d != field.d) return false;
        if (x != field.x) return false;
        return y == field.y;

    }

    @Override
    public int hashCode() {
        int result = d;
        result = 31 * result + x;
        result = 31 * result + y;
        return result;
    }

    @Override
    public String toString() {
        return "mill.Field{" +
                "d=" + d +
                ", x=" + x +
                ", y=" + y +
                '}';
    }

    public boolean isNeighbour(Field n) {
        return !this.equals(n)
                &&
                ((d == n.d &&
                        ((Math.abs(x - n.x) == 1 && Math.abs(y - n.y) == 0)
                                || (Math.abs(x - n.x) == 0 && Math.abs(y - n.y) == 1)))
                        ||
                        (Math.abs(d - n.d) == 1
                                && x == n.x && y == n.y && (x == 1 || y == 1)));


    }


    public static void main(String[] args) {
        List<Field> fieldList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                for (int k = 0; k < 3; k++) {
                    if (!(j == 1 && k == 1)) {
                        fieldList.add(new Field(i, j, k));
                    }
                }
            }
        }

        fieldList.stream().forEach(System.out::println);

        fieldList.stream().forEach(f -> {
            System.out.println("Neighbours from " + f);
            fieldList.stream().filter(n -> f.isNeighbour(n)).forEach(System.out::println);
        });
    }


}

Aber händisch fest verdrahten ist auch kein größerer Spaß.

[QUOTE=ionutbaiu]Hab da mal ein Beispiel gemacht wie man da Nachbarn finden kann.



import java.util.ArrayList;
import java.util.List;

/**
 * Created by ionutbaiu on 19.08.16.
 */
public class Field {
    private final int d, x, y;

    public Field(int d, int x, int y) {
        this.d = d;
        this.x = x;
        this.y = y;
    }

    public int getD() {
        return d;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Field field = (Field) o;

        if (d != field.d) return false;
        if (x != field.x) return false;
        return y == field.y;

    }

    @Override
    public int hashCode() {
        int result = d;
        result = 31 * result + x;
        result = 31 * result + y;
        return result;
    }

    @Override
    public String toString() {
        return "mill.Field{" +
                "d=" + d +
                ", x=" + x +
                ", y=" + y +
                '}';
    }

    public boolean isNeighbour(Field n) {
        return !this.equals(n)
                &&
                ((d == n.d &&
                        ((Math.abs(x - n.x) == 1 && Math.abs(y - n.y) == 0)
                                || (Math.abs(x - n.x) == 0 && Math.abs(y - n.y) == 1)))
                        ||
                        (Math.abs(d - n.d) == 1
                                && x == n.x && y == n.y && (x == 1 || y == 1)));


    }


    public static void main(String[] args) {
        List<Field> fieldList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                for (int k = 0; k < 3; k++) {
                    if (!(j == 1 && k == 1)) {
                        fieldList.add(new Field(i, j, k));
                    }
                }
            }
        }

        fieldList.stream().forEach(System.out::println);

        fieldList.stream().forEach(f -> {
            System.out.println("Neighbours from " + f);
            fieldList.stream().filter(n -> f.isNeighbour(n)).forEach(System.out::println);
        });
    }


}

Aber händisch fest verdrahten ist auch kein größerer Spaß.[/QUOTE]

Hallöle,
ah da bist Du mir zuvor gekommen. Ich hab die gleiche Lösung gerade für Swift versucht zu lösen.

Es ist recht kompliziert und sollte einfacher gehen. Die folgende Lösung umfasst jetzt alle außer die vier Mittelfelder die noch ihre Querverbindungen haben, die kann man ja dann noch manuell machen.

//: Playground - noun: a place where people can play

import Cocoa

class Board {
    var boardDict = [Pos3D : Field]()
    
    init() {
        for d in 0...2 {
            for x in 0...2 {
                for y in 0...2 {
                    if !(x==1 && y==1) {
                        print("\(d)-\(x)-\(y)")
                        boardDict[Pos3D(d,x,y)] = Field(d,x,y)
                        //boardArr.append(Field(d: d, x: x, y: y))
                    }
                }
            }
        }
        
        for fieldA in boardDict {
                for fieldB in boardDict {
                    if fieldA.key != fieldB.key {
                        if fieldA.key.d == fieldB.key.d && fieldA.key.x == fieldB.key.x && abs(fieldA.key.y - fieldB.key.y)==1 {
                            fieldA.value.neighbours.append(fieldB.value)
                        }
                        if fieldA.key.d == fieldB.key.d && fieldA.key.y == fieldB.key.y && abs(fieldA.key.x - fieldB.key.x)==1 {
                            fieldA.value.neighbours.append(fieldB.value)
                        }
                    }
                }
            }
        
        for a in boardDict.values { //Zur Überprüfung eine Ausgabe
            print("(\(a.pos.d)|\(a.pos.x)|\(a.pos.y))-Neighbours: ", terminator:"")
            for b in a.neighbours {
                print("(\(b.pos.d)|\(b.pos.x)|\(b.pos.y))", terminator:"")
            }
            print("")
        }
        
    }
}

class Pos3D : Hashable {
    let d : Int
    let x : Int
    let y : Int
    var hashValue: Int {
        return 100*self.d+10*self.x+self.y
    }
    
    init(_ d: Int, _ x: Int, _ y: Int) {
        self.d = d
        self.x = x
        self.y = y
    }
}

func ==(lhs: Pos3D, rhs: Pos3D) -> Bool {
    return 100*lhs.d+10*lhs.x+lhs.y == 100*rhs.d+10*rhs.x+rhs.y
}

class Field {
    var pos : Pos3D
    var neighbours = [Field]()//[Field]()
    
    init(_ d: Int, _ x: Int, _ y: Int) {
        pos = Pos3D(d, x, y)
    }
}

Board()

Es tut mir leid, dass ich es gerade nicht erklären kann, da ich in Zeitnot geraten bin und schon in einer Stunde im Urlaub bin. :wink: Letzte Aufgabe davor. Ich erkläre es, wenn es nicht sofort verstanden wird danach. Habs auch noch nicht getestet.

Schöne Grüße
FranzFerdinand

Der Thread kann natürlich weiter für Diskussionen zu allem, was ich aufgeworfen habe genutzt werden! :smiley:

Was macht ihr denn da?


import java.util.HashSet;
import java.util.Set;

class Field {
	private static final Field[] BOARD;

	static {
		Field[] board = new Field[24];
		for(int n = 0; n < board.length; n++) {
			board[n] = new Field();
		}
		board[0].setNeigbours(board[1], board[7]);
		board[1].setNeigbours(board[0], board[2], board[9]);
		board[2].setNeigbours(board[1], board[3]);
		board[3].setNeigbours(board[2], board[4], board[11]);
		board[4].setNeigbours(board[3], board[5]);
		board[5].setNeigbours(board[4], board[6], board[13]);
		board[6].setNeigbours(board[5], board[7]);
		board[7].setNeigbours(board[0], board[6], board[15]);
		board[8].setNeigbours(board[9], board[15]);
		board[9].setNeigbours(board[1], board[8], board[10], board[18]);
		board[10].setNeigbours(board[9], board[11]);
		board[11].setNeigbours(board[3], board[10], board[12], board[19]);
		board[12].setNeigbours(board[11], board[13]);
		board[13].setNeigbours(board[5], board[12], board[14], board[21]);
		board[14].setNeigbours(board[13], board[15]);
		board[15].setNeigbours(board[7], board[8], board[14], board[23]);
		board[16].setNeigbours(board[17], board[23]);
		board[17].setNeigbours(board[9], board[16], board[18]);
		board[18].setNeigbours(board[17], board[19]);
		board[19].setNeigbours(board[11], board[18], board[20]);
		board[20].setNeigbours(board[19], board[21]);
		board[21].setNeigbours(board[13], board[20], board[22]);
		board[22].setNeigbours(board[21], board[23]);
		board[23].setNeigbours(board[15], board[16], board[22]);
		BOARD = board;
	}

	private final Set<Field> neighbours = new HashSet<Field>();

	private void setNeigbours(Field... fields) {
		for(Field f : fields) {
			neighbours.add(f);
		}
	}

	boolean isNeighbour(Field f) {
		return neighbours.contains(f);
	}

	public static Field[] getBoard() {
		return BOARD.clone();
	}
}```Knapp 60 Zeilen, na bitte. Aber nicht bloß mit C+P übernehmen... "Board"-Krams gehört in eine eigene Klasse, die evtl. über eine Methode getFields() verfügt. ;)

P.S.: Felder können natürlich auch mit Koordinaten zur grafischen Anordnung initalisiert werden... keine Frage.

Ja, da war schon ein bißchen bikeshedding in den letzten Beiträgen :wink: (Aber zugegeben: So ein bißchen „gefuddel“, um irgendeine kompakte Repräsentation zu finden (unabhängig davon ob sie sinnvoll ist), macht ja auch Spaß :smiley: ). Ich denke, ein 7x7-Array wäre schon OK, aber die Nachbarschaften können schon so ausgeschrieben werden.

EDIT: Nur für den Fall, dass sich jemand gefragt hat, was ich mit „Gefuddel“ meinte:

    static boolean areNeighbors(int r0, int c0, int r1, int c1)
    {
        return (r0==r1) ? Math.abs(c0 - c1) == Math.max(1, Math.abs(r0 - 3)) : 
            (c0 == c1) && Math.abs(r0 - r1) == Math.max(1, Math.abs(c0 - 3));
    }

:cool: :o) :sick: