0

Cómo generar mazmorras procedurales con Python – Parte IV: Módulos

[Volver a Parte III]

¡Hola de nuevo, mis queridos Señores Oscuros! En esta cuarta parte váis a ver cómo construir una mazmorra aleatoria en Python a partir de módulos. En vez de generar las habitaciones y luego conectarlas con pasadizos como vimos en la primera parte, hoy tendremos un conjunto de habitaciones prefabricadas (módulos) que vamos a ir copiando sobre el mapa, conectándolas con otras habitaciones previamente colocadas.
 

Planning

Cada habitación será un objeto de una clase con dos atributos: una matriz 2D con el contenido de la habitación y un array de coordenadas que serán los posibles puntos de acceso a la habitación. Crearemos varios objetos que serán los distintos tipos de habitaciones, y los guardaremos dentro de un vector. Este vector será como una “paleta de habitaciones”.

Estas son algunas de las habitaciones que definiremos. Aquí, las casillas oscuras representan muros y los posibles accesos están en amarillo.

 

Para construir la mazmorra, partiremos de una matriz llena de “tierra”, que será nuestro mapa, y empezaremos colocando una habitación en una posición al azar.

Elegimos una habitación y la colocamos sobre el mapa. Forzaremos que la primera habitación sea una que disponga de muchos accesos.

 

Después, elegiremos uno de los accesos de esta primera habitación. Escogeremos también una habitación al azar de la “paleta” y uno de sus accesos.

Elegimos el acceso norte de la primera habitación (en verde). Elegimos también una habitación de la paleta al azar y uno de sus accesos (en azul).

 

Colocaremos esta nueva habitación sobre el mapa, conectando los dos accesos.

Los accesos de las dos habitaciones tendrán que quedar superpuestos.

Se repite este proceso por cada nueva habitación: elegimos uno de los accesos disponibles, elegimos una habitación de la paleta y una de sus entradas, y la colocamos sobre el mapa haciendo coincidir las dos entradas.

 

Cada vez que coloquemos una nueva habitación, sus posibles accesos se guardarán en un vector de “entradas libres”. También habrá un segundo vector de “entradas usadas”. Cada vez que se vaya a colocar una nueva habitación se elegirá una posición del vector “entradas libres”. Si consigue colocarse la nueva habitación, se moverá esta posición del vector de “entradas libres” al vector de “entradas usadas”. Esto evitará que el programa intente conectar las nuevas habitaciones a accesos que ya han sido utilizados previamente.

El algoritmo sólo intentará conectar nuevas habitaciones a entradas que estén libres.

Este proceso se repetirá hasta llegar a un cierto número de habitaciones (o hasta que sea imposible colocar más).

Finalmente se convertirán todas las posiciones del vector de “entradas usadas” en espacios vacíos/puertas. ¡Et voilá! Tenemos una mazmorra lista para ser poblada con todo tipo de monstruos y horrores transmundanos:

 


¿Cómo conectamos las habitaciones?

La idea está muy bien, pero… ¿cómo copiamos las habitaciones de tal forma que las dos entradas elegidas queden conectadas?

Supongamos que acabamos de colocar la primera habitación y hemos elegido el acceso superior (A). También hemos elegido una habitación de la “paleta” y una de sus entradas (B).

La 1ª habitación (abajo) y la nueva habitación que queremos colocar (arriba).

 

El punto más importante de este tutorial será construir una función que nos permita copiar la matriz de la habitación sobre la matriz del mapa, pero respetando que las posiciones A y B queden superpuestas.

 

Cuándo copiemos la matriz de la habitación sobre el mapa, empezaremos por la posición [0,0] y la vamos a ir copiando por filas. El truco estará en conocer la posición del mapa sobre la que hay que colocar la posición [0,0] de la nueva habitación, teniendo en cuenta que las casillas A y B tienen que solaparse.

Esto se hace con un desplazamiento. Si ponemos A = [Ax, Ay] y B = [Bx, By], entonces la posición [x,y] del mapa sobre la que habrá que empezar a copiar la matriz será

x = AxBx
y = AyBy


1. Código básico

Antes de empezar es vital poner algo de música de fondo mientras trabajamos, así que hoy os propongo algo bastante adecuado para lo que hacemos…

Bien, manos a la obra. Al igual que en las partes anteriores vamos a empezar con un programa básico que genere la mazmorra y la dibuje por la terminal con carácteres de texto, y después lo ampliaremos para que funcione con Pygame.

Para este proyecto recomiendo utilizar Python 3. También necesitaremos la librería Numpy y, como es lógico, Pygame.

Separaremos el programa en tres ficheros:

  1. ‘habitacion.py’, que se encargará de definir una clase para las habitaciones. También creará varios objetos de esta clase para los distintos tipos de habitación, y contendrá los vectores de la “paleta”, las “entradas libres” y las “entradas usadas”.
  2. ‘generador.py’, que contendrá todas las funciones para generar el mapa, incluyendo la función copiar(), que copia las habitaciones de la forma que hemos visto antes.
  3. ‘main.py’, que llamará las funciones de los otros ficheros para generar nuestra mazmorra. También se encargará de dibujarla en la pantalla de la terminal.

Recordad que estos tres ficheros tienen que estar en el mismo directorio en el momento que vayamos a ejecutarlos.

 

1.1 – Código ‘habitacion.py’

Este será el código para el fichero “habitacion.py”. ¡Revisad bien el tabulado una vez lo tengáis copiado!


#-------CLASE HABITACION---------

class Habitacion:
	matriz = [] #Almacena el contenido de la habitacion
	entradas = [] #Almacena los posibles accesos a la habitacion

	def __init__(self, matriz, entradas):
		self.matriz = matriz
		self.entradas = entradas

	def get_matriz(self):
		return self.matriz

	def get_entradas(self):
		return self.entradas




# ----------------------------------------------------
# A partir de aqui construimos varios tipos de habitacion
# y las guardamos en un array.
# ----------------------------------------------------

#Definir algunas habitaciones...
hab1 = Habitacion( [
	[2,2,2,2,2],
	[2,1,1,1,2],
	[2,1,1,1,2], 
	[2,1,1,1,2],
	[2,2,2,2,2]
	], [[0,2],[2,0],[4,2],[2,4]])

hab2 = Habitacion([
	[0,0,2,2,2,2,2,2,2,2],
	[0,2,2,1,1,1,1,1,1,2],
	[2,1,1,1,1,1,1,1,1,2], 
	[2,1,1,1,1,1,1,1,1,2],
	[2,1,1,1,1,1,1,1,1,2],
	[0,2,2,1,1,1,1,1,1,2],
	[0,0,2,2,2,2,2,2,2,2]
	], [[3, 9]])

hab3 = Habitacion([
	[2,2,2,2,2],
	[2,1,1,1,2],
	[2,1,2,1,2], 
	[2,1,1,1,2],
	[2,2,2,2,2]
	], [[0,2],[2,0],[4,2],[2,4]])

hab4 = Habitacion([
	[0,2,2,2,2],
	[2,2,1,1,2],
	[2,1,1,1,2], 
	[2,2,1,1,2],
	[0,2,2,2,2]
	], [[2,4]])

hab5 = Habitacion([
	[0,2,2,2,2,2,2,2,2],
	[2,2,1,1,2,1,1,1,2],
	[2,1,1,1,1,1,1,1,2], 
	[2,2,1,1,2,1,1,1,2],
	[0,2,2,2,2,2,2,2,2]
	], [[4,6],[0,6],[2,8]])

hab6 = Habitacion([
	[0,2,2,2,2,0],
	[2,2,1,1,2,2],
	[2,1,1,1,1,2],
	[2,1,1,1,1,2],
	[2,1,1,1,1,2],
	[2,1,1,1,1,2],
	[2,1,1,1,1,2],
	[2,1,1,1,1,2],
	[2,2,1,1,2,2],
	[0,2,2,2,2,0],
	], [[4,0],[4,5]])

hab7 = Habitacion([
	[0,0,2,2,2,2,2,0,0],
	[0,2,2,1,1,1,2,2,0],
	[2,2,1,1,1,1,1,2,2],
	[2,1,1,1,2,1,1,1,2],
	[2,1,1,2,2,2,1,1,2],
	[2,1,1,1,2,1,1,1,2],
	[2,2,1,1,1,1,1,2,2],
	[0,2,2,1,1,1,2,2,0],
	[0,0,2,2,2,2,2,0,0]
	], [[0,4], [8,4],[4,8],[4,0]])


hab8 = Habitacion([
	[2,2,2,2,2,0,0],
	[2,1,1,1,2,0,0],
	[2,1,1,1,2,0,0],
	[2,1,1,1,2,2,2],
	[2,1,1,1,1,1,2],
	[2,1,1,1,1,1,2],
	[2,1,1,1,1,1,2],
	[2,1,1,1,2,2,2],
	[2,1,1,1,2,0,0],
	[2,1,1,1,2,0,0],
	[2,2,2,2,2,0,0]
	], [[0,2],[10,2],[5,6]])

hab9 = Habitacion([
	[0,0,2,2,2,2,2,0,0],
	[0,0,2,1,1,1,2,0,0],
	[0,0,2,1,1,1,2,0,0],
	[2,2,2,1,1,1,2,2,2],
	[2,1,1,1,1,1,1,1,2],
	[2,1,1,1,1,1,1,1,2],
	[2,1,1,1,1,1,1,1,2],
	[2,2,2,1,1,1,2,2,2],
	[0,0,2,1,1,1,2,0,0],
	[0,0,2,1,1,1,2,0,0],
	[0,0,2,2,2,2,2,0,0]
	], [[0,4],[10,4],[5,8],[5,0]])

hab10 = Habitacion([
	[0,0,0,0,2,0,0,0,0],
	[0,0,2,2,1,2,2,0,0],
	[0,0,2,1,1,1,2,0,0],
	[2,2,2,1,1,1,2,2,2],
	[2,1,1,1,1,1,1,1,2],
	[2,1,1,1,1,1,1,1,2],
	[2,1,1,1,1,1,1,1,2],
	[2,2,2,1,1,1,2,2,2],
	[0,0,2,1,1,1,2,0,0],
	[0,0,2,1,1,1,2,0,0],
	[0,0,2,2,2,2,2,0,0]
	], [[10,4],[5,8],[5,0]])

hab11 = Habitacion([
	[0,2,2,2,0],
	[0,2,1,2,0],
	[0,2,1,2,0],
	[2,2,1,2,2],
	[2,1,1,1,2],
	[2,1,1,1,2],
	[2,1,1,1,2],
	[2,2,2,2,2]
	], [[0,2]])

hab12 = Habitacion([
	[0,0,0,2,2,2,2,2],
	[2,2,2,2,1,1,1,2],
	[2,1,1,1,1,1,1,2],
	[2,2,2,2,1,1,1,2],
	[0,0,0,2,2,2,2,2]
	], [[2,0]])

hab13 = Habitacion( [
	[0,2,2,2,0],
	[2,2,1,2,2],
	[2,1,1,1,2], 
	[2,2,1,2,2],
	[0,2,2,2,0]
	], [[0,2],[2,0],[4,2],[2,4]])

hab14 = Habitacion( [
	[0,2,2,2,0],
	[2,1,1,2,2],
	[2,1,1,1,2], 
	[2,1,1,2,2],
	[0,2,2,2,0]
	], [[2,0]])

hab15 = Habitacion([
	[0,0,2,2,2,2,2,2,2],
	[0,0,2,1,1,1,1,1,2],
	[0,0,2,1,2,2,2,2,2],
	[2,2,2,1,1,1,2,2,2],
	[2,1,1,1,1,1,1,1,2],
	[2,1,1,2,2,2,1,1,2],
	[2,1,1,1,1,1,1,1,2],
	[2,2,2,1,1,1,2,2,2],
	[0,0,2,1,2,2,2,2,2],
	[0,0,2,1,1,1,1,1,2],
	[0,0,2,2,2,2,2,2,2]
	], [[1,8],[5,8],[9,8]])


#Array para almacenar las habitaciones posibles
paleta_habitaciones = [hab1, hab2, hab3, hab4, hab5, hab6, hab7, hab8, hab9, hab10, hab11, hab12, hab13, hab14, hab15]

 

Como podéis ver, se empieza por definir una clase “Habitacion”. Esta clase esta compuesta por:

  • Una matriz con el contenido de la habitación. Esta vez, los espacios con ‘0’ son tierra, ‘1’ es el interior de la habitación y ‘2’ serán los muros.
  • Un array que contiene las posibles entradas a la habitación.
  • Constructor y getters para ambos atributos.

En la segunda mitad del código se definen varios objetos de esta clase que serán los distintos tipos de habitación que podrá tener nuestra mazmorra.

Al final se guardan todas las habitaciones dentro del array ‘paleta_habitaciones’.

 

1.2 – Código ‘generador.py’

Este es el código para el fichero “generador.py”, que contiene las funciones para generar el mapa.

"""
	CÓDIGO DEL GENERADOR

	Aquí se definen las funciones necesarias
	para generar el mapa.

"""


import numpy as np
import random
from habitacion import *


def copiar(hab, mapa, pos_mapa, pos_hab):

	# Copia una habitación de la paleta sobre el mapa,
	# de tal forma que las casillas 'pos_mapa' y 'pos_hab'
	# de la matriz del mapa y de la habitación coincidan

	#Calcular la posicion de la habitacion sobre el mapa
	fila = pos_mapa[0]-pos_hab[0]
	columna = pos_mapa[1]-pos_hab[1]

	#Copiar el contenido de la matriz de la habitacion sobre el mapa
	for f in range(fila, fila+len(hab)):
		for c in range(columna, columna+len(hab[0])):

			#Guardamos el valor de la casilla
			valor_casilla = hab[f-fila][c-columna]

			# Solo copiamos los valores diferentes de 0. De esta
			# forma no se destruyen los muros de las habitaciones
			# previamente colocadas:
			if valor_casilla != 0:
				mapa[ f ][ c ] = valor_casilla

def check(hab, mapa, pos_mapa, pos_hab):

	# Comprueba si hay suficiente espacio para colocar una nueva
	# habitacion.

	#Calcular la posicion de la habitacion sobre el mapa
	fila = pos_mapa[0]-pos_hab[0]
	columna = pos_mapa[1]-pos_hab[1]

	#Comprobar si se sale de rango
	if fila < 0 or columna < 0 or fila >= len(mapa) or columna >= len(mapa[0]):
		return False
	if (fila+len(hab)) >= len(mapa) or (columna+len(hab[0])) >= len(mapa[0]):
		return False

	# Comprobar si el area de la nueva habitacion choca con el interior de alguna otra
	# (recuerda que el valor '1' indica el interior de una habitacion)
	return not (1 in mapa[fila:(fila+len(hab)), columna:(columna+len(hab[0]))])


def generar_mapa(nfilas, ncols, nhab):

	# Genera una mazmorra aleatoria con unas dimensiones (nfilas, ncols) y
	# un número de habitaciones (nhab) determinados.

	#Inicializar mapa y llenarlo de 'tierra'
	mapa = np.zeros((nfilas,ncols))


	#Vectores para las entradas libres y entradas utilizadas
	lista_entradas_libres = []
	lista_entradas_usadas = []



	# --- A partir de aquí se empieza a generar la mazmorra ----


	# Elegir posicion al azar en el mapa y colocar la primera habitacion
	while 1:
		#Elegir una posicion al azar para la habitación
		fila = random.randint(0, nfilas-1)
		col = random.randint(0, ncols-1)

		#(elegimos la habitacion 1 porque tiene muchas entradas)
		if check(hab1.get_matriz(), mapa, [fila, col], [2,2]):
			copiar(hab1.get_matriz(), mapa, [fila, col], [2,2])
			for i in hab1.get_entradas():
				lista_entradas_libres.append([fila-2+i[0], col-2+i[1]])
			break

	# Colocar el resto de habitaciones
	while nhab > 0:
	
		# Para no entrar en un bucle infinito, hay 200 intentos para colocar
		# cada nueva habitación:
		intentos = 0
		while intentos < 200:

			#Elegir una posición y una habitación de la "paleta"
			posicion_mapa = random.choice(lista_entradas_libres)
			habitacion = random.choice(paleta_habitaciones)

			# Elegir una de las posibles entradas de la nueva habitación,
			# que se intentará conectar con una de las habitaciones previamente colocadas
			entrada = random.choice(habitacion.get_entradas())

			# Comprobar si se puede colocar la nueva habitación, haciendo coincidir
			# las casillas 'posicion_mapa' y 'entrada'
			if check(habitacion.get_matriz(), mapa, posicion_mapa, entrada):

				#Si hay suficiente espacio libre, colocar la habitación
				copiar(habitacion.get_matriz(), mapa, posicion_mapa, entrada)
				lista_entradas_usadas.append(posicion_mapa)
				lista_entradas_libres.remove(posicion_mapa)
			
				#Restar 1 al contador de habitaciones
				nhab -= 1

				# Añadir las entradas de la nueva habitacion a la lista de posiciones posibles
				# para colocar una nueva habitacion
				for i in habitacion.get_entradas():
					if i not in lista_entradas_libres:
						lista_entradas_libres.append([posicion_mapa[0]-entrada[0]+i[0], posicion_mapa[1]-entrada[1]+i[1]])

			#Si no hay espacio para una nueva habitacion, sumar +1 a los intentos
			else:
				intentos += 1

			#Si se llega a 200 intentos, ya no se colocarán más habitaciones
			if intentos == 200:
				nhab = 0

	#Convertir todas las entradas usadas en espacios libres
	for i in lista_entradas_usadas:
		mapa[i[0],i[1]] = 1

	return mapa


 

Estas funciones son:

  • copiar(), que recibe como parámetros un objeto de la classe “Habitacion”, la matriz del mapa, y las dos posiciones que tienen que solaparse (en forma de vector de enteros [nfila, ncolumna] ).
  • check(), que comprueba si hay suficiente espacio para colocar una nueva habitación. Utiliza el mismo método que la función copiar() para calcular el desplazamiento.
  • generar_mapa(), que recibe como parámetros las dimensiones del mapa y el número máximo de habitaciones, y va colocando habitaciones sobre el mapa. Para no entrar en un bucle infinito hemos establecido un máximo de 200 intentos para colocar cada nueva habitación.

 

1.3 – Código ‘main.py’

Por último, el fichero “main.py” llamará a la función para generar el mapa y lo dibujará por la terminal. Esta vez no pediremos al usuario que introduzca las dimensiones a mano, pero podéis hacerlo si queréis.

#----CÓDIGO DEL MAIN----
import numpy as np
import sys
from generador import *


#Definir las dimensiones del mapa
nfilas = 30
ncols = 30

#Definir el maximo de habitaciones
nhab = 11

#Generar el mapa
mapa = generar_mapa(nfilas, ncols, nhab)

#Dibujar el mapa con carácteres ASCII
for fila in mapa:
	for elemento in fila:
		if elemento == 2:
			sys.stdout.write("# ") #Pared
		elif elemento == 1:
			sys.stdout.write(". ") #Interior de habitación
		else:
			sys.stdout.write("  ") #Espacio vacío
	print("")


 

Ejecutar el programa

Como he dicho al principio, los tres ficheros Python deben estar en el mismo directorio a la hora de ejecutarse. Si has copiado bien cada parte verás que al ejecutar el ‘main.py’ aparece una mazmorra dibujada con carácteres por la Terminal:

Tres mazmorras diferentes generadas con nuestro algoritmo.


2. Código con Pygame

En primer lugar, descargad y descomprimid esta carpeta con sprites (son los mismos que hemos utilizado la Parte I de esta serie). La carpeta ’tiles’ tiene que quedar en el mismo directorio que los tres ficheros de Python.

Después, cambiad el código de “main.py” por este otro:


"""
	---CÓDIGO PYGAME---
	Dibuja una mazmorra aleatoria con Pygame
"""


import sys
import pygame
from generador import *



#----Función para dibujar el mapa en una ventana de Pygame----

def dibujarMapa(nfilas, ncols, mapa, gameDisplay, casillax, casillay):
 
	#Resetear la pantalla
        gameDisplay.fill((25,25,25)) 
 
        #Recorrer todo el array del mapa y dibujarlo
        for fila in range(nfilas):
            sys.stdout.write("\n")
            for col in range(ncols):
 
                #Interior de habitacion
                if mapa[fila][col] == 1:
                    sys.stdout.write(". ")
                    tile = pygame.image.load('tiles/floor1.bmp').convert_alpha()
                    gameDisplay.blit(tile, (col*casillax,fila*casillay))
 
                #Espacio vacio
                if mapa[fila][col] == 0:
                    sys.stdout.write("  ")
                    if fila != 0:
                        if mapa[fila-1][col] == 2 or mapa[fila-1][col] == 3:
                            tile = pygame.image.load('tiles/abyss.bmp').convert_alpha()
                            gameDisplay.blit(tile, (col*casillax,fila*casillay))
                #Pared de habitacion   
                elif mapa[fila][col] == 2:
                    sys.stdout.write("# ")
                     
                    #Si en la casilla de abajo hay una pared, habra que colocar un muro de tipo 2
                    if mapa[fila+1][col] == 2:
                        tile = pygame.image.load('tiles/wall2.bmp').convert_alpha()

                    #De lo contrario, un muro de tipo 1
                    else:
                        tile = pygame.image.load('tiles/wall1.bmp').convert_alpha()
                        gameDisplay.blit(tile, (col*casillax,fila*casillay))
                     
                    gameDisplay.blit(tile, (col*casillax,fila*casillay))
 
                    # Para dar el efecto de 2.5D, las paredes van a tapar una parte de la casilla que tienen
                    # detras (siempre que no sea otro muro)
                    if mapa[fila-1][col] != 2:
                        image = pygame.image.load('tiles/wall_border_alpha.bmp')
                         
                        #Pintar objetos con alpha es un poco mas complicado...
                        surface = pygame.Surface(image.get_size())
                        key = (255,0,255)
                        surface.fill(key, surface.get_rect())
                        surface.set_colorkey(key)
                        surface.blit(image, (0,0))
                        surface.set_alpha(255)
 
                         
                        gameDisplay.blit(surface, (col*casillax,(fila-1)*casillay))
                 
        pygame.display.update()



#-----Aquí se genera el mapa------

#Dimensiones del mapa
nfilas = 30
ncols = 30

#Numero maximo de habitaciones
nhab = (nfilas*ncols)/80

#Generar el mapa
mapa = generar_mapa(nfilas, ncols, nhab)




#----A partir de aquí dibujamos el mapa con pygame----


#Dimensiones (en px) del dibujo de cada casilla
casillax = 16
casillay = 16
 
#Dimensiones de la ventana de pygame donde se dibuja el mapa
ancho_ventana = ncols*16
alto_ventana = nfilas*16
 
#Crear la ventana y cambiar su nombre
gameDisplay = pygame.display.set_mode((ancho_ventana, alto_ventana))
gameDisplay2 = pygame.display.set_mode((ancho_ventana, alto_ventana))
pygame.display.set_caption('Dungeon Party!')
     
#Iniciar pygame
pygame.init()
 
     
#Dibujar la ventana con el mapa
dibujarMapa(nfilas, ncols, mapa, gameDisplay, casillax, casillay)
 
 
#Esperar a que el jugador se canse de mirar el mapa...
print("\nCierra la ventana de Pygame para detener el programa")
continuar = True
while continuar:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            continuar = False
 
pygame.quit()


 

Como véis, esta vez hemos inicializado una ventana de Pygame y hemos separado la parte del código que dibuja el mapa en una función aparte. El funcionamiento es análogo al que hemos venido haciendo en las otras partes de la serie.

Al ejecutar el “main.py”, veréis que se abre la ventana de Pygame con el mapa de la mazmorra dibujada:


Conclusiones

Hoy hemos aprendido a crear una mazmorra aleatoria a partir de módulos. Esta es una técnica muy útil porque combina lo mejor del diseño manual de niveles con la incertidumbre de la generación procedural. Al estar hechos por un humano, los módulos tendrán mucha más variedad que si los generamos con algoritmos desde cero, pero su disposición sobre el mapa nunca será la misma.

A pesar de que el código que hemos hecho hoy es muy simple, este concepto es muy parecido al que utilizan juegos como Binding of Isaac, X-Com 2 o Warframe  para generar sus mapas.

Salvando las distancias obvias, juegos como Warframe también disponen de un conjunto de módulos prefabricados que se conectan para crear cada nivel. Después, los tesoros y enemigos que contienen se colocan al azar.

El próximo día veremos cómo poblar la mazmorra añadiendo tesoros y monstruos, y también aprenderemos algunos métodos sencillos para crear variaciones en el terreno. ¡Hasta la próxima!


[Ir a Parte I]
[Ir a Parte II]
[Ir a Parte III]

Gl4r3

Brillante, luminosa y cegadora a veces, Glare es tan artista como técnica. Le encanta dar rienda suelta a sus módulos de imaginación y desdibujar los ya de por si delgados límites que separan el mundo de la electrónica y el arte. Su mayor creación hasta la fecha es un instrumento capaz de convertir los colores y la luz en música. Cuándo sus circuitos no están trabajando en una nueva obra electrónica, le gusta dedicar sus ciclos a la lectura o a documentar sus invenciones para beneficio de los humanos. Sus artilugios favoritos son aquellos que combinan una funcionalidad práctica con un diseño elegante y artístico.

Deja un comentario

avatar