Apuntes_Python/05_diez_proyectos/07-minesweeper/buscaminas.py

179 lines
6.6 KiB
Python
Raw Normal View History

2022-12-24 22:41:20 -03:00
import random
import re
import os
# Limpiar Pantalla
cl = lambda: os.system('clear') if os.name == 'posix' else os.system('cls')
class Tablero:
"""
Creación de objeto tablero que representa al juego 'BuscaMinas'
"""
def __init__(self, dim_size, num_bombs):
self.dim_size = dim_size
self.num_bombs = num_bombs
# Crear tablero (helper function?)
self.tablero = self.hacer_nuevo_tablero() # Plantar bombas
self.asignar_valores_a_tablero()
# Inicializar un set() para hacer seguimiento de las locaciones descubiertas
# se almacenan tuplas (fila, columna) en este set
self.excavado = set()
def hacer_nuevo_tablero(self):
"""
Cronstruye un nuevo tablero según dim_size y num_bombs.
Basado en una lista de listas (represantación 2-D)
"""
# generar nuevo tablero
tablero = [[None for _ in range(self.dim_size)] for _ in range(self.dim_size)]
# Contruye algo como esto:
# [ [None, None, .., None],
# [None, None, .., None],
# [... ...],
# [None, None, .., None] ]
# Plantar bombas
bombas_plantadas = 0
while bombas_plantadas < self.num_bombs:
loc = random.randint(0, self.dim_size ** 2 - 1)
fil = loc // self.dim_size
col = loc % self.dim_size
if tablero[fil][col] == '💥️':
continue # ignorar
tablero[fil][col] = '💥️' # plantar bomba
bombas_plantadas += 1
return tablero
def asignar_valores_a_tablero(self):
"""
Asigna números enteros de 0 a 8 a cada espacio vacío, según
cuantas bombas existan al rededor
"""
for f in range(self.dim_size):
for c in range(self.dim_size):
if self.tablero[f][c] == '💥️':
continue # si ya es una bomba
self.tablero[f][c] = self.get_num_bombs_cercanas(f, c)
def get_num_bombs_cercanas(self, fil, col):
"""
Itera cada cuadrante cercano y suma el número de bombas
'top left: (fil-1, col-1) top middle: (fil-1, col) top right: (fil-1, col+1)'
' left: ( fil, col-1) right : ( fil, col+1)'
'bot left: (fil+1, col-1) bot middle: (fil+1, col) bot right: (fil+1, col+1)'
"""
num_bombs_cerca = 0
for f in range( max(0, fil-1), min(self.dim_size-1, fil+1) +1):
for c in range( max(0, col-1), min(self.dim_size-1, col+1) +1):
if f == fil and c == col:
continue # posicion original, no checkear
if self.tablero[f][c] == '💥️':
num_bombs_cerca += 1
return num_bombs_cerca
def excavar(self, fil, col):
"""
Busca minas en la posicion, retorna True si tiene exito
False si es una bomba (gameover), si no hay bombas cercanas
la busqueda sigue recursivamente
"""
self.excavado.add((fil, col)) # seguimiento de locaciones 'excavadas'
if self.tablero[fil][col] == '💥️':
return False
elif self.tablero[fil][col] > 0:
return True
# self.tablero[fil][col] == 0
for f in range( max(0, fil-1), min(self.dim_size-1, fil+1) +1):
for c in range( max(0, col-1), min(self.dim_size-1, col+1) +1):
if (f, c) in self.excavado:
continue # no 'excava' donde ya se ha 'excavado'
self.excavar(f, c)
return True
def __str__(self):
"""
Retorna un string con la represantación del tablero de juego
"""
tablero_visible = [[None for _ in range(self.dim_size) ] for _ in range(self.dim_size)]
for fil in range(self.dim_size):
for col in range(self.dim_size):
if (fil, col) in self.excavado:
tablero_visible[fil][col] = str(self.tablero[fil][col])
else:
tablero_visible[fil][col] = ' '
# Formato de string para represantación de tablero
string_rep = ''
anchos = [] # anchos maximo de columna para print
for idx in range(self.dim_size):
columnas = map(lambda x: x[idx], tablero_visible)
anchos.append(len(max(columnas, key = len)))
# print the csv strings
indices = [i for i in range(self.dim_size)]
indices_fil = ' '
cells = []
for idx, col in enumerate(indices):
format = '%-' + str(anchos[idx]) + "s"
cells.append(format % (col))
indices_fil += ' '.join(cells)
indices_fil += ' \n'
for i in range(len(tablero_visible)):
fila = tablero_visible[i]
string_rep += f'{i} |'
cells = []
for idx, col in enumerate(fila):
format = '%-' + str(anchos[idx]) + "s"
cells.append(format % (col))
string_rep += ' |'.join(cells)
string_rep += ' |\n'
str_len = int(len(string_rep) / self.dim_size)
string_rep = indices_fil + '-'*str_len + '\n' + string_rep + '-'*str_len
return string_rep
def jugar(dim_size = 10, num_bombs = 10):
"""
Comenzar juego
Paso 1 : Crear tablero y plantar bombas
Paso 2 : Mostrar tablero y preguntar donde excavar
Paso 3a: Si se elige el lugar donde hay una bomba, muestra Game Over
Paso 3b: Si el cuadrante no es una bomba, excava recursivamente hasta un cuadrante
cercano a una bomba.
Paso 4 : Repetir pasos 2 y 3 hasta que no queden espacios para excavar -> Victoria!
"""
# Paso 1
safe = True
tablero = Tablero(dim_size ,num_bombs)
# Paso 2
while len(tablero.excavado) < tablero.dim_size ** 2 - num_bombs:
cl()
print(tablero)
# 3, 4 o 3, 4 o 3, 4
user_input = re.split(',(\\s)*', input("Ingresa coordenada para buscar\n-> fila, columna : "))
fil, col = int(user_input[0]), int(user_input[-1])
if fil < 0 or fil >= tablero.dim_size or col < 0 or col >= dim_size:
print("Posición invalida, prueba otra.")
continue
# si es valida
safe = tablero.excavar(fil, col)
if not safe:
break # game over
if safe:
print("\n 🎉FELICITACIONES!!🎊️ Ganaste 😎️ ")
else:
cl()
tablero.excavado = [(f, c) for f in range(tablero.dim_size) for c in range(tablero.dim_size)]
print(tablero)
print("\n Perdiste! 👻️ \n")
if __name__ == '__main__':
jugar()