Klasse verschiedene varianten

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

Ich möchte folgendes Programmieren:
Ich habe eine Hauptklasse, und möchte von dieser Unterklassen erstellen.
Die Unterklassen sollen aber auch nur leichte Änderungen in ein Paar Funktionen haben,
so dass z.B. eine Variable unterschiedlich ist.
Diese soll aber eigentlich nicht als Parameter übergeben werden, da ich von den Unterklassen viele Instanzen bilden möchte
und nicht immer wieder die gleichen Parameter mit übergeben möchte.

Wie ist hier die eleganteste Lösung? was würdet ihr tuen?

In meinm Programm ist das dann so:
Ich habe einen Hauptgebäudetyp, (die klasse Building)
aus dem ich viele verschiedene andere Gebäude erstellen möchte,
die z.B. einen Anderen Namen oder aussehen haben.
Von diesen können dann alle beim Programmablauf vom user sehr oft gebaut, d.h. es können viele Instanzen gebildet werden.
Würdet ihr hier dennoch alle Gebäude als neue Klasse definieren, die von Building erbt?
Oder eventuell eine Funktion die diese Definiert? also z.B. so:

Code: Alles auswählen

def register_building(name, image, ...):
    class Building:
        def __init__(self, position, owner):
            self.position = position
            self.owner = owner
            self.name = name
            self.image = image
            self..... =...
    return Building
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
BlackJack

@Pygoscelis papua: Also wenn ich das richtig verstehe, dann unterscheiden sich die Klassen gar nicht, sondern nur die Exemplare die daraus erstellt werden. Also sollte man da nicht verschiedene Klassen erstellen, die dann auch noch alle den gleichen Namen haben.

Das Problem ist hier IMHO viel zu allgemein beschrieben als das man da konkret was zu sagen kann, ausser das es verschiedene Möglichkeiten gibt das lösen. Zum Beispiel mit Funktionen. Die man, wenn es wirklich so einfach ist das sich einfach nur festgelegte Argumente für das erstellen der Exemplare unterscheiden, mit `functools.partial()` erstellen könnte. Es könnte aber auch sein das man das mit anderen Mitteln besser lösen kann.
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

Also es ist im moment noch nicht viel mehr als das beschriebene da.
Die Gebäude sollen verschiedene Eigenschaften haben, wie halt das aussehen
aber auch z.B. ob dort etwas mit getan werden kann, z.B. das dort etwas Produziert wird etc.
Dazu wird teilweise dann auch eine Funktion geändert werden müssen aber oft halt auch nur ein ganz kleiner Teil
wie eine oder 2 variablen.
Hier ist ein Beispiel für die Oberklasse:

Code: Alles auswählen

class Building:
    def __init__(selfposition, owner):
        self.owner = owner
        self.position = position
    def can_build(self):
        return True
    def on_turn_start(self, turn):
        do_something()
Und dann kann z.B. do_something() immer etwas anderes sein
oder die Funktion can_build, welche True zurückgeben soll wenn der Spieler das Gebäude an der gegeben Position bauen darf
Es kann aber halt auch sein das nur noch eine variable hinzugefügt wird, wie ein anderer Name,
der dann auch beim GUI bewirkt, dass dieser ein anderes Bild für das Gebäude auf das Feld setzt.
Da ich später das Spiel noch erweitern möchte, sollten dort möglichst viele Möglichkeiten offen gehalten werden,
aber ich glaube nicht das es Sinn macht dann jedes mal eine Neue Unter-Klasse zu definieren,
zumal so wie so eine Liste mit allen Gebäude-typen erstellt werden muss.

Ich weiß jetzt nicht so richtig was ich noch konkretisieren soll,
wenn noch etwas wichtiges Fehlt bitte einfach Fragen.
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
BlackJack

@Pygoscelis papua: Das klingt eher so als wäre die eine Klasse zu wenig und ein bisschen danach das möglichst *alles* variabel und flexibel sein soll, so für die Zukunft und so. Man kann solche Entscheidungen aber nur bedingt weit verschieben. Irgendwann muss man konkret werden und auch Einschränkungen machen. Und beschreibt ein `Building`-Exemplar ein konkretes Gebäude, oder einen Gebäudetyp? Denn ob ein konkretes Gebäude an einer bestimmten Position gebaut werden kann, bestimmt eigentlich nicht das Gebäude, das sollte vorher geklärt worden sein. Wenn das erstellt wurde mit einer Position, dann erwarte ich eigentlich das das gebaut wurde an der Position. Kann es sein das Du hier schon zwei Dinge vermischst die eigentlich gar nicht in einem Datentyp abgebildet werden sollten?

Eventuell möchtest Du Dir das Strategie-Entwurfsmuster anschauen. Und/oder das Command-Muster. Und falls das ein Projekt mit Abgabedatum und Benotung ist, würde ich wieder Meilensteine bei der Planung empfehlen und mit einem ganz einfachen aber tatsächlich spielbaren Programm anfangen, bevor man sich da sonstwelche komplizierten Regeln und Varianten ausdenkt. Aufpassen das man beim Planen und Implementieren nicht YAGNI-Code produziert.
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

Also die Funktionen müssen auf jeden Fall änderbar sein, da vor allem anhand dieser die Gebäude charakterisiert werden.
Zu der build Funktion: ja letztendlich sollte pos eher Parameter dieser anstatt der init-funktion sein,
die Funktion muss auch für jedes Gebäude verschieden sein, d.h. sie kann nicht extern definiert werden,
da jedes Gebäude woanders gebaut werden können muss.
Diese Funktion kann aber erst nach dem erstellen des Objektes aufgerufen werden, weil sonst self ja fehlt....
von daher ist es eigentlich egal ob die Funktion die Position erst festlegt oder schon init,
das Objekt kann ja einfach gelöscht werden, wenn Festgestellt wird, dass man das so nicht bauen kann.

Zu den Meilensteinen: Die habe ich mir schon gemacht ...
Im Moment bin ich halt bei den Gebäuden angekommen, die gehen theoretisch auch schon, aber die
werden im Moment über eine register-funktion erstellt. (die Klassen, nicht die Instanzen)

Zur Benotung: Nein der Pythonteil wird nicht wirklich bewertet, der ist nur zusätztlich, nachdem festgelegt wurde, dass wir gegen die Erwartungen
in Delphi/Lazarus/Pascal programmieren müssen :(, die letzten jahrgänge durften auch andere Sprachen verwenden und uns wurde das im letzten Halbjahr auch noch so gesagt :x
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
BlackJack

@Pygoscelis papua: Auch nach diesem Beitrag ist mir immer noch nicht klar was ”Gebäude” denn nun eigentlich ist. Jedes Gebäude muss woanders gebaut werden können oder Gebäudetypen müssen woanders gebaut werden können? Deine Register-Funktion erstellt Gebäude oder Gebäudetypen? Was von `self` wird in der `can_build()`-Methode denn verwendet? Nur die Position? Die könnte man auch als Argument übergeben. Dir ist klar das es auch Klassenmethoden und statische Methoden gibt? Und Metaklassen? Und das man die Trennung zwischen Gebäude und Gebäudetyp auch tatsächlich durch zwei Klassen erreichen kann? Oder vielleicht auch einfach durch eine oder mehrere Funktionen?

Eine Register-Funktion die etwas erstellt ist übrigens ein bisschen überraschend, da würde man eher erwarten das die, nun ja etwas registriert und nichts zurück gibt. Und dann vielleicht auch eine Methode auf einer Klasse ist, die die registrierten Objekte verwaltet.

Wenn die ”Funktionen” das Verhalten der Gebäude charakterisieren, dann willst Du entweder eine Klasse pro Gebäudetyp, wo das verhalten dann halt als Methoden umgesetzt wird, oder das Strategie-Muster wenn die Verhalten selbst noch mal parametrisiert und vielleicht auch verschieden kombiniert werden sollen.

Ich persönlich würde wohl mit einem Gebäude(typ) als ganz normale Klasse anfangen. Und dann sieht man, wenn man anfängt den nächsten zu schreiben, was man da an Gemeinsamkeiten/Unterschiede hat, ohne das man bei so etwas wie dynamisch Klassen per Funktion zu erstellen landet. Was mir ein bisschen zu viel Magie wäre, vor allem weil ich den Sinn hier nicht sehe.
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

BlackJack hat geschrieben:@Pygoscelis papua: Auch nach diesem Beitrag ist mir immer noch nicht klar was ”Gebäude” denn nun eigentlich ist. Jedes Gebäude muss woanders gebaut werden können oder Gebäudetypen müssen woanders gebaut werden können? Deine Register-Funktion erstellt Gebäude oder Gebäudetypen? Was von `self` wird in der `can_build()`-Methode denn verwendet? Nur die Position? Die könnte man auch als Argument übergeben. Dir ist klar das es auch Klassenmethoden und statische Methoden gibt? Und Metaklassen? Und das man die Trennung zwischen Gebäude und Gebäudetyp auch tatsächlich durch zwei Klassen erreichen kann? Oder vielleicht auch einfach durch eine oder mehrere Funktionen?
Ok vieleicht habe ich mich etwas unverständlich ausgedrückt, normaler weise meint Gebäude die Instanz und Gebäudetyp die Klasse.
Natürlich muss nur jeder Gebäudetyp eine woanders gebaut werden können, das hängt dann von anderen Gebäuden rundrum bzw. von den
"Landschaftlichen" Eingenschaften ab. Da die Position auch in andern Funktionen, wie z.B. on_turn_start verwendet werden wird, macht es auch snn, diese schon bein __init__ zu übergeben, da sie sich eh nicht ändert ( meine Gebäude laufen nicht :) )
für can_build wird außerdem vermutlich erst mal nichts übergeben, ich überlege gerade nur, wie die Funktion dann tatsächlich auf die Karte zugreift.
Eventuell muss die als Argument übergeben werden.

Klassenmethoden habe ich mir schon mal angeschaut, ich habe sie aber noch nicht verwendet. Sollte ich soetwas für can_build verwenden, um keine instanz haben zu müssen oder wie geht das?
Über statische Methoden habe ich noch nicht viel gehört, vieleicht könntest du mir einen guten Link senden? Sonst suche ich auch selber, etwas habe ich das auch schon gemacht.
Und Zuerst hatte ich auch Building als Metaklasse realisiert, aber hier ist eben meine Frage:
ist es Sinn der Sache jedes mal für die kleinen Änderungen (von Meta-gebäude zu einzelnem Gebäudetyp) eine Neue Klasse zu schreiben,
wie ich das jetzt verstanden habe schon. Oder?
BlackJack hat geschrieben: Eine Register-Funktion die etwas erstellt ist übrigens ein bisschen überraschend, da würde man eher erwarten das die, nun ja etwas registriert und nichts zurück gibt. Und dann vielleicht auch eine Methode auf einer Klasse ist, die die registrierten Objekte verwaltet.
Die Register Funktion erstellt die Klasse und registriert sie für die Oberklasse Game (methode von Game) (bis jetzt ist das ein ablegen in einer Liste)
um dann überprüfen zu können welche Gebäude es gibt, diese dann aufzulisten bzw. nach dem Namen dann ein Gebäudetyp zum bauen auszuwählen.
BlackJack hat geschrieben: Wenn die ”Funktionen” das Verhalten der Gebäude charakterisieren, dann willst Du entweder eine Klasse pro Gebäudetyp, wo das verhalten dann halt als Methoden umgesetzt wird, oder das Strategie-Muster wenn die Verhalten selbst noch mal parametrisiert und vielleicht auch verschieden kombiniert werden sollen.
Ich persönlich würde wohl mit einem Gebäude(typ) als ganz normale Klasse anfangen. Und dann sieht man, wenn man anfängt den nächsten zu schreiben, was man da an Gemeinsamkeiten/Unterschiede hat, ohne das man bei so etwas wie dynamisch Klassen per Funktion zu erstellen landet. Was mir ein bisschen zu viel Magie wäre, vor allem weil ich den Sinn hier nicht sehe.
Ok wie ich das jetzt verstanden habe wird jedes Gebäude eine Klasse sein die von Building (Metaklasse) erbt, vermutlich werden es erstmal auch garnicht 200 Gebäudetypen ... :) bis jetzt sind 7 in den Meilensteinen festgelegt ...

Habe ich das jetzt so richtig verstanden?
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
BlackJack

@Pygoscelis papua: Wenn jeder Gebäudetyp eine Klasse ist die von `Building` erbt, dann ist `Building` keine Metaklasse sondern einfach nur eine Basisklasse.

Letztlich kann man so allgemein, wie schon gesagt, gar nichts sagen, es ist halt nur so, dass eine Methode die Klassen dynamisch erstellt, wirklich so ziemlich am Ende des magischen Spektrums ist, und ich da insbesondere bei Anfängern so ähnlich wie bei `eval()` drauf reagiere: Das ist mit ziemlicher Sicherheit der falsche, viel zu flexible und ”magische” Ansatz. Mir ist immer noch nicht klar was gegen ganz normale Klassen für die Gebäudetypen spricht, mit Vererbung wenn man gemeinsames Verhalten in Oberklassen herausziehen kann. (Wobei „Oberklasse Game“ übrigends auch wieder total verwirrend ist, weil ich glaube (hoffe?) das war nicht wirklich so gemeint wie es sich anhört, denn `Game` ist ziemlich sicher keine Oberklasse von `Building`, denn ein Gebäude ist kein Spiel. Nehme ich jetzt mal an.
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

Ok und was ist dann eine Metaklasse?
Und meine Frage war eben ob man solche Funktionen zum erstellen verwenden sollte oder halt wirklich Unterklassen schreibt die von Building erben.
Wie ich das jetzt verstanden habe ja.

Und nein Game ist eine Andere Klasse Oberklasse ist der Falsche Ausdruck ...
Aber Building ist in so fern von Game abhängig, dass es nur dort verwendet wird,
Wenn es kein Game gibt wird auch kein Gebäude (keine Instanz von Building) erstellt.
import this
hidden python features

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

Die Test-Funktion, ob ein Gebäude gebaut werden kann, würde ich so in der Art definieren:

Code: Alles auswählen

class Building:
    def __init__(self, ...):
        # ...

    # Some methods

    @staticmethod
    def is_buildable(game_map):
        # Only concrete buildings are buildable
        return False


class Farm(Building):
    # ...

    @staticmethod
    def is_buildable(game_map):
        # Check the map
        # Return True or False
Statische Methoden sind im Grunde auf der Klasse definierte Funktionen. Sie werden ohne das self definiert, da in dem Moment noch kein Exemplar der Klasse verwendet wird. Der Aufruf kann somit auf der Klasse selbst erfolgen:

Code: Alles auswählen

Farm.is_buildable(game_map)
Ich würde die Methode nutzen, um dem Spieler bereits vorab z.B. mittels roter Markierung sagen zu können, dass das Gebäude nicht gebaut werden kann. Und in __init__ würde ich sie auch nochmal aufrufen (nur zur Sicherheit) und eine Exception werfen, wenn die Test-Methode False liefert. Übrigens, self.is_buildable(game_map) ist trotzdem möglich.
Benutzeravatar
snafu
User
Beiträge: 6857
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Pygoscelis papua hat geschrieben:Die Register Funktion erstellt die Klasse und registriert sie für die Oberklasse Game (methode von Game) (bis jetzt ist das ein ablegen in einer Liste)
um dann überprüfen zu können welche Gebäude es gibt, diese dann aufzulisten bzw. nach dem Namen dann ein Gebäudetyp zum bauen auszuwählen.
Alles nachvollziehbar, aber der Name ist ungünstig gewählt, da man unter Registrierung normalerweise versteht, dass bereits vorhandene Dinge irgendwo angemeldet werden. Wenn die Funktion zur Registrierung diese Dinge erst selbst erstellen muss, dann ist das halt ungewöhnlich. Auch hier ein Vorschlag wie ich das wahrscheinlich lösen würde:

Code: Alles auswählen

class GameMap:
    # ...

    def build_object(self, object_type, position, *optargs):
        if not object_type.is_buildable(self):
            # entsprechende Darstellung, z.B. roter Bereich
            # als eigene Methode auslagern
        else:
            # Erzeuge Exemplar von Gebäudetyp
            # Gib die Map (d.h. dich selbst) als Argument mit
            # Optionale Argumente für Anzahl, Farbe, ... möglich
            obj = object_type(self, *optargs)
            self.register(obj)
            return obj
Demnach würde man dann natürlich die Basisklasse Buiding so definieren, dass sie eine Map als Argument erwartet. Die konkreten Gebäudetypen können neben einer Map auch weitere Argumente erwarten, falls nötig. Aufruf mit dem Farm-Beispiel wäre dann:

Code: Alles auswählen

game_map.build_object(Farm, (pos_x, pos_y))

# Oder mit optionalen Argumenten
game_map.build_object(Farm, (pos_x, pos_y), weiteres_arg, noch_eins)
EDIT:
Habe Map und Positionierung vermischt. Aber ich hoffe, es ist klar geworden, was ich meinte.
BlackJack

@Pygoscelis papua: Klassen beschreiben die Eigenschaften und das Verhalten von Objekten die man aus der Klasse erstellen kann. Sind also so eine Art Bauplan für Objekte. Da Klassen selbst in Python auch Objekte sind, gibt es mit Metaklassen die Möglichkeit einen Bauplan für Klassen zu definieren. Das ist ziemlich ”magisch” und wird nicht wirklich oft verwendet, aber es ermöglicht so Sachen wie den ORM von SQLAlchemy mit den ”Deklarationen” auf Klassenebene.
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

Ok cool da hab ich mal wieder was nützliches dazu gelernt :D
Nochmal danke an snafu und BlackJack fürs Antworten, ich weiß jetzt zumindest wie ich weiter machen muss :)

Zu den Metaklassen (nur aus interesse) könntet ihr mir dafür ein Beispiel nennen (als Code am besten) ?
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
BlackJack

@Pygoscelis papua: Im Blogartikel Python metaclasses by example gibt es neben einer Erklärung auch ein paar Beispiele, unter anderem `string.Template` aus der Standardbibliothek.

Und noch ein Zitat von Python-Kernentwickler Tim Peters: „Metaclasses are deeper magic that 99% of users should never worry about. If you wonder whether you need them, you don't (the people who actually need them know with certainty that they need them, and don't need an explanation about why).“
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

Ich dachte, da es ja immer was an code auszusetzten gibt, bzw. ich garantiert keinen perfekten Code schreibe,
stelle ich den jetzt hier einfach nochmal rein, vieleicht findet ihr ja Fehler :D

Code: Alles auswählen

#!/usr/bin/python3
import random

class Game():
    def __init__(self, size, planetcount, planetmapsize, playercount, buildings):
        self.registered_buildings = {} 
        for building_class in buildings:
            self.register_building(building_class)

        self.turn = 0
        self.undetermined_commands = []

        #place planets
        self.planets = []
        for plnt in range(planetcount):
            x, y = [random.randrange(size) for i in range(2)]
            pos = (x, y)
            self.planets.append(Planet(pos, planetmapsize))
        ##init player
        if planetcount < playercount:
            raise ValueError("planetcount less then playercount")
        #give every player one planet:
        for player in range(playercount):
            self.planets[player].place_commando_center(player)
            #we can do this because all planets where placed randomly
           
    def _show_all_planets(self): #only for Test
        for plnt in self.planets:
            plnt._show()
    
    def get_player_first_planet(self, player):
        return self.planets[player]

    def build_object(self, planet, player, pos, object):
        planet = self.planets[planet] 
        if not object.is_buildable(planet, player, pos):
            return False
        field = planet.get_field(pos)
        planet.set_field(pos, [field[0], object(planet, player, pos)])
        return True

    def register_building(self, building_class):
        self.registered_buildings[building_class.__name__] = building_class

    def turn_end(self):
        while self.undetermined_commands:
            command = self.undetermined_commands.pop(random.randrange(len(
                self.undetermined_commands)))
            self.run_command(command)
        self.undetermined_commands = []

    def add_command(self, command):
        self.undetermined_commands.append(command)

    def run_command(self, command):
        name = command[0]
        if name == "build":
            self.build_object(command[1], command[2], command[3], 
                    self.registered_buildings[command[4]])


class Planet():
    def __init__(self, pos, size):
        self.x, self.y = pos
        self.map = []
        self.map_size = size
        for row in range(size):
            self.map.append([])
        for row in self.map:
            for column in range(size):
                row.append(["d", None])

    def place_commando_center(self, player):
        pos = [random.randrange(self.map_size) for i in range(2)]
        field = self.get_field(pos)
        self.set_field(pos, [field[0], CommandoCenter(self, player, pos)])

    def has_commando_center(self, player):
        for y in map:
            for x in y:
                if type(x[1]) == CommandoCenter:
                    if x[1].owner == player:
                        return True
        return False

    def get_field(self, pos):
        return self.map[pos[0]][pos[1]]
    
    def set_field(self, pos, field):
        self.map[pos[0]][pos[1]] = field
    
    def _show(self):
        print("planet", (self.x, self.y))
        for i in self.map:
            print(i)
    

class Building():
    __name__ = "Building"
    def __init__(self, planet, owner, pos):
        self.owner = owner
        self.planet = planet
    
    @staticmethod
    def is_buildable(planet, owner, pos):
        return True

class CommandoCenter(Building):
    __name__ = "Commando Center"

class House(Building):
    __name__ = "House"
    @staticmethod
    def is_buildable(planet, owner, pos):
        #TODO: make it only buildable near CommandoCenter
        return True
        

buildings = []
buildings.append(CommandoCenter)
buildings.append(House)

class SpaceShip():
    def __init__(self, pos, target):
        self.dir_vector = dir_vector
        self.pos = pos
        self.target = target

    def on_step():
        pass#NotImplementedYet





##test section
print(buildings)
g = Game(10, 3, 5, 3, buildings)
print("NEW GAME", end="\n"*3)
g.add_command(("build", 0, 0, (0, 0), "House"))
g.turn_end()
g._show_all_planets()
import this
hidden python features

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

- Überschreiben von Builtins vermeiden (z.B. von map)
- List Comprehensions nutzen, wenn möglich
- pythonische Lösungen (z.B. zip() und any()) gegenüber klassischen Ansätzen bevorzugen
- bei Factory-Methoden das erstellte Objekt liefern oder None anstatt True/False
- ...

Bei weitem nicht alles, was ich verbessern würde, aber hier ein paar Ansätze (ungetestet):

Code: Alles auswählen

#!/usr/bin/python3
from random import randrange
 
def _randpos(size):
    return randrange(size), randrange(size)

 
class Game():
    def __init__(
        self, game_size, planet_size, num_planets, num_players, building_types
    ):
        if num_players > num_planets:
            raise ValueError('num_players must not exceed num_planets')
        
        self.planets = [
            Planet(_randpos(game_size), planet_size)
            for _ in range(num_planets)
        ]
        for i, planet in zip(range(num_players), self.planets):
            planet.place_commando_center(i)
        
        self.building_types = {tp.__name__: tp for tp in building_types}
        self.turn = 0
        self.undetermined_commands = []
           
    def _show_all_planets(self):
        # Only meant for testing purposes
        for plnt in self.planets:
            plnt._show()
   
    def get_home_planet(self, player):
        return self.planets[player]
 
    def build_object(self, planet_id, player_id, position, object_type):
        planet = self.planets[planet]
        if object_type.is_buildable(planet, player_id, position):
            field = planet.get_field(position)
            obj = object_type(planet_id, player, position)
            planet.set_field(position, (field[0], obj))
            return obj
 
    def turn_end(self):
        while self.undetermined_commands:
            command = self.undetermined_commands.pop(random.randrange(len(
                self.undetermined_commands)))
            self.run_command(command)
        self.undetermined_commands = []
 
    def add_command(self, command):
        self.undetermined_commands.append(command)
 
    def run_command(self, command):
        name = command[0]
        if name == "build":
            self.build_object(command[1], command[2], command[3],
                    self.registered_buildings[command[4]])
 
 
class Planet():
    def __init__(self, position, size):
        self.position = position
        self.size = size
        self.map = [
            [('d', None) for _ in range(size)] for _ in range(size)
        ]
 
    def __repr__(self):
        class_name = type(self).__name__
        x, y = self.position
        return '{}(x={}, y={})'.format(class_name, x, y)
 
    def place_commando_center(self, player_id):
        position = _randpos(self.size)
        field = self.get_field(pos)
        commando_center = CommandoCenter(self, player_id, position)
        self.set_field(position, (field[0], commando_center))
 
    def has_commando_center(self, player_id):
        return any(
            field[1].owner == player_id and isinstance(field[1], CommandoCenter)
            for row in self.map for field in row
        )
 
    def get_field(self, position):
        x, y = position
        return self.map[x][y]
   
    def set_field(self, position, field):
        x, y = position
        self.map[x][y] = field
   
    def _show(self):
        print(self)
        print('\n'.join(self.map))
   
 
class Building():
    __name__ = "Building"
    def __init__(self, planet, owner, pos):
        self.owner = owner
        self.planet = planet
   
    @staticmethod
    def is_buildable(planet, owner, pos):
        return True
 
class CommandoCenter(Building):
    __name__ = "Commando Center"
 
class House(Building):
    __name__ = "House"
    @staticmethod
    def is_buildable(planet, owner, pos):
        #TODO: make it only buildable near CommandoCenter
        return True
       
 
buildings = []
buildings.append(CommandoCenter)
buildings.append(House)
 
class SpaceShip():
    def __init__(self, pos, target):
        self.dir_vector = dir_vector
        self.pos = pos
        self.target = target
 
    def on_step():
        pass#NotImplementedYet
 
 
 
 
 
##test section
print(buildings)
g = Game(10, 3, 5, 3, buildings)
print("NEW GAME", end="\n"*3)
g.add_command(("build", 0, 0, (0, 0), "House"))
g.turn_end()
g._show_all_planets()
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

Vielleicht sollte ich mir mal ein cheat sheet für die häufigsten Python Funktionen erstellen ... any z.B. hatte ich mir lange nicht mehr angeschaut :)
Danke für die Tipps. Ich glaub ich werde die Gebäude insgesamt in eine andere Datei auslagern, auf welche dann als Modul zugegriffen wird.
Dann kann ich mir den ganzen register-Quatsch sparen :), das wird dann mit ausgelagert ...
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
Sirius3
User
Beiträge: 18261
Registriert: Sonntag 21. Oktober 2012, 17:20

@Pygoscelis papua: für den Ablauf ist es egal, wie Du Dein Programm strukturierst; auch wenn die Gebäudetypen in einem eigenen Modul sind, brauchst Du irgendeinen „register-Quatsch“. Weil building_classes building heißen, macht es für mich schwer lesbar. register_building ist auch überflüssig, weil damit sowieso nur ein Wörterbuch in ein anderes kopiert wird. Bei build_object übergibst Du ein object, das aber eine Klasse ist. Wäre auch sonst komisch, einer Funktion, die build_object heißt, schon ein Objekt zu übergeben. Besser als direkt die Klasse wäre es, den Gebäudetypnamen zu übergeben, wozu hast Du denn registered_building_types.
Die Methode sollte dann auch build_building heißen. Du arbeitest auch noch mit zu vielen unstrukturierten Listen und magischen Indizes. Warum muß man einem set_field erst das 0. Element des ursprünglichen Feldes übergeben? Für turn_end schau Dir mal random.shuffle an. Bei run_command benutzt Du wieder eine Liste anstelle eines strukturierten Objekts. Am besten schreibst Du für jeden Befehlstyp eine eigene Klasse. In has_commando_center ist y für eine Feldzeile der falsche Name, x für ein Feld ebenso. type sollte man nicht zum Vergleichen benutzen, isinstance ist besser, eine eigene Methode is_commando_center noch besser. __name__ ist ein internes Attribut, das beim Erzeugen einer Klasse automatisch gesetzt wird. Das selbst zu setzen ist ein Fehler, da es keine Auswirkung hat. Nenn es doch einfach BUILDING_TYPE. Bisher sind Planeten und Spieler nur Nummern, was dem Game-Objekt die Aufgabe aufbürdet das alles zu verwalten. Arbeite mehr mit Objekten.
Benutzeravatar
snafu
User
Beiträge: 6857
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Genau, das wollte ich auch noch sagen: Anstatt mit 2-elementrigen Listen zu arbeiten, die man über den Index ansprechen muss, sind namedtuples viel besser. Also wie schon vorgeschlagen wurde, ruhig zu Beginn ein paar Containerklassen definieren, jeweils für Koordinaten, Feldinfo und sowas. Denkbar sind auch mehrschichtige Strukturen, sodass man ein benanntes Tupel für ein Feld hat und dieses besitzt als Attribute die Koordinaten (wieder als eigener Typ) sowie eine Info zur Bebauung.
Benutzeravatar
kbr
User
Beiträge: 1506
Registriert: Mittwoch 15. Oktober 2008, 09:27

snafu hat geschrieben:Also wie schon vorgeschlagen wurde, ruhig zu Beginn ein paar Containerklassen definieren, jeweils für Koordinaten, Feldinfo und sowas.
Noch eine Stufe vorher empfehle ich ein großes Blatt Papier, einen Bleistift, sowie einen *großen* Radiergummi. :)
Antworten