Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/maze.py
blob: c540e6c52ff51037443b1761e2218335c142eb78 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#!/usr/bin/python
# coding=utf-8
"""Un juego de laberinto de línea de comandos simple.

Este juego está diseñado como un tutorial de cómo dividir un problema en
componentes y resolver cada uno individualmente. El juego en sí es bastante
aburrido y carece de una interfaz agradable.

En el juego, hay un solo jugador atrapado en un laberinto generado
aleatoriamente. El jugador sólo conoce su nombre, ubicación actual y los
caminos disponibles para ellos a partir de su ubicación actual. El grafo se
representa como una red de enlaces entre nodos, almacenados como un diccionario
que mapea un nodo de una lista de nodos que está vinculado. Cada enlace es
direccional, pero el grafo es simétrica. Se permiten los ciclos y enlaces
reflexivos. Todos los nodos se tratan como una tupla dar su posición X e Y en
una cuadrícula entera de tamaño dado. (0, 0) es el nodo de inicio y
(size, size) es el nodo de acabado. Esta es una forma estándar para representar
un grafo en un programa.

Por último, la clase MazeGame debe ser escrito para unir ambas Maze y Player.
Esta clase debe implementar la interfaz de usuario, preguntando repetidamente
el jugador que se mueven van a hacer, a continuación, actualizar la ubicación
del jugador en consecuencia.

La implementación de este requiere un método que se añade a Maze para recuperar
una lista de los nodos que legítimamente el jugador puede mover a partir de su
ubicación actual. Para evitar romper la compartimentación y la codificación de
la conducta del jugador en la clase Maze, el método toma un nodo como un
parámetro en lugar de acceder a la ubicación del reproductor directamente.

Una característica del lenguaje a destacar es el uso de "while...else" in
MazeGame.run(). El código en el bloque 'else' se ejecutará inmediatamente
después de la última iteración del bucle 'while', pero no se ejecutará si la
ejecución sale de la curva mediante la ejecución de la instrucción 'break'.
Esto significa que el "que ha ganado" el mensaje no se imprimirá si el usuario
sale el juego antes de tiempo.
"""

import random


class Player(object):
    """Un jugador en el juego.

    Esto almacena la ubicación actual del jugador como (X, Y) tupla de
    coordenadas, y también el nombre del jugador. El nombre del jugador no se
    puede cambiar después de la construcción.
    """
    def __init__(self, name):
        self._name = name
        self._location = (0, 0)

    def get_location(self):
        """Obtener la ubicación actual del jugador."""
        return self._location

    def set_location(self, location):
        """Ajustar la ubicación actual del jugador."""
        self._location = location

    location = property(get_location, set_location)

    def get_name(self):
        """Obtener el nombre del jugador."""
        return self._name

    name = property(get_name)


class Maze(object):
    """El laberinto el jugador debe navegar alrededor.

    Esto representa un laberinto en forma de grafo simétrico. El grafo no es
    necesariamente acíclico, y puede tener enlaces reflexivos. Asimismo, no es
    necesariamente plana, y puede contener enlaces duplicados. El grafo se
    limita a una cuadrícula número entero entre el nodo de inicio en (0, 0) y
    el nodo de acabado al (size, size). La cuadrícula debe ser de un tamaño por
    lo menos 1.
    """
    def __init__(self, size):
        self._links = {}
        self._finish_node = (size, size)
        self.__generate_maze(size)

    # Este atributo indica que la función no utiliza el parámetro 'self' en
    # absoluto, por lo que el parámetro puede ser eliminado.
    @staticmethod
    def __generate_num_nodes(size):
        """Elija un número aleatorio de nodos de tener en el laberinto.

        Esto será de entre 0,25*size*size y 0,5*size*size por lo que el
        laberinto no es demasiado lleno o demasiado vacío. El nodo de inicio y
        el nodo de acabado no se incluyen en este número.
        """
        max_nodes = size * size  # para una cuadrícula totalmente lleno
        return random.randint(int(0.25 * max_nodes), int(0.5 * max_nodes))

    @staticmethod
    def __generate_num_links(num_nodes):
        """Elija un número aleatorio de los enlaces a tener entre los nodos en
        el laberinto.

        Esto será de entre 0,1*num_nodes*num_nodes y 0,4*num_nodes*num_nodes
        por lo que el laberinto no es totalmente disjuntos o completamente
        conectado.
        """
        max_links = num_nodes * num_nodes  # para un grafo conectado
        return random.randint(int(0.1 * max_links), int(0.4 * max_links))

    def __generate_maze(self, size):
        """Generar un nuevo laberinto, sobrescribir el contenido de
        self._links.

        El laberinto usará coordenadas (0, 0) a (como máximo) (size, size).
        Puede contener enlaces reflexivos y duplicados. Todas las conexiones
        están garantizados para ser simétrico (es decir, un par de enlaces
        dirigidos). El laberinto en sí misma no es garantía de ser plana.
        """

        # Generar una lista de nodos que forman los nodos del laberinto.
        # Asegúrese de que el inicio y el final nodos están en la lista. No se
        # preocupan por duplicados, si un nodo se duplica en los nodos,
        # significa que va a ser el doble de probabilidades de ser elegido en
        # el código de enlace de la generación siguiente.
        nodes = [self.start_node]
        num_nodes = self.__generate_num_nodes(size)
        for _ in range(num_nodes):
            nodes.append((random.randint(0, size), random.randint(0, size)))

        # Ponga el nodo final al final de la lista, así que puede recorrer la
        # lista para generar la ruta ganadora, a continuación.
        nodes.append(self.finish_node)

        # Explícitamente generar una ruta desde el principio hasta el final,
        # para asegurarse de que hay al menos una solución para el laberinto.
        # Esto está garantizado para incluir tanto el nodo inicial y el nodo de
        # acabado, como el valor inicial de current_index es 0 (el índice del
        # nodo de inicio), y un bucle no termina hasta current_index es al
        # menos igual al índice del nodo de acabado, lo que significa que
        # next_index debe haber sido el índice del nodo final al menos una vez.
        self._links = {}
        current_index = 0  # garantía de ser el nodo de inicio
        while current_index < len(nodes) - 1:
            step = random.randint(1, len(nodes) - current_index - 1)
            next_index = current_index + step
            self.__add_link(nodes[current_index], nodes[next_index])
            current_index = next_index

        # Añadir otros enlaces entre los nodos. Estos pueden conectar hasta la
        # ruta ganadora, puede formar rutas adicionales ganadores, o puede ser
        # desconectado.
        for _ in range(self.__generate_num_links(num_nodes)):
            current_index = random.randint(0, num_nodes - 1)
            next_index = random.randint(0, num_nodes - 1)
            self.__add_link(nodes[current_index], nodes[next_index])

    def __add_link(self, point_a, point_b):
        """Añadir un par de enlaces entre point_a y point_b y vice-versa."""
        self.__add_one_way_link(point_a, point_b)
        self.__add_one_way_link(point_b, point_a)

    def __add_one_way_link(self, point_a, point_b):
        """Añadir un enlace unidireccional entre point_a y point_b."""
        links = self._links.get(point_a, [])
        links.append(point_b)
        self._links[point_a] = links

    def get_start_node(self):
        """Obtener el nodo de inicio para el jugador.

        Este nodo está garantizado para existir en el laberinto.
        """
        return (0, 0)

    start_node = property(get_start_node)

    def get_finish_node(self):
        """Obtener el nodo de acabado para el jugador.

        Este nodo está garantizado para existir en el laberinto.
        """
        return self._finish_node

    finish_node = property(get_finish_node)

    def get_adjacents_to_node(self, node):
        """Obtener una lista de nodos que son adyacentes al nodo dado.

        Esta lista puede incluir el propio nodo, como se permiten enlaces
        reflexivos. Si se pasa un nodo no válido como parámetro, se devuelve
        una lista vacía.
        """
        return self._links.get(node, [])


class MazeGame(object):
    """Aplicación de línea de comandos del juego del laberinto.

    Esto conecta el Player al Maze y ejecuta el bucle de entrada que maneja
    opciones de movimiento del jugador.
    """
    def __init__(self):
        name = raw_input('Introduzca su nombre: ')
        size = int(raw_input('Introduzca el tamaño del laberinto (5–20): '))

        self._player = Player(name)
        self._maze = Maze(size)

    def __choose_next_location(self):
        """Preguntar al usuario para la siguiente ubicación para moverse.

        El lugar elegido se devuelve como una tupla (X, Y), que está
        garantizado para ser un nodo válido en el laberinto. Si el usuario opta
        por salir del juego, se devuelve None.
        """

        # Obtenga una lista de los nodos conncted a la ubicación actual del
        # jugador.
        adjacent_nodes = \
            self._maze.get_adjacents_to_node(self._player.location)

        # Imprimir las opciones, numeradas.
        option = 1
        print('Usted se encuentra en %s. Puede acceder a:' %
            str(self._player.location))
        for node in adjacent_nodes:
            print(' %u) %s' % (option, str(node)))
            option += 1

        print(' 0) Salir del juego')

        # Pida al usuario la opción que quieran.
        chosen_option = int(raw_input('Ingrese el número de su elección: '))
        if chosen_option == 0:
            return None  # salir del juego
        else:
            return adjacent_nodes[chosen_option - 1]

    def run(self):
        """Ejecutar el juego hasta que el jugador gana o se cierra."""
        while self._player.location != self._maze.finish_node:
            chosen_node = self.__choose_next_location()
            if chosen_node is None:
                break  # salir del juego

            self._player.location = chosen_node
        else:
            print('Has ganado, %s!' % self._player.name)


# Ejecutar el juego.
MazeGame().run()