jueves, 30 de abril de 2009

Iniciando la interfaz gráfica (Parte 2)


En este artículo, agregaré un par de nuevos elementos a la interfaz para dejarla prácticamente completa. Hay que tener en cuenta que aún no he entrado en Sugar, estoy usando aún sólo GTK. Esto tiene la ventaja de que aún podemos correr el programa en equipos sin Sugar, pero aún no tenemos funcionalidad de sugar como la barra superior de la actividad o la posibilidad de integrar nuestra aplicación con el Diario de Sugar.
En la entrada anteriror, mostré cómo hacer la cuadrícula de letras y cómo ponerla dentro de una ventana GTK. Ahora que voy a colocar otros elementos como el contador y la entrada de texto, es necesario usar "Contenedores" de GTK. Los contenedores son "objetos" que me permiten colocar otros "objetos" dentro de ellos. Como en una repisa, puedo tener varias divisiones para poner elementos. Para construir la interfaz, usaré los contenedores gtk.VBox y gtk.HBox que son contenedores con espacios alineados vertical u horizontalmente respectivamente.
Primero usaré un HBox para partir la ventana en dos partes. En la izquierda voy a tener los dados y la línea de entrada de texto, y en la derecha, tendré el contador y la información de las palabras. El HBox me crea un contenedor (que llamo box1) con elementos alineados uno al lado del otro. Luego con un VBox (que llamo box2) que colocaré dentro del HBox (usando la función pack_start), podré colocar la cuadrícula sobre la entrada de texto:

mainwindow = gtk.Window(gtk.WINDOW_TOPLEVEL)
mainwindow.connect("delete_event", gtk.main_quit)
box1 = gtk.HBox()
box1.show()
mainwindow.add(box1)
box2 = gtk.VBox() # Left side panels
box2.show()
box1.pack_start(box2)
box3 = gtk.VBox() # Right side panels
box3.show()
box1.pack_start(box3)
# Create dice grid
grid = wordhuntgrid()
grid.set_size_request(250, 250)
grid.show()
box2.pack_start(grid)
# Create text entry line
word_entry = gtk.Entry()
word_entry.show()
box2.pack_start(word_entry)

Finalmente en otra caja vertical (box3), pondré el contador y otros dos elementos de texto vacíos de los que me encargaré en otra ocasión:

# Create timer display
timer_label = gtk.Label("1:00")
timer_label.modify_font(pango.FontDescription("mono 24"))
timer_label.set_size_request(100, 90)
timer_label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(60000, 5000, 5000))
timer_label.show()
box3.pack_start(timer_label)
#Create dummy Player Panel
player_panel = gtk.Label(" ")
player_panel.show()
box3.pack_start(player_panel)
#Create dummy Definitions panel
definition_panel = gtk.Label(" ")
definition_panel.show()
box3.pack_start(definition_panel)
mainwindow.show()
gtk.main()

Con este sencillo código ya tengo una ventana que se ve así en Ubuntu (Gnome):


Y así en Sugar:


Y como es usual, el código:

#! /usr/bin/env python
# -*- coding: utf-8 -*-

# GTK es la librería gráfica usada por Sugar
import gtk

# Pango es una librería para el dibujo de texto internacional
import pango

class wordhuntgrid(gtk.Table):
def __init__(self):
self.size = 4
gtk.Table.__init__(self, self.size, self.size)
self.dice_labels = []
for row in range(self.size):
dice_row = []
for column in range(self.size):
new_label = gtk.Label("A")
new_label.modify_font(pango.FontDescription("sans 32"))
new_label.show()
self.attach(new_label, \
row, row + 1, column, column + 1,
gtk.EXPAND, gtk.EXPAND)
dice_row += [new_label]
self.dice_labels += [dice_row]

mainwindow = gtk.Window(gtk.WINDOW_TOPLEVEL)
mainwindow.connect("delete_event", gtk.main_quit)
box1 = gtk.HBox()
box1.show()
mainwindow.add(box1)
box2 = gtk.VBox() # Left side panels
box2.show()
box1.pack_start(box2)
box3 = gtk.VBox() # Right side panels
box3.show()
box1.pack_start(box3)
# Create dice grid
grid = wordhuntgrid()
grid.set_size_request(250, 250)
grid.show()
box2.pack_start(grid)
# Create text entry line
word_entry = gtk.Entry()
word_entry.show()
box2.pack_start(word_entry)
# Create timer display
timer_label = gtk.Label("1:00")
timer_label.modify_font(pango.FontDescription("mono 24"))
timer_label.set_size_request(100, 90)
timer_label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(60000, 5000, 5000))
timer_label.show()
box3.pack_start(timer_label)
#Create dummy Player Panel
player_panel = gtk.Label(" ")
player_panel.show()
box3.pack_start(player_panel)
#Create dummy Definitions panel
definition_panel = gtk.Label(" ")
definition_panel.show()
box3.pack_start(definition_panel)
mainwindow.show()
gtk.main()

Imágen:
mark sebastian: http://www.flickr.com/photos/markjsebastian/210573715/

martes, 28 de abril de 2009

Iniciando la interfaz gráfica (Parte 1)



Ya tenemos una parte del motor interno de nuestro juego. Ahora, debemos presentar nuestro juego dentro de una ventana propia con todos los elementos necesarios visibles. Las ventanas además de presentar información, son el tablero de control de nuestro juego.
Como eso de usar ventanas, botones y menus es tan común, los programadores no tenemos que hacer todo el trabajo. Existen muchas herramientas libres (y que además pueden usarse desde de Python) para esto. Sugar en particular usa la biblioteca GTK, así que empezaremos a construir nuestra interfaz gráfica con ella. Para esto es necesario importar en python el módulo gtk:

import gtk

Además voy a usar Pango, que se encarga del formato y dibujo del texto. Por ahora solo lo usaré para cambiar de tamaño mis letras. Debo cargar el módulo pango con:

import pango

Antes de empezar a escribir código, he hecho un simple boceto de lo que quiero tener:
En este dibujo se ve la cuadrícula de los dados en la izquierda y una línea de entrada de texto debajo de ésta. En la derecha de la interfaz gráfica, tendremos el tiempo de juego, las palabras entradas por los jugadores (cuando logremos llegar a compartir la actividad), y abajo un panel para mostrar la definición de la palabra seleccionada (posiblemente traída de internet para no tener que empacar un diccionario completo con nuestro juego).
Además, en la barra superior habrá dos pestañas, una de "Actividad", que permite iniciar el juego, y determinar el tiempo de juego, y otra pestaña que permite ver los puntajes.
En esta entrada simplemente vamos a crear la cuadrícula de los dados.
Para hacer esta cuadrícula, voy a crear una clase que hereda la clase gtk.Table. Es decir es una tabla, con algunas características adicionales.
En cada una de las 16 casillas coloco (con la función attach) un gtk.Label que mostrará la letra (por ahora solo una "A"), y los guardaré en una lista (dice_labels) para poder manipuarlos después.


# GTK es la librería gráfica usada por Sugar
import gtk

# Pango es una librería para el dibujo de texto internacional
import pango

class wordhuntgrid(gtk.Table):
def __init__(self):
self.size = 4
gtk.Table.__init__(self, self.size, self.size)
self.dice_labels = []
for row in range(self.size):
dice_row = []
for column in range(self.size):
new_label = gtk.Label("A")
new_label.modify_font(pango.FontDescription("sans 32"))
new_label.show()
self.attach(new_label, \
row, row + 1, column, column + 1,
gtk.EXPAND, gtk.EXPAND)
dice_row += [new_label]
self.dice_labels += [dice_row]


Hemos creado aquí una clase que puede mostrarse, pero hasta que no la instanciemos, es un cascarón vacío...
Lo que haremos es crear una ventana (mainwindow) y luego agregamos nuestra cuadrícula (guardada en la variable grid)

mainwindow = gtk.Window(gtk.WINDOW_TOPLEVEL)
grid = wordhuntgrid()
mainwindow.add(grid)
grid.set_size_request(250, 250)
grid.show()
mainwindow.show()
mainwindow.connect("delete_event", gtk.main_quit)
gtk.main()


Esta parte eventualmente cambiará radicalmente, pero por ahora es útil para ver cómo funciona nuestra cuadrícula.
En Sugar todas las ventanas ocupan la pantalla completa y no tienen borde por defecto, por eso para cerrar el programa, es necesario usar las teclas "Alt"+ "Esc" al mismo tiempo (el "Esc" en una máquina XO es la X en la esquina superior izquierda).
Y ahora como siempre al final el código:

# GTK es la librería gráfica usada por Sugar
import gtk

# Pango es una librería para el dibujo de texto internacional
import pango

class wordhuntgrid(gtk.Table):
def __init__(self):
self.size = 4
gtk.Table.__init__(self, self.size, self.size)
self.dice_labels = []
for row in range(self.size):
dice_row = []
for column in range(self.size):
new_label = gtk.Label("A")
new_label.modify_font(pango.FontDescription("sans 32"))
new_label.show()
self.attach(new_label, \
row, row + 1, column, column + 1,
gtk.EXPAND, gtk.EXPAND)
dice_row += [new_label]
self.dice_labels += [dice_row]

mainwindow = gtk.Window(gtk.WINDOW_TOPLEVEL)
grid = wordhuntgrid()
mainwindow.add(grid)
grid.set_size_request(250, 250)
grid.show()
mainwindow.show()
mainwindow.connect("delete_event", gtk.main_quit)
gtk.main()


Enlaces

Imágenes

lunes, 27 de abril de 2009

Recibiendo palabras (Parte 2)

Ahora que podemos recibir las palabras entradas por el jugador, el computador debe revisar si la palabra se puede construir con las letras disponibles. ¡Este tipo de cálculos son la razón de existir de los computadores!
Para averiguar si una palabra se encuentra, tenemos que revisar primero si la primera letra está disponible. Además, debemos tratar de formar la palabra a partir de cada aparición de la letra, es decir debemos repetir el proceso para cada vez que la letra se encuentre. Para esto se escribí la función start_positions, que busca entre los dados las ubicaciones de la letra, y entrega la lista de coordenadas en las que se encuentra. Entrega una lista vacía si la letra no se encuentra.
def start_positions(letter):
positions = []
for row in range(4):
for column in range(4):
if dice[facedie[row][column]][face[row][column]] == letter:
positions.append([row, column])
return positions
Para mejorar un poco la interacción, modifico el ciclo principal para que muestre los dados después de cada intento. Este es un execelente ejemplo de lo que sucede en un proceso de desarrollo, al necesitar afinar la interacción a partir de la evaluación del uso real. Además, como la función word_is_valid ahora entregará la lista de coordenadas de la palabra formada, entonces para saber si la palabra fue encontrada, tenemos que revisar el último elemento de la lista.
while True:
print_face()
word = raw_input(">")
word = word.upper() #todo en mayusculas
if word == "X":
print "Adiós"
break
elif len(word) < 3:
print "Demasiado Corta"
else:
word_chain = word_is_valid(word)
if word_chain[-1] != False:
print "Válida", word_chain
else:
print "Inválida"
Ahora si, pasemos a la pulpa...
El proceso de encontrar la palabra consiste en a partir de la primera letra, buscar la siguiente en las casillas adyacentes, teniendo en cuenta de no buscar en casillas que no existen y sobretodo teniendo en cuenta no usar letras que ya han sido usadas. Si entre las adyacentes encontramos la siguiente letra, pasamos a esta y repetimos el mismo proceso de buscar en las adyacentes hasta que se han acabado las letras de la palabra. Si las letras se acaban y hemos logrado encontrarlas todas en línea, quiere decir que la palabra existe. Si alguna letra no la encontramos, debemos ir hacia atrás para buscar otras posibilidades. Este tipo de procesos son ideales para ser modelados en código por medio de recursión. la recursión es el proceso de llamar la función desde ella misma, hasta que alguna condición permita terminar la cadena. La función find_word es una función recursiva, que se llama a si misma mientras la palabra tenga letras. Cuando la llamamos, no le entrego la palabra completa, sino el pedazo de la palabra que falta por encontrar. De esta forma, si se han acabado las letras, sabremos que la palabra fue encontrada.
Para buscar en las casillas adyacentes, uso la matriz matrix que guarda las coordenadas relativas a la casilla. Recorro cada una de ellas en un ciclo (cada elemento es la variable transform), y en este ciclo, si la letra es encontrada, llamo recursivamente find_word.
def find_word(word, row, column, used):
matrix = [[-1, -1], [-1,0], [-1, 1],
[0, -1], [0,1],
[1, -1], [1, 0], [1,1] ]
if word == "":
return [] #fin de la palabra-cerrar recursion
letter = get_letter(word,0)
if letter == "Qu":
new_word = word[2:]
else:
new_word = word[1:]
for transform in matrix:
new_row = row + transform[0]
new_column = column + transform[1]
if new_row < 0 or new_column < 0 or \
new_row > 3 or new_column > 3 or \
used[new_row][new_column] == 1:
pass
elif dice[facedie[new_row][new_column]][face[new_row][new_column]] == letter:
used_copy = used
used_copy[new_row][new_column] = 1
return [[new_row, new_column]] + \
find_word(new_word, new_row, new_column, used_copy)
return [False]

Ya se puede ver en esta función un problema que surge de la existencia de la letra "Qu". En los dados , puede salir la letra "Qu" que debe contarse como una sola letra a pesar de contener dos letras. Esto implica que en todas las palabras que se entren la palabra es inválida si hay una "q" a la que no le sigue un "u", y además que cuando aparezca "qu" debemos tratarla como una sola letra.
Finalmente, hay que hacer algunas adiciones a la función word_is_valid para que busque la primera letra de la palabra, y a partir de esta inicie la recursion con la función find_word.
def word_is_valid(word):
letter = get_letter(word,0)
if letter == "":
return [False]
elif letter == "Qu":
new_word = word[2:]
else:
new_word = word[1:]
for position in start_positions(letter):
used = [[0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0]]
used[position[0]][position[1]] = 1
used_copy = used
word_chain = [[position[0],position[1]]] + \
find_word(new_word, position[0], position[1], used_copy)
if word_chain[-1] != False:
return word_chain
return [False]
Y listo por ahora... En la próxima empezaremos a crear la interfaz visual para nuestro programa (además de buscar un pequeño error que hace que algunas veces no se detecte la palabra correctamente....)
Como siempre aquí está el código:

import random

dice = [ ["T", "O", "E", "S", "S", "I"],
["A", "S", "P", "F", "F", "K"],
["N", "U", "I", "H", "M", "Qu"],
["O", "B", "J", "O", "A", "B"],
["L", "N", "H", "N", "R", "Z"],
["A", "H", "S", "P", "C", "O"],
["R", "Y", "V", "D", "E", "L"],
["I", "O", "T", "M", "U", "C"],
["L", "R", "E", "I", "X", "D"],
["T", "E", "R", "W", "H", "V"],
["T", "S", "T", "I", "Y", "D"],
["W", "N", "G", "E", "E", "H"],
["E", "R", "T", "T", "Y", "L"],
["O", "W", "T", "O", "A", "T"],
["A", "E", "A", "N", "E", "G"],
["E", "I", "U", "N", "E", "S"] ]

face = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]
facedie = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]

def new_dice_set():
available_dice = range(16)
for row in range(4):
for column in range(4):
facedie[row][column] = random.randint(0,len(available_dice) - 1)
available_dice.pop(facedie[row][column])

def new_shuffled_group():
for row in range(4):
for column in range(4):
face[row][column] = random.randint(0,5)

def print_face():
for row in range(4):
for column in range(4):
print dice[facedie[row][column]][face[row][column]], " ",
print ""

def get_letter(word, index):
internal_index = 0
real_index = 0
if word == "":
return ""
while internal_index < index:
letter = word[real_index]
if letter == "Q":
real_index = real_index + 1
if real_index < len(word):
letter = word[real_index]
elif letter != "U":
return ""
real_index = real_index + 1
internal_index = internal_index + 1
result = word[real_index]
if result == "Q":
real_index = real_index + 1
if real_index < len(word):
result = result + "u"
return result

def start_positions(letter):
positions = []
for row in range(4):
for column in range(4):
if dice[facedie[row][column]][face[row][column]] == letter:
positions.append([row, column])
return positions

def find_word(word, row, column, used):
matrix = [[-1, -1], [-1,0], [-1, 1],
[0, -1], [0,1],
[1, -1], [1, 0], [1,1] ]
if word == "":
return [] #fin de la palabra-cerrar recursion
letter = get_letter(word,0)
if letter == "Qu":
new_word = word[2:]
else:
new_word = word[1:]
for transform in matrix:
new_row = row + transform[0]
new_column = column + transform[1]
if new_row < 0 or new_column < 0 or \
new_row > 3 or new_column > 3 or \
used[new_row][new_column] == 1:
pass
elif dice[facedie[new_row][new_column]][face[new_row][new_column]] == letter:
used_copy = used
used_copy[new_row][new_column] = 1
return [[new_row, new_column]] + \
find_word(new_word, new_row, new_column, used_copy)
return [False]

def word_is_valid(word):
letter = get_letter(word,0)
if letter == "":
return [False]
elif letter == "Qu":
new_word = word[2:]
else:
new_word = word[1:]
for position in start_positions(letter):
used = [[0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0]]
used[position[0]][position[1]] = 1
used_copy = used
word_chain = [[position[0],position[1]]] + \
find_word(new_word, position[0], position[1], used_copy)
if word_chain[-1] != False:
return word_chain
return [False]

new_shuffled_group()
new_dice_set()

while True:
print_face()
word = raw_input(">")
word = word.upper() #todo en mayusculas
if word == "X":
print "Adiós"
break
elif len(word) < 3:
print "Demasiado Corta"
else:
word_chain = word_is_valid(word)
if word_chain[-1] != False:
print "Válida", word_chain
else:
print "Inválida"
Imágenes

viernes, 24 de abril de 2009

Recibiendo palabras (Parte 1)

Ahora que tenemos listos los dados para jugar, necesitamos al jugador...


El jugador debe poder entrar las palabras al computador con el teclado. Para esto podemos usar un ciclo "while" que permite al jugador entrar palabras hasta que se canse y entre la letra "x":
while True:
word = raw_input(">")
word = word.upper() #todo en mayusculas
if word == "X":
print "Adiós"
break
elif len(word) < 3:
print "Demasiado Corta"
elif word_is_valid(word):
print "Palabra válida"
else:
print "No válida"

Como ven, uso la función raw_input y luego convierto todas las letras a mayúsculas usando upper().
Luego reviso si se entró la letra "X" para terminar o si la palabra tiene menos de 3 letras, lo que la hace demasiado corta para el juego. Si la palabra pasa estas pruebas, es necesario luego revisar si se puede formar con los dados, para lo que defino la función word_is_valid. Esta función es la primera de nuestro programa que usa return para devolver un valor a quien la llama. Lo que hace es evaluar la palabra y da el veredicto de si es válida o no. Además esta función recibe un "argumento", es decir, al llamarla, debemos entregarle una información para que trabaje. En este caso, la información es la palabra a evaluar:
def word_is_valid(word):
letter = get_letter(word,0)
if letter == "":
return False
for row in range(4):
for column in range(4):
used = [[0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0]]
if dice[facedie[row][column]][face[row][column]] == letter:
print "Encontrada ", row, column
return True

Por ahora esta función solo revisa si la primera letra de la palabra se encuentra entre los dados, y reporta si lo está (Esta función la completaré en la parte 2 de este artículo).
En este momento surge un problema complicado, que resulta del hecho que el dado que tiene la "Q" está marcado "Qu", es decir, la Q siempre viene seguida de una U. Por esto la Q seguida de U debe contarse como una sola letra, a pesar de ser en realidad dos. Además se deben declarar inválidas las palabras que tengan una Q que no está seguida por una U, porque no es posible hacerlas con los dados. Para esto creo la función get_letter, que me permite averiguar las letras de la palabra, contando la "QU" como una sola letra:
def get_letter(word, index):
if index > len(word):
return ""
internal_index = 0
real_index = 0
while internal_index < index:
letter = word[real_index]
if letter == "Q":
real_index = real_index + 1
if real_index < len(word):
letter = word[real_index]
elif letter != "U":
return ""
real_index = real_index + 1
internal_index = internal_index + 1
result = word[real_index]
if result == "Q":
real_index = real_index + 1
if real_index < len(word):
result = result + "u"
return result

A esta función debo entregarle la palabra y el número de la letra que quiero averiguar (la primera se considera la letra 0). Esta función retorna la letra correspondiente a la posición que se pide o ninguna letra ("") si hay algún problema, como que se pidió una casilla que no existe o si hay una Q sin U.
En la segunda parte completaré la función word_is_valid para que busque entre los dados la palabra del jugador (Como está el programa todas las palabras las reporta como válidas!). Al unir lo que acabo de mostrar con nuestro programa anterior, tenemos:
import random

dice = [ ["T", "O", "E", "S", "S", "I"],
["A", "S", "P", "F", "F", "K"],
["N", "U", "I", "H", "M", "Qu"],
["O", "B", "J", "O", "A", "B"],
["L", "N", "H", "N", "R", "Z"],
["A", "H", "S", "P", "C", "O"],
["R", "Y", "V", "D", "E", "L"],
["I", "O", "T", "M", "U", "C"],
["L", "R", "E", "I", "X", "D"],
["T", "E", "R", "W", "H", "V"],
["T", "S", "T", "I", "Y", "D"],
["W", "N", "G", "E", "E", "H"],
["E", "R", "T", "T", "Y", "L"],
["O", "W", "T", "O", "A", "T"],
["A", "E", "A", "N", "E", "G"],
["E", "I", "U", "N", "E", "S"] ]

face = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]
facedie = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]

def new_dice_set():
available_dice = range(16)
for row in range(4):
for column in range(4):
facedie[row][column] = random.randint(0,len(available_dice) - 1)
available_dice.pop(facedie[row][column])

def new_shuffled_group():
for row in range(4):
for column in range(4):
face[row][column] = random.randint(0,5)

def print_face():
for row in range(4):
for column in range(4):
print dice[facedie[row][column]][face[row][column]], " ",
print ""

def get_letter(word, index):
if index > len(word):
return ""
internal_index = 0
real_index = 0
while internal_index < index:
letter = word[real_index]
if letter == "Q":
real_index = real_index + 1
if real_index < len(word):
letter = word[real_index]
elif letter != "U":
return ""
real_index = real_index + 1
internal_index = internal_index + 1
result = word[real_index]
if result == "Q":
real_index = real_index + 1
if real_index < len(word):
result = result + "u"
return result

def word_is_valid(word):
letter = get_letter(word,0)
if letter == "":
return False
for row in range(4):
for column in range(4):
used = [[0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0]]
if dice[facedie[row][column]][face[row][column]] == letter:
print "Encontrada ", row, column
return True

new_shuffled_group()
new_dice_set()
print_face()

while True:
word = raw_input(">")
word = word.upper() #todo en mayusculas
if word == "X":
print "Adiós"
break
elif len(word) < 3:
print "Demasiado Corta"
elif word_is_valid(word):
print "Palabra válida"
else:
print "No válida"

Imágenes
  • Ross Angus: http://www.flickr.com/photos/ross_angus/483602549/
  • J.Smith831: http://www.flickr.com/photos/29814869@N07/2801793552/
  • Karen Horton: http://www.flickr.com/photos/karenhorton/1583519788/

miércoles, 22 de abril de 2009

Barajando los dados


Hay que empezar por el principio.

Antes de entrar en las gráficas y mecanismos del juego como el conteo del tiempo y la revisión de palabras, hay que crear los dados. Para esto, vamos a guardarlos en una lista de Python. Una lista es simplemente eso, un lugar que guarda un elemento tras otro. Cada uno de los 16 dados es una lista de sus seis lados, por ejemplo:
["T", "O", "E", "S", "S", "I"]




(Explicaré el código por partes y al final está el código completo)

Podemos tener una sola variable que contenga todos los dados. Esta sería una lista que contiene 16 listas así:

dice = [ ["T", "O", "E", "S", "S", "I"],
["A", "S", "P", "F", "F", "K"],
["N", "U", "I", "H", "M", "Qu"],
["O", "B", "J", "O", "A", "B"],
["L", "N", "H", "N", "R", "Z"],
["A", "H", "S", "P", "C", "O"],
["R", "Y", "V", "D", "E", "L"],
["I", "O", "T", "M", "U", "C"],
["L", "R", "E", "I", "X", "D"],
["T", "E", "R", "W", "H", "V"],
["T", "S", "T", "I", "Y", "D"],
["W", "N", "G", "E", "E", "H"],
["E", "R", "T", "T", "Y", "L"],
["O", "W", "T", "O", "A", "T"],
["A", "E", "A", "N", "E", "G"],
["E", "I", "U", "N", "E", "S"] ]



Ahora que ya tenemos dados, necesitamos una cuadrícula para poner los dados. Para esto también podemos crear listas.
Lo que haré es tener dos cuadrículas de 4 por 4. Una contendrá el número del dado que está en esa posición y la otra contiene el número del lado del dado que está visible:
face = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]
facedie = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]

Ahora crearé una función para colocar los dados en una posición. Como no se puede repetir un dado, creo una lista llamada "available_dice", en la que guardo los números de 0 a 15, para cuando salga un dado quitarlo de la lista. Luego para cada posición (row, column) de la lista "facedie", selecciono al azar que dado queda en esa casilla así:
def new_dice_set():
available_dice = range(16)
for row in range(4):
for column in range(4):
facedie[row][column] = random.randint(0,len(available_dice) - 1)
available_dice.pop(facedie[row][column])

Luego hago algo similar con los lados. Para cada casilla, "lanzo" el dado, para que quede uno de sus lados al azar. En la lista "face" guardo el lado visible en cada una de las casillas:
def new_shuffled_group():
for row in range(4):
for column in range(4):
face[row][column] = random.randint(0,5)

Listo, con esto tenemos la cajita y los dados. ¡Pero aún no los vemos!
Para terminar por ahora haré una sencilla función que muestre los dados en la pantalla, y llamaré las funciones anteriores:
def print_face():
for row in range(4):
for column in range(4):
print dice[facedie[row][column]][face[row][column]], " ",
print ""

new_shuffled_group()
new_dice_set()
print_face()

Ya debes poder ver algo como:
T   B   P   K
X Y E O
M U K N
P I E E

Nos vemos en la próxima. Aquí está el código completo:
import random

dice = [ ["T", "O", "E", "S", "S", "I"],
["A", "S", "P", "F", "F", "K"],
["N", "U", "I", "H", "M", "Qu"],
["O", "B", "J", "O", "A", "B"],
["L", "N", "H", "N", "R", "Z"],
["A", "H", "S", "P", "C", "O"],
["R", "Y", "V", "D", "E", "L"],
["I", "O", "T", "M", "U", "C"],
["L", "R", "E", "I", "X", "D"],
["T", "E", "R", "W", "H", "V"],
["T", "S", "T", "I", "Y", "D"],
["W", "N", "G", "E", "E", "H"],
["E", "R", "T", "T", "Y", "L"],
["O", "W", "T", "O", "A", "T"],
["A", "E", "A", "N", "E", "G"],
["E", "I", "U", "N", "E", "S"] ]

face = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]
facedie = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]

def new_dice_set():
available_dice = range(16)
for row in range(4):
for column in range(4):
facedie[row][column] = random.randint(0,len(available_dice) - 1)
available_dice.pop(facedie[row][column])

def new_shuffled_group():
for row in range(4):
for column in range(4):
face[row][column] = random.randint(0,5)

def print_face():
for row in range(4):
for column in range(4):
print dice[facedie[row][column]][face[row][column]], " ",
print ""


new_shuffled_group()
new_dice_set()
print_face()


Fuentes
Blog de Matthew James Taylor: De este blog tomé el contenido de los dados.
Créditos Imágenes

Iniciando el juego "Caza Palabras"

He decido iniciar el desarrollo de un juego sencillo y documentar el proceso para desentrañar los misterios de Sugar (Nada mejor que explicar un tema mientras se aprende para asegurarse que se entendió...)
Quiero hacer un juego basado en Boggle. Este juego consiste en encontrar palabras en una cuadrícula de 4x4, que contiene dados con letras.
Iré contando paso a paso los detalles de la construcción, y espero llegar a terminarlo...
¿Qué se necesita para seguir el desarrollo? Sólo un editor de texto para escribirlo y Python, el lenguaje de programación para correrlo. En un XO, se puede usar Pippy.


(Foto por squacco: http://www.flickr.com/photos/squeakywheel/505768192/ Licencia CC-BY-SA)