Erstes "Programm" ähnlich Kaiser

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Rizzo
User
Beiträge: 3
Registriert: Montag 22. Oktober 2018, 20:17

Hey Community, :-)
dies ist mein erster Post und mein erstes "richtiges Programm". Ich hab eine Weile die Grundlagen gelernt aber nie versucht, Alles in einem Programm unterzubringen und hab mir dann mal dieses Projekt vorgenommen. Grob orientierte ich mich an dem alten Klassiker Kaiser. Allerdings ging es mir dann recht schnell eher um die Konzepte etc und nicht um den Inhalt oder Werte etc. Ich würde trotzdem gerne mal von euch hören was ihr dazu denkt und wo ihr Optimierungsbedarf seht.
Ich möchte als nächstes das Programm dahingehend erweitern, dass es ganz rudimentäre Einheiten gibt aber auch Dinge wie Forschung und dies als Vorraussetzung für den Bau dient etc. Habe allerdings keinerlei Ahnung wie ich das machen soll (dachte an zusätzliche Klassen weil ich das am Liebsten jeweils in eigene Dateien auslagern möchte) aber bekomme dann den Zugriff nicht hin. Egal. Falls jemand Lust hätte mir da ein paar Fragen zu beantworten, würde ich mich auch sehr freuen und dann nochmal detaillierter schreiben. Sagt also am Besten einfach Bescheid.

Code: Alles auswählen

from random import uniform, randint, choice


game_continue = True
titel_tupel = ("Baron", "Graf", "Fürst", "Herzog", "Kurfürst", "Großherzog", "König", "Kaiser")


anzahl_spieler_str = input("Wie viele Spieler?")
while anzahl_spieler_str.isdigit() is False:
    anzahl_spieler_str = input("Bitte erneut eingeben: Wie viele Spieler?")
anzahl_spieler = int(anzahl_spieler_str)

spieler_liste = []
for spieler in range(anzahl_spieler):
    print("Spieler ", spieler)
    spieler_name = input("Name: ")
    print()
    spieler_liste.append(spieler_name)


def generiere_preise():
    kornpreis_kaufen = randint(3, 6)
    kornpreis_verkaufen = randint(2, 5)
    landpreis_kaufen = randint(70, 100)
    landpreis_verkaufen = randint(50, 80)
    return kornpreis_kaufen, kornpreis_verkaufen, landpreis_kaufen, landpreis_verkaufen


def auswahl_konsum():
    print("Wie viel Korn dürfen die Bürger konsumieren?")
    korn_auswahl = input("Minimum = 1 / Durchschnitt = 3 / Maximum = 5  ")
    while korn_auswahl != "1" and korn_auswahl != "3" and korn_auswahl != "5":
        korn_auswahl = input("Bitte erneut eingeben: Minimum = 1 / Durchschnitt = 3 / Maximum = 5 ")
    return korn_auswahl


def check_ob_digit(menge_str):      # Abfrage wurde stets wiederholt also in Funktion ausgelagert
    while menge_str.isdigit() is False:
        menge_str = input("Bitte erneut eingeben: Wie viel möchten sie handeln?")
        print()
    menge = int(menge_str)
    return menge


class Game(object):
    def __init__(self, vorname, runde, spieljahr, titel, bevoelkerung_gesamt,
                 geld, muehlen_anzahl, maerkte_anzahl, land_anzahl, korn_vorhanden, palast_elemente):
        self.vorname = vorname
        self.runde = runde
        self.spieljahr = spieljahr
        self.titel = titel
        self.bevoelkerung_gesamt = bevoelkerung_gesamt
        self.geld = geld
        self.muehlen_anzahl = muehlen_anzahl
        self.maerkte_anzahl = maerkte_anzahl
        self.land_anzahl = land_anzahl
        self.korn_vorhanden = korn_vorhanden
        self.palast_elemente = palast_elemente


    def anzeige(self):
        print("------------------")
        print()
        print("Runde:", self.runde)
        print(titel_tupel[self.titel], self.vorname + " im Jahre", self.spieljahr)
        print()
        print("Bevölkerung:", int(self.bevoelkerung_gesamt), "Geld:", int(self.geld), "Landbesitz:", self.land_anzahl,
              "Kornmühlen:", self.muehlen_anzahl, "Märkte:", self.maerkte_anzahl, "Palastelemente:", self.palast_elemente)
        print()
        print("Vorhandenes Korn:", int(self.korn_vorhanden))
        print(" Benötigtes Korn")
        print("             Min:", int(self.bevoelkerung_gesamt * 0.5))
        print("    Durchschnitt:", int(self.bevoelkerung_gesamt * 0.8))
        print("             Max:", int(self.bevoelkerung_gesamt * 1.1))
        print()
        print("Kornpreise:       Landpreise:")
        print("   Einkauf:", kornpreis_kaufen, "      Einkauf:", landpreis_kaufen)
        print("   Verkauf:", kornpreis_verkaufen, "      Verkauf:", landpreis_verkaufen)
        print()


    def auswahl_kauf_verkauf(self):
        eingabe = input("Möchten sie Korn/Land kaufen (k) oder verkaufen (v) oder Nichts (jede andere Taste) tun?")
        if eingabe == "k":
            kaufwahl = input("Soll Korn (k) oder Land (l) gekauft werden?")
            while kaufwahl != "k" and kaufwahl != "l":
                kaufwahl = input("Bitte erneut eingeben, Korn (k) oder Land (l):")

            if kaufwahl == "k":
                menge_str = input("Wie viel Korn möchten sie kaufen?")
                print()
                menge = check_ob_digit(menge_str)       # hier wird neue funktion aufgerufen

                while self.geld <= menge * kornpreis_kaufen:
                    menge_str = input("Das können sie sich nicht leisten. Bitte eine niedrigere Anzahl eingeben: ")
                    menge = check_ob_digit(menge_str)

                else:
                    self.korn_vorhanden += menge
                    self.geld -= menge * kornpreis_kaufen

            else:
                menge_str = input("Wie viel Land möchten sie kaufen?")
                print()
                menge = check_ob_digit(menge_str)

                while self.geld <= menge * landpreis_kaufen:
                    menge_str = input("Das können sie sich nicht leisten. Bitte eine niedrigere Anzahl eingeben: ")
                    menge = check_ob_digit(menge_str)

                else:
                    self.land_anzahl += menge
                    self.geld -= menge * landpreis_kaufen

        elif eingabe == "v":
            verkaufwahl = input("Soll Korn (k) oder Land (l) verkauft werden?")
            while verkaufwahl != "k" and verkaufwahl != "l":
                verkaufwahl = input("Bitte erneut eingeben, Korn (k) oder Land (l):")

            if verkaufwahl == "k":
                menge_str = input("Wie viel Korn möchten sie verkaufen?")
                print()
                menge = check_ob_digit(menge_str)

                while self.korn_vorhanden < menge:
                    menge_str = input("Soviel besitzen sie leider nicht. Bitte eine niedrigere Anzahl eingeben: ")
                    menge = check_ob_digit(menge_str)

                else:
                    self.korn_vorhanden -= menge
                    self.geld += menge * kornpreis_verkaufen

            else:
                menge_str = input("Wie viel Land möchten sie verkaufen?")
                print()
                menge = check_ob_digit(menge_str)

                while self.land_anzahl < menge:
                    menge_str = input("Soviel besitzen sie leider nicht. Bitte eine niedrigere Anzahl eingeben: ")
                    menge = check_ob_digit(menge_str)

                else:
                    self.land_anzahl -= menge
                    self.geld += menge * landpreis_verkaufen

        else:
            print("Es wurde nicht gehandelt.")
            print()


    def auswahl_erweiterung(self):
        eingabe = input("Möchten sie Kornmühlen (k), Märkte (m) oder Palastelemente (p) errichten oder Nichts tun (jede andere Taste) ?")
        if eingabe == "k":
            if self.geld >= 1000:
                self.geld -= 1000
                self.muehlen_anzahl += 1
                print("Eine Mühle gekauft.")
                print()
            else:
                print("Leider nicht genügend Geld!")
                print()

        elif eingabe == "m":
            if self.geld >= 2000:
                self.geld -= 2000
                self.maerkte_anzahl += 1
                print("Einen Marktplatz gekauft.")
                print()
            else:
                print("Leider nicht genügend Geld!")
                print()

        elif eingabe == "p":
            if self.geld >= 9000:
                self.geld -= 9000
                self.palast_elemente += 1
                print("Ein Palastelement gekauft.")
                print()
            else:
                print("Leider nicht genügend Geld!")
                print()

        else:
            print("Kein Kauf getätigt.")
            print()


    def berechnungen(self):
        if korn_auswahl == "1":
            self.korn_vorhanden -= self.bevoelkerung_gesamt * 0.5
            bevoelkerung_gestorben = self.bevoelkerung_gesamt * uniform(0.1, 0.2)
            bevoelkerung_geboren = self.bevoelkerung_gesamt * uniform(0.02, 0.075)
        elif korn_auswahl == "3":
            self.korn_vorhanden -= self.bevoelkerung_gesamt * 0.8
            bevoelkerung_gestorben = self.bevoelkerung_gesamt * uniform(0.075, 0.125)
            bevoelkerung_geboren = self.bevoelkerung_gesamt * uniform(0.075, 0.125)
        else:
            self.korn_vorhanden -= self.bevoelkerung_gesamt * 1.1
            bevoelkerung_gestorben = self.bevoelkerung_gesamt * uniform(0.02, 0.075)
            bevoelkerung_geboren = self.bevoelkerung_gesamt * uniform(0.1, 0.2)

        self.bevoelkerung_gesamt = self.bevoelkerung_gesamt - bevoelkerung_gestorben + bevoelkerung_geboren
        self.geld += self.bevoelkerung_gesamt * self.muehlen_anzahl * 0.05 + self.bevoelkerung_gesamt * self.maerkte_anzahl * 0.1


    def erzeuge_korn(self):
        self.korn_vorhanden += self.muehlen_anzahl * 300 * choice([1.5, 1.2, 1, 0.8, 0.5])


    def counter(self):
        self.runde += 1
        self.spieljahr += 1


    def befoerderung(self):  # vorher if anstatt elif benutzt und jetzt wird immer nur auf die nächste stufe geprüft
        if self.titel == 0 and self.geld > 20000 and self.bevoelkerung_gesamt > 2000 and self.land_anzahl > 200 and self.maerkte_anzahl > 2 and self.muehlen_anzahl > 2:
            self.titel = 1
            print("Herzlichen Glückwunsch. Sie wurden zum " + titel_tupel[self.titel] + " befördert! ")
            print()

        elif self.titel == 1 and self.geld > 30000 and self.bevoelkerung_gesamt > 3000 and self.land_anzahl > 300 and self.maerkte_anzahl > 3 and self.muehlen_anzahl > 3:
            self.titel = 2
            print("Herzlichen Glückwunsch. Sie wurden zum " + titel_tupel[self.titel] + " befördert! ")
            print()

        elif self.titel == 2 and self.geld > 50000 and self.bevoelkerung_gesamt > 5000 and self.land_anzahl > 500 and self.maerkte_anzahl > 5 and self.muehlen_anzahl > 5:
            self.titel = 3
            print("Herzlichen Glückwunsch. Sie wurden zum " + titel_tupel[self.titel] + " befördert! ")
            print()

        elif self.titel == 3 and self.geld > 60000 and self.bevoelkerung_gesamt > 6000 and self.land_anzahl > 600 and self.maerkte_anzahl > 6 and self.muehlen_anzahl > 6:
            self.titel = 4
            print("Herzlichen Glückwunsch. Sie wurden zum " + titel_tupel[self.titel] + " befördert! ")
            print()

        elif self.titel == 4 and self.geld > 80000 and self.bevoelkerung_gesamt > 8000 and self.land_anzahl > 800 and self.maerkte_anzahl > 8 and self.muehlen_anzahl > 8 and self.palast_elemente > 3:
            self.titel = 5
            print("Herzlichen Glückwunsch. Sie wurden zum " + titel_tupel[self.titel] + " befördert! ")
            print()

        elif self.titel == 5 and self.geld > 90000 and self.bevoelkerung_gesamt > 9000 and self.land_anzahl > 900 and self.maerkte_anzahl > 9 and self.muehlen_anzahl > 9 and self.palast_elemente > 6:
            self.titel = 6
            print("Herzlichen Glückwunsch. Sie wurden zum " + titel_tupel[self.titel] + " befördert! ")
            print()

        elif self.titel == 6 and self.geld > 120000 and self.bevoelkerung_gesamt > 12000 and self.land_anzahl > 1200 and self.maerkte_anzahl > 12 and self.muehlen_anzahl > 12 and self.palast_elemente > 10:
            self.titel = 7


    def spielende(self):
        if self.titel == 7:
            print("Herzlichen Glückwunsch. Du bist Kaiser! Du hast gewonnen!")
            print()
            game_continue = False
            return game_continue
        else:
            game_continue = True
            return game_continue


spieler_data = []
for spieler in spieler_liste:
    spieler = Game(spieler, 1, 1700, 0, 1000, 10000, 0, 0, 100, 10000, 0)
    spieler_data.append(spieler)


while game_continue is True:
    for i in range(anzahl_spieler):
        kornpreis_kaufen, kornpreis_verkaufen, landpreis_kaufen, landpreis_verkaufen = generiere_preise()
        spieler_data[i].anzeige()
        spieler_data[i].auswahl_kauf_verkauf()
        spieler_data[i].auswahl_erweiterung()
        korn_auswahl = auswahl_konsum()
        spieler_data[i].berechnungen()
        spieler_data[i].erzeuge_korn()
        spieler_data[i].counter()
        spieler_data[i].befoerderung()
        game_continue = spieler_data[i].spielende()
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

› game_continue‹ wird ganz am Anfang definiert, aber erst ganz zum Schluß benutzt. Definiere Variablen erst, wenn Du sie auch brauchst. Einen Wahrheitswert auf True zu prüfen, gibt nur wieder einen Wahrheitswert, das `is True` kann also in diesem Fall wegelassen werden.

Man schreibt nicht den Datentyp in den Variablennamen, denn der ändert sich öfter mal. Z.B. sollte titel_tupel eine Liste sein, möchtest Du jetzt überall die Variable umbenennen?

Statt nach der Anzahl Spieler zu fragen, wäre es bequemer, so lange Namen einzulesen, bis man einmal nur Enter drückt.

In › auswahl_kauf_verkauf‹ ist vieles Doppelt. Einkaufs-/Verkaufspreis könnte man als Klasse, die einzelnen Warengruppen als Liste implementieren.

Ebenso sollten die Erweiterungen als Datenstrukturen definiert sein und nicht explizit ausprogrammiert.
Rizzo
User
Beiträge: 3
Registriert: Montag 22. Oktober 2018, 20:17

Danke für deine Antwort. Habe das mit dem "is True" behoben, die Bezeichnung geändert und das mit der Spieleranzahl auch.
Die beiden letzten Punkte finde ich ein wenig tricky und würde da demnächst noch einmal auf dich zurückkommen wenn das ok wäre. Aber jetzt erstmal eine Portion Schlaf :-)

Vielleicht kommen ja noch mehr Tipps!
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Rizzo: Weitere Anmerkungen: Konstanten schreibt man per Konvention komplett in Grossbuchtaben und auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert.

Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Damit sind dann auch die modulglobalen Variablen vom Tisch und man kann auch nicht mehr unübersichtlich Programm und Funktions-/Klassendefinitionen mischen.

Es muss dann aber auch ein bisschen geändert werden, denn Du verwendest ja tatsächlich globale Variablen. Funktionen und Methoden sollten alles was sie an Werten verwenden (ausser Konstanten) als Argumente übergeben bekommen. Die Preise und die Kornauswahl werden von den entsprechenden Methoden aber einfach so ”magisch” als in der Umgebung vorhanden angenommen.

Bei `auswahl_konsum()` kann man die Bedingung mit ``korn_auswahl not in ('1', '3', '5')`` kürzer schreiben.

Da die Startwerte für alle Spieler gleich sind, könnte man die als Default-Argumente in der `__init__()` setzen.

In einer Liste mit dem Namen `spieler_data` Objekte vom Typ `Game` zu speichern sieht komisch aus. Entweder ist das eine Liste mit Spielern oder eine Liste mit Spielen und die Namen sollten dann zusammenpassen. Ich würde `Game` in `Spieler` umbenennen.

`game_continue` kann man sich sparen wenn man eine ”Endlosschleife” schreibt, die an der entsprechenden Stelle wo die Abbruchbedingung feststeht durch ein ``break`` verlassen wird. Dann sollte man die `spielende()`-Methode allerdings ändern, denn das die `True` liefert wenn das Spiel *nicht* zuende ist, verwirrt ziemlich.

Man sollte den Fall prüfen das der/die Benutzer gar keinen Spieler angelegt haben. Dann hast Du nämlich momentan mit der Spielhauptschleife eine Endlosschleife. :-)

``for i in range(len(sequence))`` nur um dann `i` als Index in `sequence` zu verwenden ist in Python ein „anti pattern“. Man *direkt* über die Elemente von Sequenztypen iterieren, ohne den Umweg über einen Index.

Wenn man `spieler_data` in `spieler` umbenannt hat, sieht man übrigens warum englsiche Bezeichner praktischer sind: Im Englsichen kommt es deutlich weniger häufig vor das Einzahl und Mehrzahl gleich geschrieben werden, und man sich irgeneine Krücke bei der Namensgebung zwischen Sequenz und Einzelwert einfallen lassen muss. Da geht ein ``for player in players:``, also das Anhängen des Mehrzahl-`s` funktioniert fast immer.

In `auswahl_kauf_verkauf()` sind ``else``-Zweige zu ``while``-Schleifen die gar kein ``break`` enthalten. Das macht keinen Sinn. Da musst Du noch einmal nachdenken was ein ``else`` zu einer Schleife bedeutet, denn es ist anscheinend nicht das was Du denkst.

Wenn `self.geld` auch negativ werden könnte, hast Du da übrigens 1a Endlosschleifen. Auch wenn der Kontostand niemals negativ werden kann oder dürfte, würde ich das an den Stellen wo das zu Problemen führen kann mindestens mit einem ``assert`` prüfen.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
from itertools import count
from random import choice, randint, uniform
# 
# TODO Schauen ob sich `enum.Enum` hier nicht eignet.
# 
TITELNAMEN = [
    'Baron', 'Graf', 'Fürst', 'Herzog', 'Kurfürst', 'Großherzog', 'König',
    'Kaiser',
]


def generiere_preise():
    kornpreis_kaufen = randint(3, 6)
    kornpreis_verkaufen = randint(2, 5)
    landpreis_kaufen = randint(70, 100)
    landpreis_verkaufen = randint(50, 80)
    return (
        kornpreis_kaufen,
        kornpreis_verkaufen,
        landpreis_kaufen,
        landpreis_verkaufen,
    )


def auswahl_konsum():
    # 
    # TODO `enum.Enum` statt der kryptischen '1', '3', und '5'.
    # 
    prompt = 'Minimum = 1 / Durchschnitt = 3 / Maximum = 5 '
    print('Wie viel Korn dürfen die Bürger konsumieren?')
    korn_auswahl = input(prompt)
    while korn_auswahl not in ('1', '3', '5'):
        korn_auswahl = input('Bitte erneut eingeben: ' + prompt)
    return korn_auswahl


def check_ob_digit(menge_str):
    # 
    # FIXME Es gibt Zeichen bei denen `isdigit()` wahr ist, die aber
    #   nicht mit `int()` in eine ganze Zahl umgewandelt werden können.
    #   Beispiel: '²'.
    # 
    while not menge_str.isdigit():
        menge_str = input(
            'Bitte erneut eingeben: Wie viel möchten sie handeln?'
        )
        print()
    return int(menge_str)


class Spieler:

    def __init__(
        self,
        vorname,
        runde=1,
        spieljahr=1700,
        titel=0,
        bevoelkerung_gesamt=1000,
        geld=10000,
        muehlen_anzahl=0,
        maerkte_anzahl=0,
        land_anzahl=100,
        korn_vorhanden=10000,
        palast_elemente=0,
    ):
        self.vorname = vorname
        self.runde = runde
        self.spieljahr = spieljahr
        self.titel = titel
        self.bevoelkerung_gesamt = bevoelkerung_gesamt
        self.geld = geld
        self.muehlen_anzahl = muehlen_anzahl
        self.maerkte_anzahl = maerkte_anzahl
        self.land_anzahl = land_anzahl
        self.korn_vorhanden = korn_vorhanden
        self.palast_elemente = palast_elemente

    def anzeige(
        self,
        kornpreis_kaufen,
        kornpreis_verkaufen,
        landpreis_kaufen,
        landpreis_verkaufen,
    ):
        print('------------------')
        print()
        print('Runde:', self.runde)
        print(
            TITELNAMEN[self.titel], self.vorname + ' im Jahre', self.spieljahr
        )
        print()
        print(
            'Bevölkerung:', int(self.bevoelkerung_gesamt),
            'Geld:', int(self.geld),
            'Landbesitz:', self.land_anzahl,
            'Kornmühlen:', self.muehlen_anzahl,
            'Märkte:', self.maerkte_anzahl,
            'Palastelemente:', self.palast_elemente,
        )
        print()
        print('Vorhandenes Korn:', int(self.korn_vorhanden))
        print(' Benötigtes Korn')
        #
        # TODO Die berechnungen stehen weiter unten im Code noch einmal,
        #   das sollte nicht sein.
        # 
        print('             Min:', int(self.bevoelkerung_gesamt * 0.5))
        print('    Durchschnitt:', int(self.bevoelkerung_gesamt * 0.8))
        print('             Max:', int(self.bevoelkerung_gesamt * 1.1))
        print()
        print('Kornpreise:       Landpreise:')
        print(
            '   Einkauf:', kornpreis_kaufen, '      Einkauf:', landpreis_kaufen
        )
        print(
            '   Verkauf:', kornpreis_verkaufen,
            '      Verkauf:', landpreis_verkaufen
        )
        print()

    def auswahl_kauf_verkauf(
        self,
        kornpreis_kaufen,
        kornpreis_verkaufen,
        landpreis_kaufen,
        landpreis_verkaufen,
    ):
        #
        # TODO Diese Funktion enthält zu viel sehr ähnlichen Code.
        # 
        eingabe = input(
            'Möchten sie Korn/Land kaufen (k) oder verkaufen (v) oder Nichts'
            ' (jede andere Taste) tun?'
        )
        if eingabe == 'k':
            kaufwahl = input('Soll Korn (k) oder Land (l) gekauft werden?')
            while kaufwahl not in ('k', 'l'):
                kaufwahl = input(
                    'Bitte erneut eingeben, Korn (k) oder Land (l):'
                )

            if kaufwahl == 'k':
                menge_str = input('Wie viel Korn möchten sie kaufen?')
                print()
                menge = check_ob_digit(menge_str)

                while self.geld <= menge * kornpreis_kaufen:
                    menge_str = input(
                        'Das können sie sich nicht leisten.'
                        ' Bitte eine niedrigere Anzahl eingeben: '
                    )
                    menge = check_ob_digit(menge_str)

                self.korn_vorhanden += menge
                self.geld -= menge * kornpreis_kaufen
            else:
                menge_str = input('Wie viel Land möchten sie kaufen?')
                print()
                menge = check_ob_digit(menge_str)

                while self.geld <= menge * landpreis_kaufen:
                    menge_str = input(
                        'Das können sie sich nicht leisten.'
                        ' Bitte eine niedrigere Anzahl eingeben: '
                    )
                    menge = check_ob_digit(menge_str)

                self.land_anzahl += menge
                self.geld -= menge * landpreis_kaufen

        elif eingabe == 'v':
            verkaufwahl = input('Soll Korn (k) oder Land (l) verkauft werden?')
            while verkaufwahl not in ('k', 'l'):
                verkaufwahl = input(
                    'Bitte erneut eingeben, Korn (k) oder Land (l):'
                )

            if verkaufwahl == 'k':
                menge_str = input('Wie viel Korn möchten sie verkaufen?')
                print()
                menge = check_ob_digit(menge_str)

                while self.korn_vorhanden < menge:
                    menge_str = input(
                        'Soviel besitzen sie leider nicht.'
                        ' Bitte eine niedrigere Anzahl eingeben: '
                    )
                    menge = check_ob_digit(menge_str)

                self.korn_vorhanden -= menge
                self.geld += menge * kornpreis_verkaufen

            else:
                menge_str = input('Wie viel Land möchten sie verkaufen?')
                print()
                menge = check_ob_digit(menge_str)

                while self.land_anzahl < menge:
                    menge_str = input(
                        'Soviel besitzen sie leider nicht.'
                        ' Bitte eine niedrigere Anzahl eingeben: '
                    )
                    menge = check_ob_digit(menge_str)

                self.land_anzahl -= menge
                self.geld += menge * landpreis_verkaufen

        else:
            print('Es wurde nicht gehandelt.')
            print()

    def auswahl_erweiterung(self):
        # 
        # TODO Diese Funktion enthält zu viel sehr ähnlichen Code.
        # 
        eingabe = input(
            'Möchten sie Kornmühlen (k), Märkte (m) oder Palastelemente (p)'
            ' errichten oder Nichts tun (jede andere Taste) ?'
        )
        if eingabe == 'k':
            if self.geld >= 1000:
                self.geld -= 1000
                self.muehlen_anzahl += 1
                print('Eine Mühle gekauft.')
                print()
            else:
                print('Leider nicht genügend Geld!')
                print()

        elif eingabe == 'm':
            if self.geld >= 2000:
                self.geld -= 2000
                self.maerkte_anzahl += 1
                print('Einen Marktplatz gekauft.')
                print()
            else:
                print('Leider nicht genügend Geld!')
                print()

        elif eingabe == 'p':
            if self.geld >= 9000:
                self.geld -= 9000
                self.palast_elemente += 1
                print('Ein Palastelement gekauft.')
                print()
            else:
                print('Leider nicht genügend Geld!')
                print()

        else:
            print('Kein Kauf getätigt.')
            print()

    def berechnungen(self, korn_auswahl):
        #
        # TODO Die Berechnungen/Faktoren für die für die ausgewählte
        #   Kornmenge stehen weiter oben schon einmal in der Anzeigemethode.
        #
        # TODO Wenn `korn_auswahl` ein `enum.Enum`-Typ wäre, könnte
        #   man auch die Werte die Faktoren und die Grenzwerte für die
        #   Zufallszahlen und sogar die gemeinsame Berechnung in die
        #   `Enum` verlagern und sich das ``if``/``elif``/``else`` sparen.
        # 
        if korn_auswahl == "1":
            self.korn_vorhanden -= self.bevoelkerung_gesamt * 0.5
            bevoelkerung_gestorben = (
                self.bevoelkerung_gesamt * uniform(0.1, 0.2)
            )
            bevoelkerung_geboren = (
                self.bevoelkerung_gesamt * uniform(0.02, 0.075)
            )
        elif korn_auswahl == "3":
            self.korn_vorhanden -= self.bevoelkerung_gesamt * 0.8
            bevoelkerung_gestorben = (
                self.bevoelkerung_gesamt * uniform(0.075, 0.125)
            )
            bevoelkerung_geboren = (
                self.bevoelkerung_gesamt * uniform(0.075, 0.125)
            )
        else:
            self.korn_vorhanden -= self.bevoelkerung_gesamt * 1.1
            bevoelkerung_gestorben = (
                self.bevoelkerung_gesamt * uniform(0.02, 0.075)
            )
            bevoelkerung_geboren = self.bevoelkerung_gesamt * uniform(0.1, 0.2)

        self.bevoelkerung_gesamt += (
            bevoelkerung_geboren - bevoelkerung_gestorben
        )
        self.geld += (
            self.bevoelkerung_gesamt * self.muehlen_anzahl * 0.05
            + self.bevoelkerung_gesamt * self.maerkte_anzahl * 0.1
        )

    def erzeuge_korn(self):
        self.korn_vorhanden += (
            self.muehlen_anzahl * 300 * choice([1.5, 1.2, 1, 0.8, 0.5])
        )

    def counter(self):
        #
        # TODO Die beiden Werte sind redundant.  Einen davon durch ein
        #   `property()` ersetzen und aus dem anderen berechnen.  Zum
        #   Beispiel das Spieljahr aus der Runde und einem festen
        #   Startjahr.
        # 
        self.runde += 1
        self.spieljahr += 1

    def befoerderung(self):
        # 
        # TODO Hier liessen sich die Grenzwerte für die Bedingungen
        #   als Daten herausziehen.  Zum Beispiel in `Enum`-Objekte
        #   für den Titel, und damit eine Menge Code sparen, weil man
        #   dann nur das nächsthöhere Titelobjekt ermitteln und die
        #   Spielerwerte gegen die Grenzwerte testen muss.
        #
        if (
            self.titel == 0
            and self.geld > 20000
            and self.bevoelkerung_gesamt > 2000
            and self.land_anzahl > 200
            and self.maerkte_anzahl > 2
            and self.muehlen_anzahl > 2
        ):
            self.titel += 1
            print(
                'Herzlichen Glückwunsch. Sie wurden zum',
                TITELNAMEN[self.titel],
                'befördert! '
            )
            print()

        elif (
            self.titel == 1
            and self.geld > 30000
            and self.bevoelkerung_gesamt > 3000
            and self.land_anzahl > 300
            and self.maerkte_anzahl > 3
            and self.muehlen_anzahl > 3
        ):
            self.titel += 1
            print(
                'Herzlichen Glückwunsch. Sie wurden zum',
                TITELNAMEN[self.titel],
                'befördert! '
            )
            print()

        elif (
            self.titel == 2
            and self.geld > 50000
            and self.bevoelkerung_gesamt > 5000
            and self.land_anzahl > 500
            and self.maerkte_anzahl > 5
            and self.muehlen_anzahl > 5
        ):
            self.titel += 1
            print(
                'Herzlichen Glückwunsch. Sie wurden zum',
                TITELNAMEN[self.titel],
                'befördert! '
            )
            print()

        elif (
            self.titel == 3
            and self.geld > 60000
            and self.bevoelkerung_gesamt > 6000
            and self.land_anzahl > 600
            and self.maerkte_anzahl > 6
            and self.muehlen_anzahl > 6
        ):
            self.titel += 1
            print(
                'Herzlichen Glückwunsch. Sie wurden zum',
                TITELNAMEN[self.titel],
                'befördert! '
            )
            print()

        elif (
            self.titel == 4
            and self.geld > 80000
            and self.bevoelkerung_gesamt > 8000
            and self.land_anzahl > 800
            and self.maerkte_anzahl > 8
            and self.muehlen_anzahl > 8
            and self.palast_elemente > 3
        ):
            self.titel += 1
            print(
                'Herzlichen Glückwunsch. Sie wurden zum',
                TITELNAMEN[self.titel],
                'befördert! '
            )
            print()

        elif (
            self.titel == 5
            and self.geld > 90000
            and self.bevoelkerung_gesamt > 9000
            and self.land_anzahl > 900
            and self.maerkte_anzahl > 9
            and self.muehlen_anzahl > 9
            and self.palast_elemente > 6
        ):
            self.titel += 1
            print(
                'Herzlichen Glückwunsch. Sie wurden zum',
                TITELNAMEN[self.titel],
                'befördert! '
            )
            print()

        elif (
            self.titel == 6
            and self.geld > 120000
            and self.bevoelkerung_gesamt > 12000
            and self.land_anzahl > 1200
            and self.maerkte_anzahl > 12
            and self.muehlen_anzahl > 12
            and self.palast_elemente > 10
        ):
            self.titel += 1
            print(
                'Herzlichen Glückwunsch. Sie wurden zum',
                TITELNAMEN[self.titel],
                'befördert! '
            )
            print()

    def spielende(self):
        if self.titel == 7:
            print('Herzlichen Glückwunsch. Du bist Kaiser! Du hast gewonnen!')
            print()
            return True
        else:
            return False


def main():
    print('Spielernamen eingeben.  Leere Eingabe um fortzufahren.')
    spieler = []
    for i in count(1):
        print('Spieler ', i)
        spielername = input('Name: ').strip()
        if not spielername:
            break
        print()
        spieler.append(Spieler(spielername))

    if spieler:
        while True:
            for aktueller_spieler in spieler:
                (
                    kornpreis_kaufen,
                    kornpreis_verkaufen,
                    landpreis_kaufen,
                    landpreis_verkaufen,
                ) = generiere_preise()
                aktueller_spieler.anzeige(
                    kornpreis_kaufen,
                    kornpreis_verkaufen,
                    landpreis_kaufen,
                    landpreis_verkaufen,
                )
                aktueller_spieler.auswahl_kauf_verkauf(
                    kornpreis_kaufen,
                    kornpreis_verkaufen,
                    landpreis_kaufen,
                    landpreis_verkaufen,
                )
                aktueller_spieler.auswahl_erweiterung()
                aktueller_spieler.berechnungen(auswahl_konsum())
                aktueller_spieler.erzeuge_korn()
                aktueller_spieler.counter()
                aktueller_spieler.befoerderung()
                if aktueller_spieler.spielende():
                    break


if __name__ == '__main__':
    main()
Bei den Titeln und der Konsumauswahl kann man sich mal `enum.Enum` anschauen statt magischer Zahlen bzw. noch kryptischeren Zeichenketten mit Ziffern zu verwenden.

`check_ob_digit()` ist problematisch weil es Zeichen gibt die als Ziffer durchgehen, aber nicht mit `int()` umgewandelt werden können. Man würde hier eher einfach in `int` umwandeln und im Falle dass das nicht klappt, die Eingabe wiederholen lassen. Dann müsste man alllerdings auch den Fall von negativen Eingaben noch behandeln. Die Aufteilung ist auch ein wenig ungünstig: `check_ob_digit()` prüft ja nicht nur, sondern wandelt auch um *und* fragt den Bunutzer ggf. nach Eingaben. Das erwartet man bei dem Funktionsnamen nicht. Sinnvoller wäre eine Funktion die einen Prompt entgegennimmt und den Benutzer dann die Menge eingeben lässt und die als Ergebnis liefert. Denn Du hast ja nur einen Teil des sich immer wiederholenden Codes in `check_ob_digit()` verlagert, statt den gesamten Vorgang in einer Funktion zu stecken.

Üblicher ist es auch den Code für die Eingabe nur einmal zu haben und in einer ``while True:``-Schleife das ganze solange abzuarbeiten bis die Eingabe stimmt und die Schleife dann per ``return eingabewert`` oder ``break`` verlassen werden kann. Das noch an anderen Stellen im Code sinnvoll.

Die `Game`-Klasse enthält mit `runde` und `spieljahr` gleich mehrfach redundante Daten. 1. werden die ja beide immer gleich verändert, also liesse sich immer das eine aus dem anderen berechnen, und zweitens sind das ja Werte die für alle Spieler gleich gelten, trotzdem hat jeder Spieler seine eigene Kopie davon. Hier wäre wie weiter oben schon gesagt zu überlegen `Game` in `Spieler` umzubenennen und eine `Game`-Klasse einzuführen und auch zu überlegen welche Funktionalität in `Game` und welche in `Spieler` gehört.

Die Berechnung vom benötigten Korn für Min, Durchschnitt, und Max steht an zwei unterschiedlichen Stellen im Code. Das ist eine Fehlerquelle, weil man spätestens bei Veränderungen immer daran denken muss beide Stellen zu verändern und aufpassen muss, dass man das bei beiden genau gleich macht.

Du solltest Dir für die Ausgabe(n) wo Werte formatiert werden müssen mal die `format()`-Methode und die Möglichkeiten die Formatierung von Werten zu beeinflussen anschauen. Zum Beispiel kann man die Gesamtanzahl und die Anzahl von Nachkommastellen von Gleitkommazahlen angeben und so für eine immer gleich breite Ausgabe von Zahlen sorgen.

Die Aufteilung zwischen `berechnungen()` und `erzeuge_korn()` ist mir nicht so ganz klar. Warum sind das zwei Methoden und nicht eine wo alles berechnet wird oder drei wo auch `geld` eine eigene Methode bekommt? Die momentane Aufteilung scheint etwas willkürlich.

Apropos Aufteilung: Als nächsten Schritt könnte man die Spiellogik und die Benutzerinteraktion sauber trennen. Dann kann man zum Beispiel später mit dem gleichen Code für die Spiellogik eine Text- und eine GUI-Variante des Spiels anbieten. Oder eine einfache Text-Variante wie momentan, und eine die `curses`, `urwid` oder etwas in der Richtung verwendet um eine ”grafischere” Textausgabe zu realisieren. Man könnte auch einen Spielserver schreiben und mehrere Spieler über das Netz ermöglichen. Und/oder ein Webfrontend.

Welches Kaiser steht denn Pate? Ich habe das damals auf dem C64 gespielt. Soll da noch Krieg dazu kommen, oder belässt Du es bei der Handelskomponente?
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Rizzo
User
Beiträge: 3
Registriert: Montag 22. Oktober 2018, 20:17

WOW! Einfach WOW :-) Vielen Dank für deine ausführliche Kritik und den ganzen Verbesserungsvorschlägen. Da kann man noch so viele Bücher etc lesen aber nichts ist besser als Menschen mit Erfahrung, die bereit sind diese weiterzugeben. Ich werde mir das alles zu Herzen nehmen und mich da mal ran setzen. Als Anfänge dauert es bei mir immer eine Weile bis ich den Fehler in Gänze verstehe dann die Logik bzw das Konzept dahinter begreife und schließlich die programmiertechnische Lösung dafür entwickle.
Das hier ist meine achte Version und jede hat riesen Sprünge gemacht. Ich bin mir sicher, nachdem ich all eure Ratschläge umgesetzt habe, werden die kommenden Versionen mir eine Menge Wissen vermitteln.
Mein Ziel ist es eigentlich nur Python zu lernen im Allgemeinen. Habe mehrere Bücher durch und Dutzende Aufgaben aber das sind immer isolierte Übungen. Tue mich immer recht schwer damit mir Projekte auszudenken, die nicht zu leicht/zu schwer sind aber es wird ja langsam.
Als nächstes würde ich gerne irgendwann das Spiel erweitern um andere Konzepte zu lernen. Mir ist zwar klar, dass man größere Projekte über mehrere Dateien aufbaut aber... führe ich beispielsweise Militär ein, so möchte ich dies in einer eigenen Datei alles haben. Ich denke das macht man so? Klassen und Vererbung und Pakete und Module sind mir zwar halbwegs alle klar aber ich weiß dann noch nicht wie ich das aufbaue. Erzeuge ich eine neue Klasse dafür? Aber wie greift dann Instanz von Spieler 1 auf eine andere Klasse in einer anderen Datei zu? Oder schreib ich das alles doch in eine große Datei? Genauso mit der Forschung. Man muss erst gewisse Dinge erforscht haben bevor man xy bauen kann. Das sind so Dinge bei denen ich noch keine Ahnung habe wie ich das angehe..wie der richtige Weg da aussieht. Ich denke dafür ist es jetzt noch zu früh und würde mich nur verwirren aber jemand der mir nur grob die Richtung weist und mich dann tüfteln lässt und korrigiert würde mir da eine Menge helfen. Wenn es ok wäre würde ich da auf dich mal zurückkommen?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Rizzo: Wo Du gerade die Versionen/Sprünge erwähnst: Du hattest Da Kommentare drin, die anscheinend Unterschiede an bestimmten Codestellen zur Vorgängerversion beschrieben haben; so etwas ist in den Commit-Nachrichten einer Versionsverwaltung (Git, Mercurial, …) besser aufgehoben.

Aufteilung auf mehr als ein Modul (Modul = Datei in Python) macht man entweder wenn Module deutlich zu gross werden (ich habe da so 1.000 Zeilen als Schmerzgrenze), oder wenn man ganz klar abgegrenzte Funktionalität hat. Zum Beispiel die Logik von der Benutzerinteraktion trennen kann. Man sollte es auch nicht zu kleinteilig aufteilen. Ein Modul pro Klasse macht beispielsweise keinen Sinn, weil dann das Modul als Organisationseinheit gar nicht benutzt wird.

Wenn mehr als ein Modul, dann würde ich empfehlen das gleich in ein Package zu stecken, so das man auf ”oberster Ebene” für Importe nur *einen* Namen hat. Das verringert die Gefahr von Namenskollisionen mit anderen installierten Packages.

Das Aufteilen von einem Programm das nur aus einem Modul besteht in ein Package kann man problemlos schrittweise erledigen, weil man ein Modul einfach in ein Package verwandeln kann, in dem man aus der Datei ein Verzeichnis macht, und den Dateiinhalt dort in eine `__init__.py` verschiebt. Also wenn man vorher nur eine `kaiser.py` hatte, kann man den Inhalt der Datei in ``kaiser/__init__.py`` verschieben, ohne das man sonst etwas am Code ändern muss. Und dann kann man anfangen Teile aus der `__init__.py` in neue Module unter `kaiser/` zu verschieben, oder eventuell auch den gesamten Inhalt in Module aufzuteilen.

Bei Modulen ist noch wichtig das man keine direkten oder indirekten Kreisbeziehungen bei den Importen bekommt. Also immer so schreiben das ein Modul ein anderes braucht, das aber nicht umgekehrt das importierende Modul. Wenn sich zwei Module gegenseitig brauchen, dann sind sie zu eng miteinander verknüpft. Dann ist das eigentlich *ein* Modul, oder der Inhalt der beiden Module ist falsch verteilt, oder man hat eigentlich *noch* ein Modul das den Kram enthält den beide Module brauchen.

Auf Klassen greift man zu in dem man sie in den Modulnamensraum holt (wenn sie nicht im gleichen Modul sind): ``import``. Falls Du eigentlich meintest wie man von einem Objekt auf ein anderes Objekt zugreift: Man muss die halt irgendwie miteinander bekannt machen. Zum Beispiel in dem man ein Objekt beim Aufruf einer Methode des anderen Objekts als Argument übergibt. Und das ist unabhängig davon ob die Klassen für diese beiden Objekte im gleichen Modul oder in unterschiedlichen Modulen definiert sind. Denn in den Modulen gibt es ja keine Variablen. Falls doch sollten die da verschwinden. :-)

Einfach nur die Begriffe Militär und Forschung in den Raum zu werfen ist zu vage um da sagen zu können wie man das richtig macht. (Es fängt ein bisschen an nach Civilisation zu klingen :-))

Weil Du sowohl bei Sirius3 als auch bei mir die Frage stellst ob Du noch mal auf uns zurückkommen kannst: Falls damit private Nachrichten gemeint sind, dann denke ich auch für Sirius3 und die meisten anderen Regulars hier sagen zu können: Bitte nicht. Das ist ein öffentliches Forum und wir verbringen hier ja Freizeit, und da haben einfach mehr Leute etwas davon wenn man nicht privat und einzeln Fragen diskutiert.

Falls dagegen gemeint war ob Du hier im Thema Folgefragen stellen kannst oder neue Themen aufmachen kannst: Na klar, dafür ist das Forum ja da. :-)

Jetzt hast Du mich übrigens mit dem Thema ”infiziert”. Habe gestern Abend schon die Diskette herausgesucht und ein bisschen zum Thema recherchiert. Es gab ein „Kaiser II“ für Atari 8-Bitter das seit 2003 OpenSource ist. TurboBASIC XL-Quelltexte. Für den C64 scheint es auch zumindest Cracks zu geben mit dem Titel „Kaiser II“, die aber wohl nur „Kaiser“ sind, vielleicht ja eventuell mit dem möglichen Programmabsturz beim Krieg beseitigt. Soweit ich mich erinnere konnte man bei dem Absturz dann auch den BASIC-Teil von dem Spiel auflisten.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Rizzo hat geschrieben: Mittwoch 24. Oktober 2018, 12:03 Aber wie greift dann Instanz von Spieler 1 auf eine andere Klasse in einer anderen Datei zu?
Dafür musst die die Klasse aus dem anderen Modul importieren. Bedenke aber, dass keine gegenseitigen Abhängigkeiten vorliegen sollten, zumindest keine allzu engen. Denn es ist nicht möglich, Modul B in Modul A zu importieren, wenn Modul B seinerseits bereits Modul A importiert hat (Stichwort: Circular Imports). Du kannst aber natürlich Objekte als Argumente herumreichen.

Alles in allem kann ich dir aus Erfahrung sagen, dass die Unterteilung in Module manchmal ganz schön tricky sein kann, auch wenn es anfangs noch praktisch erscheint. Nichts gegen das Konzept an sich (ab einer bestimmten Projektgröße geht es kaum noch anders), aber es sollte mit Bedacht eingesetzt werden.
Antworten