#pydesw: Programmierung eines Brettspiels

Du hast eine Idee für ein Projekt?

Welches Brettspiel soll programmiert werden?

Umfrage endete am Montag 20. Februar 2017, 16:25

Mühle
6
75%
Dame
2
25%
etwas anderes (bitte im Thread vorschlagen)
0
Keine Stimmen
 
Insgesamt abgegebene Stimmen: 8
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

ok denk dir die id einfach weg. :)
Und zu player.play hat da jemand eine Antwort für mich? Ist einfach game übergeben ok? Sonst müsste man ja verbotener weise global verwenden oder?
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ein Spieler könnte bereits bei seiner Erzeugung das Spiel, zu dem er gehört, übergeben bekommen. Die Rückgabe seiner play()-Methode wäre dabei der nächste Spielzug. Was hältst du von diesem Ansatz?
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

ok das ist doch dann"call bei Referenz" oder?
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
BlackJack

@Pygoscelis papua: Nein.
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Pygoscelis papua hat geschrieben: Wie kann man hier eigentlich einen Anhang anfügen?
Hier ist das Klassendiagramm, es muss aber noch verbessert werden:

...

Ich hoffe ihr habt noch viele Verbesserungsvorschläge :D
Klar kannst du ein Bild erstellen und hier hochladen, oder du benutzt direkt ein Tool, um Klassendiagramme online zu erstellen/teilen/verbessern, da gibt es sehr viele :mrgreen:
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Pygoscelis papua hat geschrieben:ok das ist doch dann"call bei Referenz" oder?
Call by Reference ist etwas ganz anderes. Ich dachte es mir so, dass die play()-Methode ein namedtuple liefert mit den Angaben "from" (bzw "from_") und "to". Ein noch nicht gesetzter Stein hätte keine Angabe bzw den Standardwert None für das from-Attribut. Diese Rückgabe könnte vom Aufrufer (also dem Spiel) entsprechend verarbeitet werden. Im Falle eines ungültigen Zuges erfolgt eine Rückmeldung durch das Spiel per Exception, die von der GUI visuell dargestellt wird. Der Spieler ist dann weiterhin an der Reihe und play() müsste erneut aufgerufen werden.
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

Ich meine wenn man am Anfang dem Spieler das Spiel übergibt,
dann wird ja wohl nicht das Spiel in dem Spieler gespeichert, sondern eher sozusagen ein "Zeiger", da wenn man es verändert (in einer Methode von Spieler), wird das Spiel, dass übergeben wurde ja auch z.B bei einem 2. Spieler geändert, dem das Spiel auch übergeben wurde oder?
Wie wird das Spiel denn sonst angesprochen, als über einen "Zeiger", wenn es nicht im Spieler gespeichert ist?
Das Call ist hier natürlich nicht wirklich angebracht, da es keine Funktion ist ...
@Kebap die Frage was nicht ob sondern wie ... :(
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
BlackJack

@Pygoscelis papua: Du bewegst Dich da auf der Ebene der Implementierung. Egal welchen Wert Du übergibst oder an ein Attribut bindest, es handelt sich bei Python immer um das selbe Objekt. Python kopiert bei Aufrufen und Zuweisung niemals Objekte/Werte. Das Aufrufmodell kann man als „call by object sharing“ bezeichnen. Namen und Attribute stehen in Python nicht für Speicher(adressen) wo man Werte ”rein tut”. Der Speicher gehört zum Wert/Objekt.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Die Übergabe des Spiels war eine erste Idee. Eigentlich muss nur das Spielfeld übergeben werden. Als Rückgabe dann wie gesagt ein Spielzug. Hierdurch müsste die play() Methode nichts vom Spiel wissen, sondern nur das Spielfeld mit seiner aktuellen Stellung auslesen können.

Denkbar wäre auch, einen GuiPlayer und einen KIPlayer von der Player Klasse abzuleiten, die als Schnittstelle nur Farbe, Name und die play() Methode haben.
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

@snafu: Könntest du das Diagramm vielleicht deinen Vorstellungen entsprechend ändern?
Ich habe jetzt dafür nicht so viel Zeit und außerdem weiß ich nicht wie du dir das genau vorstellst :)
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich fänd es erstmal ganz gut, wenn sich noch ein paar weitere Leute an der Skizzierung des Projekts beteiligen könnten. Bisher läuft das ja nur zwischen uns beiden ab.
sebastian0202
User
Beiträge: 168
Registriert: Montag 9. Mai 2016, 09:14
Wohnort: Berlin

Ich versuche es mal. Es ist glaube zu viel des Guten und vermutlich wieder so viel vermischt..
Ich habe es halb als Pseudo Python Code geschrieben :lol: grausam

Code: Alles auswählen


class Feld(feldtyp):
    feldtyp=feldtyp
    besitzer = 'Spiel'
    
    def besetzt():
        wenn besitzer != 'Spiel':
            return True
        return False
    
    def setze(spielername):
        besitzer=spielername
    
    def typ():
        return feldtyp
    
    def leere():
        besitzer='Spiel'
    
    def besitzer():
        return besitzer

class Rechteck():
    # beginnen oben-links, enden unten-rechts
    acht_felder = [Feld('Ecke'), Feld('Kreuzung'), ... , Feld('Ecke')]
    
    def besetzt(feldnummer):
        return acht_felder[feldnummer].besetzt()
    
    def setze_besitzer(feldnummer, spielername):
        acht_felder[feldnummer].setze(spielername)
    
    def loesche_besitzer(feldnummer):
        acht_felder[feldnummer].leere()
    
    def hole_besitzer(feldnummer):
        return acht_felder[feldnummer].besitzer()

class Spiellogik():
    spielfeld = { 
        'außen': Rechteck(),
        'mittig': Rechteck(),
        'innen': Rechteck()
    }
    
    selbiges_rechteck = ([1,2,3], [6,7,8], [1,4,6], [3,5,7])
    ungleiches_rechteck = ([2,2,2], [4,4,4], [5,5,5], [7,7,7])
        
    def ist_muehle(info):
        rechteck, nummer = unpack(info)
        
        fuer muehle in selbiges_rechteck:
            wenn nummer in muehle: 
                besitzer = set()
                fuer nummer in muehle:
                    besitzer.add(ermittle_spielstein_besitzer(rechteck, nummer))
                if len(besitzer) == 1:
                    return True
        
        fuer muehle in ungleiches_rechteck:
            wenn nummer in muehle:
                besitzer = set()
                for rechteck in ('außen', 'mittig', 'innen')
                    besitzer.add(ermittle_spielstein_besitzer(rechteck, nummer))
                if len(besitzer) == 1:
                    return True

        return False
    
    def ermittle_spielstein_besitzer(rechteck, nummer):
        return spielfeld[rechteck].hole_besitzer(nummer)


class Spielfeld(Spiellogik):
    
    def setze_spielstein(spielstein, nach, spielername):
        wenn spielfeld[nach['rechteck']].besetzt(nach['nummer']):
            return False
        ansonsten
            wenn spielstein.ist_gesetzt():
                info = spielstein.hole_information()
                spielfeld[info['rechteck']].loesche_besitzer(info['nummer'])
            spielfeld[nach['rechteck']].setze_besitzer(nach['nummer'], spielername)
            return True
    
    def loesche_feld(spielstein):
        info = spielstein.hole_information()
        wenn ist_muehle(info): return False
        ansonsten:
            spielfeld[info['rechteck']].loesche_besitzer(info['nummer'])
            spielstein.toete()
            return True

    def analysiere(info):
        wenn ist_muehle(info): return True
        ansonsten: return False


class Spielstein():
    gesetzt = False
    lebendig = True
    info = ['position':None, 'rechteck':None]
    
    def hole_information():
        return info
    
    def ist_gesetzt():
        return gesetzt

    def toete():
        lebendig = False


class Auswahl():
    nach = [rechteckID, feldID]
    def hole_rechteck(gui):
    def spielstein_waehlen(gui):
        return auswahl aus der gui


class Spieler():
    spielsteine = [8*spielstein()]
    spielername = 
    gui = Auswahl() (emit Funktion spaeter?)
    letzer_zug = None

    def zug(spielfeld):
        spielsteingesetzt = False
        solange bis spielsteingesetzt: 
            spielstein = gui.spielstein_waehlen()
            spielsteingesetzt = spielfeld.setze_spielstein(spielstein, gui.nach, spielername)
        
        letzer_zug = spielstein.hole_information()
    
    def anzahl_lebendiger_spielsteine():
        anzahl = 0
        fuer stein in spielstein:
            wenn stein.lebendig:
                anzahl + 1
        return anzahl

class Spiel():
    spieler = [Spieler(), Spieler()]
    spielfeld = Spielfeld()
    generiere_schicke_grafik()
    
    def mache_zug(spieler_am_zug):
        spieler[spieler_am_zug].zug(spielfeld)

    def werte_zug_aus(spieler_am_zug):
        info = spieler_am_zug.letzer_zug()
        darf_stein_loeschen = spielfeld.analysiere(info)
        
        wenn darf_stein_loeschen:
            spielstein = spieler_am_zug.gui.spielstein_waehlen()
            solange wie nicht wahr: 
                spielfeld.loesche_feld(spielstein)
    
    def spiel_vorbei():
        fuer gamer in spieler:
            wenn spieler.anzahl_lebendiger_spielsteine() == 0:
                return True
        return False

    def update():
        solange True:
            spieler_am_zug = spieler[0]
            spieler = spieler[::-1]
            mache_zug(spieler_am_zug)
            werte_zug_aus(spieler_am_zug)

            wenn spiel_vorbei(): break

Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

+100 für die Anstrengung und das mitarbeiten an der Ausarbeitung des Spiel-Plans :D
sebastian0202 hat geschrieben:Ich versuche es mal. Es ist glaube zu viel des Guten und vermutlich wieder so viel vermischt..
Ich habe es halb als Pseudo Python Code geschrieben :lol: grausam
Ich finde man hat hier einen recht schlechten Überblick,
es wäre wirklich übersichtlicher es als Bild darzustellen, ich weiß aber immer noch nicht wie man hier einen Anhang anfügt...
Und ich glaube ein Klassendiagramm soll noch nicht festlegen, wie die einzelnen Funktionen Implementiert werden, was es auch noch einmal
unübersichtlicher macht.
Ich finde es dennoch gut, dass du neues zu dem Diagramm beigetragen hast, ich habe nur jetzt nicht die Zeit alles genau durch zuschauen bzw. zu durchschauen.
Wenn es möglich wäre das als Bild irgendwie Darzustellen wäre es echt cool, vieleicht kann das bild einfach auf GitHub hochgeladen werden (in eine fork
von der repo zur Vorstellung des Projekts?) und dann hier verlinkt werden?
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Schonmal auf die Idee gekommen, dass man auch einfach einen Link zum Bild setzen könnte...? ;)

Den Code werde ich gleich mal genauer anschauen und vielleicht später eine überarbeitete Version hier zeigen.
BlackJack

Man könnte sich auch mal Werkzeuge zum erstellen von UML-Diagrammen aus Text anschauen. PlantUML wäre so ein Kandidat. UMLet habe ich auch schon mal verwendet.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier mal ein grober Ansatz wie ich das lösen würde:

Code: Alles auswählen

from collections import namedtuple
from itertools import cycle

Position = namedtuple('Position', 'square_name, index')

class Board(object):
    def __init__(self):
        self.squares = dict(
            (name, 9 * [None]) for name in ('inner', 'mid', 'outer')
        )

    def set_piece(self, position, color):
        square = self.squares[position.square_name]
        if square[position.index]:
            raise OccupiedError
        square[position.index] = color

    def move_piece(self, old_pos, new_pos, color):
        if not self.is_valid_move(old_pos, new_pos, color):
            raise InvalidMoveError
        self.squares[old_pos.square_name][old_pos.index] = None
        self.squares[new_pos.square_name][new_pos.index] = color


class Player(object):
    def __init__(self, name, color):
        self.name = name
        self.color = color
        self.pieces_to_set = 9

    def play(self, board):
        # Hier in abgeleiteter Klasse Spielzug über GUI erfragen
        # oder Spielzug durch KI ermitteln basierend auf board.squares
        if has_lost:
            raise GameLost
        if self.pieces_to_set:
            board.set_piece(position, self.color)
            self.pieces_to_set -= 1
        else:
            board.move_piece(old_pos, new_pos, self.color)


def play_game(player1, player2):
    board = Board()
    player_cycle = cycle([player1, player2])
    for player in player_cycle:
        try:
            player.play(board)
        except GameLost:
            winner = next(player_cycle)
            return winner

def main():
    player1 = ask_player()
    player2 = ask_player()
    winner = play_game(player1, player2)
    print(winner.name, 'hat das Spiel gewonnen!')
Die Idee mit dem Tupel für einen Spielzug habe ich wieder verworfen. Besonders im Falle eines ungültigen Spielzugs fand ich es leichter, dass der Spieler eine durch die Board-Klasse ausgelöste Exception direkt abfangen kann. Als Alternative hätte play_game() das sinnvoll behandeln müssen.

Rein theoretisch könnte man das Spielbrett noch irgendwie vor "unsachgemäßen" Änderungen seitens des Spieler-Codes (auch Schummeln/Mogeln genannt) schützen. Aber das würde ich erstmal weg lassen, da die Klasse ja nur intern verwendet wird und der Endanwender keinen Einfluss darauf hat. Das würde erst dann eine Rolle spielen, wenn man einen abgeleiteten Spieler sozusagen im Rahmen eines Plugin-Systems erlaubt.

Was haltet ihr generell von den gezeigten Ansätzen? Sind sie gut nachvollziehbar? Würdet ihr bestimmte Dinge anders lösen...?
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ups, es sind natürlich in jedem Quadrat nur 8 Felder, nicht 9 wie in meinem Code...

@sebastian0202:
Die von dir vorgeschlagene Schnittstelle ist sehr komplex. Es werden viele Aufrufe herumgereicht. Vielleicht könnte man da doch ein paar Dinge vereinfachen. Exceptions z.B sind manchmal ganz hilfreich, um einen Status für mehrere Instanzen hoch zu reichen. Es ist dann eben nicht True/False sondern Exception / keine Exception. Die Ebene, die die Exception sinnvoll behandeln kann, fängt sie einfach ab.

Trotzdem hast du damit eine gute Grundlage geliefert, von der ich einige Ideen übernommen habe.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ein vernünftiges UML-Diagramm fände ich auch sinnvoll. Trotzdem finde ich's nicht schlecht, auch schon bei der Planung einen groben Ablauf zu skizzieren, der über ein UML-Diagramm hinaus geht (Stichwort: Prototyping). Dabei erkennt man schnell, ob eine Idee möglicherweise in die falsche Richtung geht und kann schon in der Planungsphase bei Bedarf nachbessern.
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

snafu hat geschrieben: Rein theoretisch könnte man das Spielbrett noch irgendwie vor "unsachgemäßen" Änderungen seitens des Spieler-Codes (auch Schummeln/Mogeln genannt) schützen. Aber das würde ich erstmal weg lassen, da die Klasse ja nur intern verwendet wird und der Endanwender keinen Einfluss darauf hat. Das würde erst dann eine Rolle spielen, wenn man einen abgeleiteten Spieler sozusagen im Rahmen eines Plugin-Systems erlaubt.

Was haltet ihr generell von den gezeigten Ansätzen? Sind sie gut nachvollziehbar? Würdet ihr bestimmte Dinge anders lösen...?
Zudem Mogeln: Das macht doch erst wirklich Sinn wenn man das Spiel über Netzwerk spielt oder?
Sonst kann der Schummler, ja eh direkt den Board Code ändern.

Ich finde die Ansätze gut nachvollziehbar, was man noch hinzufügen sollte wäre bei move_piece im board, von wo nach wo man ziehen darf,
das könnte vieleicht so aussehen:
also das quadrat sieht vom index her vieleicht so aus:
8-1-2
7---3
6-5-4

Code: Alles auswählen

def move_allowed(old_pos, new_pos):
    if old_pos.square == new_pos.square:
        wenn old_pos.index == new_pos.index-1 oder +1 
        #dabei muss natürlich der Sprung zw. 1 und 8 berücksichtigt werden
        return True #oder was sollte man hier zurückgeben?
    else:
        if old_pos.index % 2 and new_pos.index == old_pos.index:
            if quadrate_angrenzend(old_pos, new_pos):
                return True
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Idee zum Prüfen eines Zuges, der auch das Problem löst, wenn vom größten Index zum kleinsten Index gezogen wird:

Code: Alles auswählen

"""
The board:

0-----------1-----------2
|           |           |
|   0-------1-------2   |
|   |       |       |   |
|   |   0---1---2   |   |
|   |   |       |   |   |
7---7---7       3---3---3
|   |   |       |   |   |
|   |   6---5---4   |   |
|   |       |       |   |
|   6-------5-------4   |
|           |           |
6-----------5-----------4                        
"""
from collections import namedtuple

SQUARE_NAMES = ['inner', 'mid', 'outer']

NUM_SQUARE_FIELDS = 8

class Position(namedtuple('Position', 'square_name, index')):
    def __new__(cls, square_name, index):
        square_name = square_name.lower()
        if square_name not in SQUARE_NAMES:
            raise ValueError('Invalid square name')
        if not 0 <= index < NUM_SQUARE_FIELDS:
            raise ValueError('Invalid field index')
        return super().__new__(cls, square_name, index)


def is_valid_move(old_pos, new_pos):
    if old_pos == new_pos:
        return False
    if old_pos.square_name == new_pos.square_name:
        offset = old_pos.index - new_pos.index
        return abs(offset) in (1, NUM_SQUARE_FIELDS - 1)
    if old_pos.index == new_pos.index:
        is_crossing = new_pos.index % 2 != 0
        square_names = (old_pos.square_name, new_pos.square_name)
        return is_crossing and 'mid' in square_names
    return False
Die Prüfung auf ein belegtes Feld habe ich bei diesem isolierten Beispiel mal weg gelassen. Die beiden Konstanten auf Modulebene sind hierbei auch eigentlich als Instanzattribute der Board-Klasse gedacht. Das ließe sich ja leicht übertragen.
Antworten