martes, 12 de mayo de 2009

Bichos


El algoritmo recursivo tenía un bicho. O sea un error de programación...
Lo que sucedía es que cuando una letra tenía a su alrededor dos veces la letra siguiente de la palabra, solo aceptaba la palabra si se podía construir desde la primera. Mejor, vean esta imágen:

En este caso, la palabra "LAMI" (si existiera...) sería tomada como válida, pero la palabra "LAMP" no, porque "LAMP" usa la segunda "M" alrededor de la "A". Para corregir el error, fue necesario modificar la función find_word para que quedara así:

def find_word(self,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 = self.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 and new_column >= 0 and \
new_row < 4 and new_column < 4 and \
used[new_row][new_column] != 1 and \
self.dice[self.facedie[new_row][new_column]][self.face[new_row][new_column]] == letter:
used_copy = used
used_copy[new_row][new_column] = 1
new = self.find_word(new_word, new_row, new_column, used_copy)
if new != [False]:
return [[new_row, new_column]] + new
return [False]

Ya he hecho un commit y un push a GIT, así que el archivo completo está aquí:
http://git.sugarlabs.org/projects/wordhunt/repos/mainline/blobs/raw/e70b6bb86d4abdb34532055e948a3b0560e303e3/wordhunt.py
Hasta ahora hemos usado python y pygtk, que son requerimientos para hacer una actividad para Sugar, pero aún no es una actividad exclusivamente de Sugar. Lo que llevamos puede correr sin problema en cuaquier sistema que tenga python y pygtk. A partir de la próxima entrada usaremos Sugar (finalmente!).
Imágen:
e3000: http://www.flickr.com/photos/e3000/2480983620/

sábado, 9 de mayo de 2009

Dando un hogar al código

Como dije en la anterior entrada, es la última vez que pongo el código en el blog.
Esto no quiere decir que no habrá más código...
Como el código se está haciendo largo y complejo, se hace necesario partirlo en varios archivos y darles luego un hogar para que vivan su vida de código.
Seguramente se han preguntado por qué he usado variables y escrito los comentarios en inglés, si estoy haciendo esta actividad en español... La razón es que como voy a compartir mi actividad con otros desarrolladores, y es posible que en algún momento tengan que modificar mi actividad, lo más práctico es tenerla en inglés para que la mayoría de los desarrolladores (que hablan casi todos inglés) puedan hacerlo.
SugarLabs tiene un servicio muy práctico para alojar código, que permite a cualquier persona, de forma muy sencilla alojar su código en los servidores de SugarLabs.
El código de computadores usualmente se aloja no como páginas o texto normal de Internet sino usando un Sistema de control de versiones, que permite a diferentes personas acceder, modificar y revisar el código, pudiendo ver los cambios históricos que se han hecho a cada archivo. SugarLabs usa un sistema de control de versiones llamado GIT, y un portal web que hace de interfaz a GIT llamado gitorious.
Las fuentes alojadas en SugarLabs se encuentran en: http://git.sugarlabs.org/. Aquí podemos consultar la información sobre proyectos, actividades y desarrolladores. Podemos, por ejemplo, buscar proyectos para ver su estado y podemos visitar el portal de actividades desde el que se pueden bajar actividades para Sugar.

Creando nuestro proyecto en SugarLabs
Un repositorio es un lugar donde podemos alojar nuestras fuentes. Crear nuestro repositorio en SugarLabs es sencillo, pero hay que seguir una serie de pasos. Primero debemos crear una cuenta en SugarLabs. Cuando hayamos entrado nuestros datos en el formulario, recibiremos un email de de Gitorious y nuestra cuenta quedará activada cuando sigamos el enlace proporcionado en el email.
Yo ya tengo el mío aquí: Wordhunt.
Para poder garantizar la seguridad y autenticar la identidad de las personas que modifican el código, es necesario crear unas llaves de seguridad SSH. Para esto podemos usar OpenSSH. Hay bastantes tutoriales en Internet que explican la creación de llaves como este.
Una vez hemos creado nuestro par de llaves, debemos agregar la llave pública (la llave privada nunca debe salir de nuestras manos!) yendo a nuestro perfil dentro de SugarLabs y seleccionando "Add SSH key".
Una vez tenemos una llave dentro de SugarLabs, ya podemos crear nuestro proyecto. Para hacerlo, vamos a gitorious y seleccionamos "Project" en la barra superior. Este enlace nos lleva a una lista de proyectos. En esta página hay un enlace para crear nuevo proyecto. Aquí damos nombre a nuestro proyecto y otros datos como las categorías, la descripción y la licencia de nuestro proyecto. Cuando pulsemos el botón inferior "Create project", habremos creado un proyecto en SugarLabs.

Usando GIT
No haré una introducción ni una explicación de GIT aquí, ya que es largo y complejo, pero si explicaré los pasos que se deben seguir para usar git con los repositorios de SugarLabs. Asumo tambiémn que ya está instalado git (que para distribuciones de GNU/Linux, está en los repositorios oficiales).
Lo primero de debemos hacer es crear una carpeta en nuestro equipo donde tendremos nuestro código fuente. Usando una terminal cambio al directorio de mis fuentes y doy:

git init

Esto prepara mi repositorio local (aunque aún esté vacío...)
Luego conecto mi repositorio local con mi nuevo repositorio remoto con esta línea:

git remote add origin gitorious@git.sugarlabs.org:wordhunt/mainline.git
git push origin master

Naturalmente hay que cambiar "wordhunt" por el nombre del proyecto y "gitorious" por nuestro nombre en SugarLabs.
Ya podemos subir las fuentes, así que creamos el archivo wordhunt.py en nuestra carpeta, y para añadirlo al repositorio hacemos:

git add wordhunt.py

Para aprobar nuestros cambios hacemos:

git commit -a

Lo que abrirá un editor para que entremos un comentario acerca de los cambios hechos. Y finalmente para empujar nuestros cambios al servidor hacemos:

git push

Naturalmente usar git es complicado y tiene muchas sutilezas, pero para nuestro sencillo proyecto, lo que debemos hacer cada vez que queramos subir y actualizar nuestras fuentes hacemos un git add para los archivos que aún no han sido agregados, luego un git commit y un git push, que actualizará todos los archivos.
Pueden ver que ya tengo en SugarLabs mi archivo wordhunt.py. Si quieren poder copiar y pegar a un editor sin los números de línea pueden seguir en esta página el enlace que dice "raw blob data", o sea éste


Imágen:
Unhindered by Talent: http://www.flickr.com/photos/nicmcphee/656666358/

miércoles, 6 de mayo de 2009

Contando

Ahora vamos a descongelar el tiempo... Vamos a hacer que el contador se mueva y que el juego termine cuando el tiempo se acabe. Para este tipo de labores, es ideal la función timeout_add del módulo gobject. Esta función espera cierto tiempo, y luego ejecuta una segunda función, entonces lo que hacemos es definir una función que cuando sea llamada, reste uno del tiempo restante (remaining_time), y actualice el tiempo que se muestra en la interfaz. Además, vuelve a crear un contador de tiempo que vuelve a llamar esta función cuando haya pasado un segundo (1000 milisegundos). De esta forma, tendremos un contador. Cuando el tiempo llegue a 0, la casilla de entrada de texto es bloqueada para que no se pueda entrar más palabras.

def update_timer(self):
self.remaining_time -= 1
timer_text = str(int(self.remaining_time / 60)) + ":"
timer_text += "%0.2i" % (self.remaining_time % 60)
self.timer_label.set_text(timer_text)
if self.remaining_time == 0:
self.word_entry.set_state(gtk.STATE_INSENSITIVE)
else:
gobject.timeout_add(1000, self.update_timer)
return False

Además de importar el módulo gobject al inicio para poder usar el contador:

import gobject

al final de la función __init__ que ya tenemos (llamada el constructor), llamamos una vez la función update_timer para que el contador empiece a contar:

self.update_timer()

También voy a aprovechar para ir acumulando las palabras correctas en una lista (valid_words), e iré mostrando las palabras que ha entrado el jugador. Esto lo hago cada vez que se entra una palabra válida (dentro de la función word_entered). Es importante revisar que la palabra entrada no esté contenida en las palabras váidas que ya tengo, para no incluirla dos veces en la lista.

def word_entered(self,word):
word = word.upper() #todo en mayusculas
if word == "X":
print "Adiós"
gtk.main_quit()
elif len(word) < 3:
self.player_panel.set_text("Demasiado Corta")
else:
word_chain = self.word_is_valid(word)
if word_chain[-1] != False:
words = ""
if not word in self.valid_words:
self.valid_words.append(word)
for valid_word in self.valid_words:
words += valid_word + ", "
self.player_panel.set_text(words)
self.mark_word(word_chain)
else:
self.player_panel.set_text("Inválida")

Ah! Además puedo aprovechar para mostrar dónde está ubicada la palabra con la nueva función mark_word. Esta función marca en azul las letras que forman la palabra. Lo que debo hacer es recorrer las posiciones de la palabra e ir cambiando el color del gtk.Label de esa posición. Además quiero que dure marcada 5 segundos, pero que luego se desmarque automáticamente. Para esto activo un contador que llame la función unmark_word que se encarga de dejar todas las letras negras otra vez. Es importante cancelar el contador de la palabra anterior para garantizar que siempre dura 5 segundos marcada la palabra con la función source_remove de gobject.

def mark_word(self, word_chain):
self.unmark_word()
for position in word_chain:
self.grid.set_color(gtk.gdk.Color(100, 100, 60000),
position[0], position[1])
gobject.source_remove(self.highlight_timer)
self.highlight_timer = gobject.timeout_add(5000, self.unmark_word)
return

La función unmark_word se ve así:

def unmark_word(self):
for row in range(self.size):
for column in range(self.size):
self.grid.set_color(gtk.gdk.Color(0,0,0),
row, column)
return False #to stop timer

Y listo, cada vez se siente más funcional el juego. Como siempre, y por última vez aquí está el código:

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

import gtk
import gobject
import pango

import random

class wordhuntgrid(gtk.Table):
def __init__(self, size):
self.size = size
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]
def set_dice(self, text, row, column):
self.dice_labels[row][column].set_text(text)

def set_color(self, color, row, column):
self.dice_labels[row][column].modify_fg(gtk.STATE_NORMAL, color)

class wordhunt():
def __init__(self):
self.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"] ]

self.size = 4
self.face = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]
self.facedie = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]
self.new_shuffled_group()
self.new_dice_set()
self.valid_words = []
self.highlight_timer = 0
self.remaining_time = 91 #always one second more than needed
mainwindow = gtk.Window(gtk.WINDOW_TOPLEVEL)
mainwindow.set_size_request(640, 480)
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
self.grid = wordhuntgrid(self.size)
self.grid.set_size_request(250, 250)
for row in range(self.size):
for column in range(self.size):
self.grid.set_dice(
self.dice[self.facedie[row][column]][self.face[row][column]],
row,column)
self.grid.show()
box2.pack_start(self.grid)
# Create text entry line
self.word_entry = gtk.Entry()
self.word_entry.connect("activate", self.entry_activate_callback)
self.word_entry.show()
box2.pack_start(self.word_entry)
# Create timer display
self.timer_label = gtk.Label("1:00")
self.timer_label.modify_font(pango.FontDescription("mono 24"))
self.timer_label.set_size_request(100, 90)
self.timer_label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(60000, 5000, 5000))
self.timer_label.show()
box3.pack_start(self.timer_label)
#Create Player Panel
self.player_panel = gtk.Label(" ")
self.player_panel.show()
self.player_panel.set_line_wrap(True)
self.player_panel.set_size_request(140, 100)
box3.pack_start(self.player_panel)
#Create dummy Definitions panel
definition_panel = gtk.Label(" ")
definition_panel.show()
box3.pack_start(definition_panel)
mainwindow.show()
self.update_timer()

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

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

def get_letter(self,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(self,letter):
positions = []
for row in range(self.size):
for column in range(self.size):
if self.dice[self.facedie[row][column]][self.face[row][column]] == letter:
positions.append([row, column])
return positions

def find_word(self,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 = self.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 self.dice[self.facedie[new_row][new_column]][self.face[new_row][new_column]] == letter:
used_copy = used
used_copy[new_row][new_column] = 1
return [[new_row, new_column]] + \
self.find_word(new_word, new_row, new_column, used_copy)
return [False]

def word_is_valid(self,word):
letter = self.get_letter(word,0)
if letter == "":
return [False]
elif letter == "Qu":
new_word = word[2:]
else:
new_word = word[1:]
for position in self.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]]] + \
self.find_word(new_word, position[0], position[1], used_copy)
if word_chain[-1] != False:
return word_chain
return [False]

def word_entered(self,word):
word = word.upper() #todo en mayusculas
if word == "X":
print "Adiós"
gtk.main_quit()
elif len(word) < 3:
self.player_panel.set_text("Demasiado Corta")
else:
word_chain = self.word_is_valid(word)
if word_chain[-1] != False:
words = ""
if not word in self.valid_words:
self.valid_words.append(word)
for valid_word in self.valid_words:
words += valid_word + ", "
self.player_panel.set_text(words)
self.mark_word(word_chain)
else:
self.player_panel.set_text("Inválida")

def entry_activate_callback(self,widget):
self.word_entered(self.word_entry.get_text())
self.word_entry.set_text("")
return

def mark_word(self, word_chain):
self.unmark_word()
for position in word_chain:
self.grid.set_color(gtk.gdk.Color(100, 100, 60000),
position[0], position[1])
gobject.source_remove(self.highlight_timer)
self.highlight_timer = gobject.timeout_add(5000, self.unmark_word)
return

def unmark_word(self):
for row in range(self.size):
for column in range(self.size):
self.grid.set_color(gtk.gdk.Color(0,0,0),
row, column)
return False #to stop timer

def update_timer(self):
self.remaining_time -= 1
timer_text = str(int(self.remaining_time / 60)) + ":"
timer_text += "%0.2i" % (self.remaining_time % 60)
self.timer_label.set_text(timer_text)
if self.remaining_time == 0:
self.word_entry.set_state(gtk.STATE_INSENSITIVE)
else:
gobject.timeout_add(1000, self.update_timer)
return False

wh = wordhunt()
gtk.main()

Imágen:
wwarby: http://www.flickr.com/photos/wwarby/3297205226/

domingo, 3 de mayo de 2009

Mezclando


Vamos ahora a mezclar los ingredientes que tenemos. Por un lado habíamos construido el motor del juego, que puede generar la matriz de dados aleatoriamente, y que puede recibir las palabras escritas por el jugador para verificar si se pueden construir con los dados disponibles. De este ingrediente, ya no necesitaremos la función que imprime en la terminal la cuadrícula, ni la entrada de texto desde la terminal, porque esto será ahora el trabajo de nuestro segundo ingrediente. Nuestro segundo ingrediente fue la interfaz gráfica, que nos puede mostrar la cuadrícula, además de tener un espacio para entrar texto. Vamos a agregar un par de cosas a la clase worhuntgrid:

class wordhuntgrid(gtk.Table):
def __init__(self, size):
self.size = size
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]
def set_dice(self, text, row, column):
self.dice_labels[row][column].set_text(text)


En primer lugar, agrego la opción de determinar el tamaño de la cuadrícula cuando se cree el objeto, por eso ahora el constructor (__init__) toma un valor de entrada. Este valor se guarda en la variable self.size. El objeto self representa al objeto mismo en el que estamos, así que self.size es la variable size que pertence al objeto. No es la misma size que entró al objeto. Además he agregado una función set_dice, que permite dar una letra (text) y su ubicación para colocarla en la cuadrícula.

Y ahora si a mezclar...

Para combinar el motor del programa con su interfaz gráfica, voy a crear una clase llamada wordhunt. Esta clase contendrá elementos de ambos, muchas veces colocando self como prefijo a algunas variables, para que éstas estén disponibles para el resto de funciones de la clase. Naturalmente cuando las use, debo usar también el prefijo self.
Para pasar las letras de los dados que se generaron aleatoriamente, debo usar la función set_dice que mostré arriba de esta forma:

for row in range(self.size):
for column in range(self.size):
self.grid.set_dice(
self.dice[self.facedie[row][column]][self.face[row][column]],
row,column)

Para recibir la entrada de texto, debo monitorear el momento en que se ha hecho "Enter" en la entrada de texto. Muchos objetos en GTK generan "señales" cuando ocurre algo con ellos. Un objeto gtk.Entry genera una señal "activate" cuando se presiona "Enter" estando el cursor en él. Lo que debemos hacer es conectar esta señal a una función (entry_activate_callback), que será llamada cada vez que la señal se genere, así:

self.word_entry.connect("activate", self.entry_activate_callback)

La función que se llama con activate la defino así:

def entry_activate_callback(self,widget):
self.word_entered(self.word_entry.get_text())
self.word_entry.set_text("")
return

Esta función llama a una segunda función (word_entered), pasándole el contenido de la entrada de texto, y luego limpia la entrada de texto. Además, en vez de usar print como antes para reportar si una palabra es válida, pongo el texto en el gtk.Label player_panel. De esta forma el texto se muestra justo debajo del contador de tiempo. La función word_entered queda así:

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

import gtk
import pango

import random

class wordhuntgrid(gtk.Table):
def __init__(self, size):
self.size = size
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]
def set_dice(self, text, row, column):
self.dice_labels[row][column].set_text(text)

class wordhunt():
def __init__(self):
self.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"] ]

self.size = 4
self.face = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]
self.facedie = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]
self.new_shuffled_group()
self.new_dice_set()
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
self.grid = wordhuntgrid(self.size)
self.grid.set_size_request(250, 250)
for row in range(self.size):
for column in range(self.size):
self.grid.set_dice(
self.dice[self.facedie[row][column]][self.face[row][column]],
row,column)
self.grid.show()
box2.pack_start(self.grid)
# Create text entry line
self.word_entry = gtk.Entry()
self.word_entry.connect("activate", self.entry_activate_callback)
self.word_entry.show()
box2.pack_start(self.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 Player Panel
self.player_panel = gtk.Label(" ")
self.player_panel.show()
box3.pack_start(self.player_panel)
#Create dummy Definitions panel
definition_panel = gtk.Label(" ")
definition_panel.show()
box3.pack_start(definition_panel)
mainwindow.show()

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

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

def get_letter(self,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(self,letter):
positions = []
for row in range(4):
for column in range(4):
if self.dice[self.facedie[row][column]][self.face[row][column]] == letter:
positions.append([row, column])
return positions

def find_word(self,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 = self.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 self.dice[self.facedie[new_row][new_column]][self.face[new_row][new_column]] == letter:
used_copy = used
used_copy[new_row][new_column] = 1
return [[new_row, new_column]] + \
self.find_word(new_word, new_row, new_column, used_copy)
return [False]

def word_is_valid(self,word):
letter = self.get_letter(word,0)
if letter == "":
return [False]
elif letter == "Qu":
new_word = word[2:]
else:
new_word = word[1:]
for position in self.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]]] + \
self.find_word(new_word, position[0], position[1], used_copy)
if word_chain[-1] != False:
return word_chain
return [False]

def word_entered(self,word):
word = word.upper() #todo en mayusculas
if word == "X":
print "Adiós"
gtk.main_quit()
elif len(word) < 3:
self.player_panel.set_text("Demasiado Corta")
else:
word_chain = self.word_is_valid(word)
if word_chain[-1] != False:
self.player_panel.set_text("Válida")
print word_chain
else:
self.player_panel.set_text("Inválida")

def entry_activate_callback(self,widget):
self.word_entered(self.word_entry.get_text())
self.word_entry.set_text("")
return

wh = wordhunt()
gtk.main()

Imágenes:

vivere libero: http://www.flickr.com/photos/viverelibero/3004553146/
sylvar: http://www.flickr.com/photos/sylvar/348629926/
ginnerobot: http://www.flickr.com/photos/ginnerobot/3156850244/
_e.t: http://www.flickr.com/photos
/45688285@N00/82172576/

mknobil: http://www.flickr.com/photos/knobil/163202136/