2010-09-16 13 views
6

Mam ten kod dla obiektu. Ten obiekt jest ruchem, który możesz wykonać w grze z nożycami do papierów skalnych. Teraz obiekt musi być zarówno liczbą całkowitą (dla dopasowania protokołu), jak i ciągiem dla wygody pisania i przeglądania.Jak mogę utworzyć ten kod Pythonic

class Move: 
    def __init__(self, setMove): 
     self.numToName = {0:"rock", 1:"paper",2:"scissors"} 
     self.nameToNum = dict(reversed(pairing) for pairing in self.numToName.items()) 
     if setMove in self.numToName.keys(): 
      self.mMove=setMove 
     else: 
      self.mMove=self.nameToNum.get(setMove) #make it to a number 

    def defeats(self): 
     return Move((self.mMove-1)%3) 
    def losesTo(self): 
     return Move((self.mMove+1)%3) 
    def tiesWith(self): 
     return self 

    #Operator overloading 
    def __eq__(A,B): 
     return A.mMove==B.mMove 
    def __gt__(A,B): 
     return A.defeats(B) 
    def __lt__(A,B): 
     return A.losesTo(B) 
    def __ge__(A,B): 
     return A>B or A==B 
    def __le__(A,B): 
     return A<B or A==B 

    def __str__(self): 
     return self.numToName.get(self.mMove); 

    def __int__(self): 
     return self.mMove; 

Teraz jestem trochę nowy dla Pythona, pochodzącego z tła C i Java. Wielką rzeczą w pytonie jest to, że istnieje tylko jeden prawidłowy sposób na zrobienie czegoś. Kolejna rzecz nie martwi się o typ. Jestem dość wyraźnie martwiąc się o typ tutaj.

Nie jestem więc pewien, jaki jest prawidłowy sposób obsługi tych obiektów. W tej chwili mam obiekt, który jest jednym z 3 typów (lub więcej, ale nie jestem pewien, co by to zrobiło). Może zamiast tego powinienem używać obiektów różnych klas? i sprawić, by stały się singletonami? Również mój obiekt jest obecnie modyfikowalny po utworzeniu, co jest złe w mojej głowie.

A więc ten kod Pythonic i jak mogę go bardziej elegancko? (Wydaje mi się, że jest to dobry przykład do użycia, aby pomóc mi ustalić, co sprawia, że ​​dobry kod Pythona przepraszam, jeśli wydaje się nieco otwarty)

+0

Jeśli chcesz understan d więcej o rzeczywisty problem/gry: http://progcomp.ucc.asn.au/FrontPage Ruchy to drobiazg, pomyślałem sobie po prostu losowo, że chciałbym stworzyć typ, jako lepszy sposób na ich repressent. –

Odpowiedz

11

Dla mnie pojęcie kodu "pytonowego" naprawdę sprowadza się do idei, że kiedy zrozumiesz, jaki problem próbujesz rozwiązać, kod prawie sam się pisze. W tym przypadku, bez martwienia się o głębsze abstrakcje graczy, gier, rzutów itp., Masz następujący problem: istnieje pewna liczba typów ruchów, każdy z imieniem, z ustalonymi regułami, dla których ruchy pokonują inne porusza się i musisz znaleźć sposób na zdefiniowanie ruchów i dowiedzieć się, który ruch wygrywa w porównaniu.

Kiedy czytam twój kod, nie widzę od razu tego problemu, widzę dużo dodatkowej myśli, która weszła w sam kod, znajdując reprezentacje typów, wykonując arytmetyczne sztuczki i ogólnie wymuszając problem na strukturze kodu , a nie na odwrót. Sugeruję coś takiego:


class Move: 
    TYPES = ['rock', 'paper', 'scissors'] 
    BEATS = { 
    'rock': ['scissors'], 
    'paper': ['rock'], 
    'scissors': ['paper'] 
    } 

    def __init__(self, type): 
    if type not in self.TYPES: 
     raise Exception("Invalid move type") 
    self.type = type 

    def __str__(self): 
    return self.type 

    def __cmp__(self, other): 
    if other.type in self.BEATS[self.type]: 
     return 1 
    elif self.type in self.BEATS[other.type]: 
     return -1 
    else: 
     return 0 

I gotowe. Możesz wrzucić wszystkie inne dodatki itp., Ale to jest po prostu lukier, kluczowy problem jest rozwiązany, a kod jest czytelny, elastyczny, łatwy do rozszerzenia itp. To naprawdę myślę, że "pythonic" oznacza.

+1

i jeśli naprawdę potrzebujesz liczby całkowitej, użyj: def __int __ (self): return self.TYPES.index (self.type) – gmarcotte

2

Cóż, masz tylko trzy możliwe ruchy, prawda? Dlaczego po prostu nie reprezentować ich jako struny? Wydaje się, że jedynym powodem, dla którego masz numery, jest implementacja porównań (tj. Które pokonują) z pewną "sprytną" matematyką, ale szczerze mówiąc nie sądzę, że warto. Wszystko, czego naprawdę potrzebujesz, to funkcja, aby określić, który z nich jest zwycięzcą w każdej możliwej porównania:

def winner(move0, move1): 
    if move0 == move1: 
     return None 
    elif (move0 == 'rock' and move1 == 'scissors') or \ 
     (...paper vs. rock...) or \ 
     (...scissors vs. paper...): 
     return 0 
    else: 
     return 1 

po prostu składa się ceni powrót None, 0 i 1 jako przykład, można użyć, co jest właściwe dla Twoja sytuacja.

„Proste jest lepsze niż skomplikowane,” Zen Pythona linii 3 ;-)

+0

jak już powiedziałem. "Potrzebuję liczb całkowitych do dopasowania protokołu" Są inne rzeczy na świecie, które rzucają ruchy jak ja, i które rzucam ruchami, a mechanizm, który wiesz, co robisz, jest protokołem, który opiera się na tych liczbach. Strzały są tylko dla wygody. –

+0

OK, cóż, jeśli muszą to być liczby całkowite, po prostu użyj liczb całkowitych zamiast łańcuchów, to znaczy, w mojej odpowiedzi wpisz "rock" -> "0" itd. To naprawdę nic nie zmienia. Jeśli chcesz wydrukować reprezentację ciągów, dla czegoś tak prostego mogę po prostu utworzyć funkcję zwracającą ciąg odpowiadający danemu ruchowi: 'def as_string (move): return {0:" rock ", 1:" paper " , 2: "nożyczki"} [ruch] ' –

0

nie jestem pewien, że gra jest wydobywane wystarczająco dobrze. Ruch to wydarzenie, w którym bierze dwóch graczy. Innymi słowy, ruch nie jest graczem, a gracz nie jest ruchem. Co sądzisz o tym:

# notice that the element k+1 defeats element k 
THROWS = ['paper', 'scissors', 'rock'] 

class Player(object): 
    def __init__(self, name, throws): 
    # name the player 
    self.name = name 
    # the throws are contained a priori 
    self.throws = throws 

    def throw(self): 
    # a throw uses (and removes) the first element of the throws 
    # list 
    return self.throw_value(self.throws.pop(0)) 

    def throw_value(self, what): 
    if what in [0,1,2]: 
     # if the throw is a legal int, return it 
     return what 
    if what in THROWS: 
     # if the throw is a legal str, return the 
     # corresponding int 
     return THROWS.index(what) 
    # if none of the above, raise error 
    raise ValueError('invalid throw') 

class Game(object): 
    def __init__(self, player_1, player_2): 
    # a game has two players 
    self.player_1 = player_1 
    self.player_2 = player_2 

    def go(self, throws=3): 
    # a "go" of the game throws three times 
    for _ in range(throws): 
     print self.throw() 

    def throw(self): 
    # a throw contains the rules for winning 
    value_1 = self.player_1.throw() 
    value_2 = self.player_2.throw() 

    if value_1 == value_2: 
     return 'draw' 

    if value_1 > value_2: 
     return self.player_1.name 

    return self.player_2.name 

if __name__ == "__main__": 
    juan = Player("Juan", ['rock', 0, 'scissors']) 

    jose = Player("Jose", [1, 'scissors', 2]) 

    game = Game(juan, jose) 

    game.go() 
+0

Jeszcze nie czytałem kodu, ale tak, ruch nie jest graczem. Gra jest prowadzona przez inną aplikację, Gracze mogą być zapisani w dowolnym języku. Komunikują się w grę z liczbami 0,1,2 na stdio. Gra mówi im, kiedy wygrały lub przegrały. Zobacz: http://progcomp.ucc.asn.au/FrontPage Po prostu używam klasy ruchu do mojego wewnętrznego użytku, aby śledzić zachowanie innych graczy. –

+0

Rozwiązanie, które proponuję, działa również w tym przypadku. Interfejs "GRA" działa niezależnie od tego, kto przenosi gracza. – Escualo

1
mv = {"Scissor":0, "Rock":1, "Paper":2} 
def winner(m1, m2): 
    result = "Tie" if m1 == m2 else max(m1, m2) if abs(m1 - m2) != (len(mv) - 1) else min(m1, m2) 
    return mv.keys()[mv.values().index(result)] if result in mv.values() else result 

Napisałem to udowodnić pojęcia: z 5 linii i prawie nie ma orientacji obiektu można osiągnąć podaną wynik, papier; skała; nożycowy.

Słownik liczb/ciągów znaków. Jeśli wpiszesz liczby, twój wynik będzie nazwą zwycięskiego ciągu. Ważność wygranej jest sekwencyjna (< b < c < a), więc możesz po prostu wykonać kontrolę odległości, aby określić potrzebę obejścia sekwencji. Dodałem "Tie", ponieważ jest to oczywisty przypadek, ale naprawdę konstruuje grę z graczami i wszystko jest trywialne dzięki tej metodzie. Teraz, jeśli chcesz zagrać w Paper, Rock, Scissors, Lizard, Spock, będziemy potrzebować refaktora.

+0

Wow. Lubię to, o ile nie muszę wyjaśniać, co robi, nie wiedząc z góry, co robi. ;) –

+0

Powinien istnieć jeden - a najlepiej tylko jeden - czysty sposób na zrobienie tego. Chociaż może to nie być oczywiste na początku, chyba że jesteś Holendrem. – Gabriel

2

Oto krótka wersja, która werbalizuje wynik.

def winner(p1, p2): 
    actors = ['Paper', 'Scissors', 'Rock'] 
    verbs = {'RoSc':'breaks', 'ScPa':'cut', 'PaRo':'covers'} 
    p1, p2 = actors.index(p1), actors.index(p2) 
    winner, looser = ((p1, p2), (p2, p1))[(1,0,1)[p1 - p2]] 
    return ' '.join([actors[winner], 
        verbs.get(actors[winner][0:2] + actors[looser][0:2], 
           'ties'), 
        actors[looser]]) 

Zaletą tej konstrukcji jest to widoczne, gdy rozszerzony na Rock, Paper, Scissors, jaszczurka Spock

def winner(p1, p2): 
    actors = ['Paper', 'Scissors', 'Spock', 'Lizard', 'Rock'] 
    verbs = {'RoLi':'crushes', 'RoSc':'breaks', 'LiSp':'poisons', 
      'LiPa':'eats', 'SpSc':'smashes', 'SpRo':'vaporizes', 
      'ScPa':'cut', 'ScLi':'decapitate', 'PaRo':'covers', 
      'PaSp':'disproves'} 
    p1, p2 = actors.index(p1), actors.index(p2) 
    winner, looser = ((p1, p2), (p2, p1))[(1,0,1,0,1)[p1 - p2]] 
    return ' '.join([actors[winner], 
        verbs.get(actors[winner][0:2] + actors[looser][0:2], 
           'ties'), 
        actors[looser]]) 

>>> winner("Rock", "Scissors") 
'Rock breaks Scissors' 
>>> winner("Rock", "Spock") 
'Spock vaporizes Rock' 
>>> winner("Spock", "Paper") 
'Paper disproves Spock' 
>>> winner("Lizard", "Scissors") 
'Scissors decapitate Lizard' 
>>> winner("Paper", "Paper") 
'Paper ties Paper' 
+0

+1 - Wynik ogólny jest całkiem niezły. Zwłaszcza te czasowniki. – Geoff