2010-09-16 12 views
6

Así que tengo este código para un objeto. Ese objeto es un movimiento que puedes hacer en un juego de tijera de papeles de roca. Ahora, el objeto debe ser un número entero (para hacer coincidir un protocolo) y una cadena para facilitar la escritura y visualización.¿Cómo puedo hacer que este código 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; 

Ahora soy un poco nuevo en python, proveniente de C y Java. Una gran ventaja de python es que solo hay una forma correcta de hacer algo. Otra cosa no es preocuparse por el tipo. Estoy muy explícitamente preocupado por el tipo aquí.

Así que no estoy seguro de cuál es la forma correcta de manejar estos objetos. Por el momento tengo un objeto que ser uno de los 3 tipos (o más, pero no estoy seguro de lo que haría) ¿Tal vez en su lugar debería utilizar los objetos de diferentes clases? y hacerlos singletons? También mi objeto es actualmente modificable después de la creación, lo cual es algo malo en mi mente.

¿Este es el código Pythonic, y cómo puedo hacerlo más elegante? (Creo que este es un buen ejemplo para usar, para ayudarme a determinar qué es lo que hace un buen código python. Lo siento si parece un poco abierto)

+0

Si desea understan d más sobre el problema real/juego: http://progcomp.ucc.asn.au/FrontPage Las movidas son algo menor para las que al azar pensé que me gustaría crear un tipo, como una mejor forma de represtarlas. –

Respuesta

11

Para mí, el concepto de código es "pitónico" realmente se reduce a la idea de que una vez que comprenda qué problema está tratando de resolver, el código casi se escribe solo. En este caso, sin preocuparse por las abstracciones más profundas de los jugadores, los juegos, los lanzamientos, etc., tiene el siguiente problema: hay un cierto número de tipos de movimientos, cada uno con un nombre, con reglas establecidas para las cuales los movimientos ganan otro se mueve, y necesita encontrar una forma de definir movimientos y descubrir qué movimiento gana en una comparación.

Cuando leo su código, no veo este problema de inmediato, veo una gran cantidad de pensamiento adicional en el código en sí, encontrar representaciones de tipo, hacer trucos aritméticos y generalmente forzar el problema en un marco de código , en lugar de a la inversa. Así que sugeriría algo como:


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 

Y listo. Puede agregar todos los demás accesos, etc., pero en realidad es solo una guinda, el problema central está resuelto y el código es legible, flexible, fácil de extender, etc. Eso es realmente lo que creo que significa "pitónico".

+1

y si realmente necesita un número entero, use: def __int __ (self): return self.TYPES.index (self.type) – gmarcotte

2

Bueno, solo tienes tres movimientos posibles, ¿verdad? ¿Por qué no solo representarlos como cadenas? Parece que la única razón por la que tienes los números es para implementar las comparaciones (es decir, cuál derrota cuál) con algunas matemáticas "inteligentes", pero la verdad es que no creo que valga la pena. Todo lo que realmente necesita es una función para determinar cuál es el ganador en cada comparación posible:

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 

acabo de componer el retorno valora None, 0 y 1 como ejemplo, podría utilizar lo que sea apropiado para tu situación.

"Simple es mejor que complejo," El Zen de la línea de Python 3 ;-)

+0

como dije. "Necesito los enteros para hacer coincidir un protocolo" Hay otras cosas en el mundo que están lanzando movimientos como yo, y en las que lanzo movimientos, y el mecanismo que usted sabe lo que se le está haciendo es este protocolo que se basa en esos números. Las picaduras son solo por conveniencia. –

+0

Bien, bien si tienen que ser enteros, solo usa enteros en lugar de cadenas, es decir, sustituye ''rock'' ->' 0' etc. en mi respuesta. Realmente no cambia nada. Si desea imprimir una representación de cadena, para algo así de simple podría hacer una función para devolver la cadena correspondiente a un movimiento dado: 'def as_string (mover): return {0:" rock ", 1:" paper " , 2: "tijeras"} [mover] ' –

0

no estoy seguro de que el juego se abstrae lo suficientemente bien. Un movimiento es un evento que requiere dos jugadores. En otras palabras, un movimiento no es un jugador, y el jugador no es un movimiento. ¿Qué opinas sobre esto:

# 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

Todavía no he leído tu código, pero sí, un movimiento no es un jugador. El juego está siendo ejecutado por otra aplicación, Los jugadores pueden escribirse en cualquier idioma. Se comunican al juego con números 0,1,2 sobre stdio. El juego les dice entonces si ganaron o perdieron. Ver: http://progcomp.ucc.asn.au/FrontPage Solo estoy haciendo una clase de movimientos para mi uso interno para alinear cómo se comportan los demás jugadores –

+0

La solución que propongo también funciona en ese caso. La interfaz de "GAME" funciona independientemente de quién mueva al jugador. – 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 

de escribir este para probar el concepto: con 5 líneas y casi nada de la orientación a objetos se puede lograr el resultado indicado, papel; rock; cortar con tijeras.

Un diccionario de números/cadenas. Si pasa los números, su resultado será el nombre de la cadena ganadora. La validez de la ganancia es secuencial (a < b < c < a) por lo que puede simplemente hacer una verificación de distancia para determinar la necesidad de anular la secuencia. He agregado "Tie" ya que es un caso obvio, pero realmente construir el juego con jugadores es trivial con este método. Ahora, si quieres jugar a Paper, Rock, Scissors, Lizard, Spock tendremos que refactorizar.

+0

Wow. Me gusta algo así, siempre y cuando no tenga que explicar lo que hace sin saber de antemano lo que hace. ;) –

+0

Debería haber una, y preferiblemente solo una, forma obvia de hacerlo. Aunque esa forma puede no ser obvia al principio a menos que seas holandés. – Gabriel

2

Aquí hay una versión corta que verbaliza el resultado.

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]]) 

La ventaja de esta estructura es evidente cuando se expande para cubrir piedra, papel, tijeras, lagarto, 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 - El resultado generalizado es bastante bueno. Especialmente los verbos. – Geoff

Cuestiones relacionadas