Historial de desarrollo de Spock!

Descripción del contenido de la página

Historial de desarrollo de Spock!, versión plurilingüe del juego Piedra, papel, tijera, lagarto, Spock escrito en Forth, para un jugador.

Etiquetas:

2012-03-30

Primeros cambios en el código de Piedra, papel, tijeras, que sirve de base. El objetivo es probar el lenguage 4tH, un pariente cercano de Forth, aparentemente muy parecido pero con algunas peculiaridades que lo alejan del espíritu de Forth, empezando por el hecho de que es un compilador. Versión A-00.

2012-03-31

Más cambios.

Eliminadas las estructuras CASE: el preprocesador de 4tH las convierte en equivalentes con IF antes de compilar, y por ello unas tablas de búsqueda son mejor solución, como recomienda el manual. 4tH provee herramientas para crear y usar este tipo de tablas fácilmente.

Primer borrador del sistema de selección de lengua y de los textos plurilingües.

El desarrollo con 4tH es demasiado lento porque las herramientas de depuración son limitadas, los mensajes de error son de poca ayuda (no indican la línea ni el contexto, solo el motivo y el número de palabra, lo que no permite encontrar fácilmente el punto del código que causó el error). La falta de intérprete impide la interactividad, tan útil en Forth durante el desarrollo y depuración. Debido a todo ello, empiezo a añadir el código necesario para que el programa corra también en Gforth...

2012-04-01

Tablas plurilingües para los resultados de cada enfrentamiento (solo para Gforth), incluidos los empates. La lengua inglesa se divide en dos variantes: británica y usamericana, pues algunos de los textos son diferentes.

Finalmente elimino todo el código para 4tH: Es demasiado lento depurar dos versiones del código, especialmente cuando las estructuras de datos deben ser tan diferentes. Versión A-01, solo para Gforth.

2012-04-02

Nuevo sistema para los empates

El sistema empleado hasta ahora para añadir a la tabla de resultados los empates creaba una entrada en la tabla para cada combinación de comandos iguales, por tanto sin distinguir entre empates y el resto. Primero, la palabra versus calculaba un resultado único para cada combinación de comandos:

: versus  ( u1 u2 | u2 u1 | u1 u1 -- u3 )
  \ u1 u2 = Commands.
  \ u3 = Identifier of the commands combination.
  2dup min commands * >r max r> +
  ;
commands 1- dup versus constant max_versus

Segundo, los textos que anuncian empate eran mantenidos en una tabla propia:

h" Draw"  \ British English.
h" Tie"  \ American English.
h" Remiso"  \ Esperanto.
h" Empate"  \ Spanish.
create tie_messages  languages,
: 'tie_message  ( u -- a )
  \ u = Language.
  \ a = Message's addresss.
  tie_messages at @
  ;

languages, simplemente compila en el diccionario tantos elementos de la pila como lenguas:

: ,s  ( x1..xu u -- )  0  do  ,  loop  ;

: languages,  ( x..x' -- )
  \ Compile elements from the stack, one for every language.
  languages ,s
  ;

Y por último, era necesario una capa adicional, sobre la creación de los mensajes de resultados, para completar la tabla de resultados con los resultados de empate:

: tie_explanation!  ( u -- )
  \ Store a tie explanation of a commands combination in a language.
  \ u = Language.
  dup 'tie_message swap explanation!
  ;
: (tie_explanations)  ( u -- )
  \ Create tie explanations of a command in every language.
  \ u = Command.
  dup explanations:  \ Store the combination of a tie command.
  languages 0  do  i tie_explanation!  loop
  ;
: tie_explanations  ( -- )
  \ Create all tie explanations.
  commands 0  do  i (tie_explanations)  loop
  ;
tie_explanations

Para simplificar el método, primero hay que modificar versus para que devuelva un único valor en todos los casos de empate, y evitar que coincida con cualquier otra combinación posible. Lo más práctico es usar cero para los empates:

: (versus)  ( u1 u2 | u2 u1 -- u3 )
  \ u1 u2 = Commands.
  \ u3 = Identifier of the commands combination.
  2dup min commands * >r max r> + 1+
  ;
: versus  ( u1 u2 | u2 u1 | u1 u1 -- u3 )
  \ u1 u2 = Commands.
  \ u3 = Identifier of the commands combination.
  2dup = if  2drop 0  else  (versus)  then
  ;
commands 1- dup 1- versus constant max_versus

De esa manera, es posible crear los mensajes de empate con una única entrada en la tabla de resultados, cuyos dos primeros elementos se muestran a continuación:

false dup explanations:  \ Valid for any pair of equal commands.
h" Empate" spanish explanation!
h" Remiso" esperanto explanation!
h" Tie" american_english explanation!
h" Draw" british_english explanation!
rock scissors explanations:
h" La piedra rompe la tijera." spanish explanation!
h" La ŝtono rompas la tondilon." esperanto explanation!
h" The rock crushes the scissors." dup american_english explanation!
british_english explanation!
paper rock explanations:
h" El papel envuelve la piedra." spanish explanation!
h" La papero envolvas la ŝtonon." esperanto explanation!
h" The paper covers the rock." dup american_english explanation!
british_english explanation!
scissors paper explanations:
h" La tijera corta el papel." spanish explanation!
h" La tondilo tondas la paperon." esperanto explanation!
h" The scissors cut the paper." dup american_english explanation!
british_english explanation!
lizard spock explanations:
h" El lagarto envenena a Spock." spanish explanation!
h" La lacerto venenas Spock-on." esperanto explanation!
h" The lizard poisons Spock." dup american_english explanation!
british_english explanation!
rock lizard explanations:
h" La piedra aplasta el lagarto." spanish explanation!
h" La ŝtono premplatigas la lacerton." esperanto explanation!
h" The rock crushes the lizard." dup american_english explanation!
british_english explanation!
scissors lizard explanations:
h" La tijera decapita el lagarto." spanish explanation!
h" La tondilo senkapigas la lacerton." esperanto explanation!
h" The scissors decapitate the lizard." dup american_english explanation!
british_english explanation!
lizard paper explanations:
h" El lagarto come el papel." spanish explanation!
h" La lacerto manĝas la paperon." esperanto explanation!
h" The lizard eats the paper." dup american_english explanation!
british_english explanation!
spock rock explanations:
h" Spock vaporiza la piedra." spanish explanation!
h" Spock vaporigas la ŝtonon." esperanto explanation!
h" Spock vaporizes the rock." dup american_english explanation!
british_english explanation!
paper spock explanations:
h" El papel desautoriza a Spock." spanish explanation!
h" La papero senrajtigas Spock-on." esperanto explanation!
h" The paper disproves Spock." dup american_english explanation!
british_english explanation!
spock scissors explanations:
h" Spock rompe la tijera." spanish explanation!
h" Spock rompas la tondilon." esperanto explanation!
h" Spock smashes the scissors." dup american_english explanation!
british_english explanation!

La palabra h" simplemente compila la cadena en el diccionario pero antes deja su dirección en la pila: : h" ( "text" -- a ) here ," ; immediate.

Las herramientas para crear y consultar la tabla de resultados son:

create explanations  max_versus languages * cells allot
variable >explanations  \ Pointer in the EXPLANATIONS table.
: explanations:  ( u1 u2 -- )
  \ u1 u2 = Commands the following explanations definitions refers to.
  versus >explanations !
  ;
: explanation!  ( a u -- )
  \ a = Address of the explanation string.
  \ u = Language of the explanation string.
  >explanations @ languages * + explanations at !
  ;
: explanation  ( u -- a1 u1 )
  \ u = Identifier of a commands combination.
  \ a1 u1 = Explanation in the current language.
  languages * language @ + explanations at s@
  ;
: .explanation  ( u -- )
  \ u = Identifier of a commands combination.
  explanation type
  ;
: .result  ( -- )
  result .explanation
  ;

Nuevo sistema para la comprobación del ganador

La palabra que comprueba si la computadora ha ganado su turno necesita ahora diez comprobaciones, en lugar de las tres del juego original Piedra, papel, tijeras:

: computer_wins_round?  ( -- f )
  \ Rewrite with table!!!
  choices rock scissors d=
  choices rock lizard d= or
  choices scissors paper d= or
  choices scissors lizard d= or
  choices paper rock d= or
  choices paper spock d= or
  choices lizard paper d= or
  choices lizard spock d= or
  choices spock rock d= or
  choices spock scissors d= or
  ;

La palabra choices simplemente deja en la pila los comandos de ambos jugadores, que en algunas operaciones, como la anterior, son tratados como un solo número doble:

: choices  ( -- u1 u2 )
  computer_choice @  human_choice @
  ;

En lugar de hacer diez comprobaciones de números dobles para calcular un resultado es más elegante, aunque necesite más código, crear una tabla de búsqueda lista para ser utilizada en tiempo de ejecución:

: command_combinations  ( -- u )
  commands dup *
  ;
create winners  command_combinations allot
: >winner  ( u1 u2 -- a )
  \ u1 = Computer's command.
  \ u2 = Human's command.
  commands * + winners +
  ;
: winner  ( u1 u2 -- )
  \ u1 = Computer's command.
  \ u2 = Human's command.
  >winner true swap c!
  ;
: winner?  ( u1 u2 -- f )
  \ u1 = Computer's command.
  \ u2 = Human's command.
  >winners c@
  ;
: clear_winners  ( -- )
  winners command_combinations erase
  ;
: init_winners  ( -- )
  clear_winners
  rock scissors winner
  rock lizard winner
  scissors paper winner
  scissors lizard winner
  paper rock winner
  paper spock winner
  lizard paper winner
  lizard spock winner
  spock rock winner
  spock scissors winner
  ;

Para dejar la tabla preparada bastará llamar a init_winners en la palabra de inicialización general previa a todas las partidas. Durante el juego, la comprobación necesaria quedará reducida a esta simple palabra:

: computer_wins_round?  ( -- f )
  choices winner?
  ;

La versión A-02 usa este método. Es la primera versión funcional, aunque los textos de la interfaz solo están en castellano.

Integración de las definiciones de resultados y de combinaciones ganadoras

La palabra init_winners mostrada antes es prescindible, pues su trabajo puede hacerlo explanations:, que recibe los mismos parámetros (durante la creación de la tabla de resultados mostrada antes):

: explanations:  ( u1 u2 -- )
  \ u1 u2 = Commands the following explanations definitions refers to.
  versus >explanations !
  ;

Lo único que hace falta (aparte de limpiar la tabla en tiempo de compilación) es distinguir el caso de los resultados de empate (que solo se usa para crear un elemento en la tabla) del resto del casos, los ganadores:

: explanations:  ( u1 u2 -- )
  \ u1 u2 = Commands the following explanations definitions refers to.
  versus >explanations !
  ;
: winner_explanations:  ( u1 u2 -- )
  \ u1 u2 = Commands the following explanations definitions refers to.
  2dup winner  explanations:
  ;

2012-04-07

Traducción de los mensajes genéricos, con tablas que guardan las direcciones de las versiones de una cadena en cada lengua. Por ejemplo:

h" I win." dup
h" Mi venkis."
h" Yo gano."
create i_win  languages,
: i_win$  ( -- a u )
  i_win language_string
  ;

language_string forma parte del nivel inferior desde la implementación de las traducciones y está definida así:

: th  ( a1 n  -- a2 )  cells +  ;
: s@  ( a1 -- a2 u2 )  @ count  ;

: language_string  ( a1 -- a2 u2 )
  \ a1 = Address of a table of multilingual strings.
  \ a2 u2 = String in the current language.
  language @ th s@
  ;

Primeros cambios para implementar descripciones variables del resultado de cada partida, con textos aleatorios. Para ello la tabla de explicaciones pasará de guardar las direcciones de compilación de los textos a las direcciones de ejecución de las palabras que los crearán.

Pantalla de presentación. Implementado el sistema para echar otra partida o salir del programa:

h" Press space to play again; any other key to finish." dup
h" Premu spacon por reludi; alian klavon por fini."
h" Pulsa espacio para jugar de nuevo; otra tecla para terminar."
create press_space  languages,
: press_space$  ( -- a u )  press_space language_string  ;
: .press_space  cr press_space$ type  ;

h" Bye!" dup h" Adiaŭ!" h" ¡Adiós!"
create good_bye_ languages,
: good_bye$  ( -- a u )  good_bye_ language_string  ;
: .good_bye  good_bye$ type cr  ;

: enough?  ( -- ff )  .press_space .prompt key bl <>  ;
: final_pause  2 seconds  ;
: good_bye  page .good_bye final_pause bye  ;

Lo cual implica la modificación de las las palabras que forman el bucle principal:

: i_play  my_choice my_command !  ;
: you_play  your_choice your_command !  ;
: turn  page i_play you_play .results  ;
: (game)  init_game  begin  turn game_over?  until  ;
: game  init_once (game) game_over  ;
: run  splash  begin  game enough?  until  good_bye  ;

Completadas las últimas traducciones pendientes. Versión A-03.

2012-04-08

Implementado el sistema de selección de lengua, con una opción de menú similar a la utilizada en el programa Alien-B. Primero definimos los textos de la opción de menú, la tecla que se usará y la palabra que rotará entre las lenguas:

\ Menu option to change the current language:
h" Not in American English."
h" Not in British English."
h" Ne en Esperanto."
h" No en castellano."
create not_in_language  languages,
: not_in_language$  ( -- a u )  not_in_language language_string  ;
: .not_in_language  not_in_language$ type  ;

char + constant language_key  \ Char used to change the current language.
: +language  language @ 1+  dup languages < and  language !  ;

Después ampliamos el menú de opciones existente, para incluir la nueva opción y comprobar la nueva tecla, de modo que el menú se reescriba al cambiar de idioma:

char 0 constant "0"
: digit  ( u -- c )  "0" +  ;
: option  ( c -- u )  "0" -  ;
: command_key?  ( c -- ff )  "0" commands 1- digit between  ;
: language_key?  ( c -- ff )  language_key = dup  if  +language  then  ;
: valid_key?  ( c -- ff )  dup language_key? swap command_key? or  ;
: your_key  ( -- c )  0  begin  drop key dup valid_key?  until  ;
: .choice  ( u -- )  .command_name [char] . emit  ;
: .command_option  ( u -- )  dup cr . ." = " .choice  ;
: .language_option  cr cr language_key emit ."  = " .not_in_language  ;
: .options
  cr commands 0  do  i .command_option  loop  .language_option
  ;

h" Choose:" dup h" Elektu:" h" Elige:"
create choose  languages,
: choose$  ( -- a u )  choose language_string  ;
: .choose  choose$ type  ;

: menu  page .long_title .choose .options .prompt  ;
: your_choice  ( -- u)
  0  begin  drop menu your_key
  dup command_key?  until  option
  ;

Implementado el control del acusativo en la impresión de los comandos en esperanto: en el menú deben aparecer en nominativo, y en la frase que recuerda las opciones elegidas deben aparecer en acusativo. La impresión debe hacerse en la palabra .choice, comprobando si la lengua actual es el esperanto y si el indicador de acusativo está activado:

variable accusative  \ Show the Esperanto commands in accusative?
: esperanto?  ( -- ff )  language @ esperanto =  ;
: accusative?  ( -- ff )  esperanto? accusative @ and  ;
: (.accusative)  ( u -- )  spock =  if  ." -on"  else  ." n"  then  ;
: .accusative  ( u -- )  accusative?  if  (.accusative)  else  drop  then  ;
: .choice  ( u -- )  dup .command_name .accusative [char] . emit  ;

Después el indicador debe desactivarse para imprimir el menú:

: .language_option  cr cr language_key emit ."  = " .not_in_language  ;
: .options  cr accusative off .command_options .language_option  ;

Pero activarse para imprimir la opción elegida por el jugador:

: .your_choice  .you_choosed your_command @ accusative on .choice  ;

Versión A-04.

2012-04-10

Revisión general. Cambios y ampliaciones en comentarios.

Versión B-00, primera beta publicable.

2012-04-11

Añadido un borrado final de pantalla antes de salir del juego.

2012-04-14

Método más sencillo y versátil para imprimir el ganador del juego. El antiguo sistema, incómodo para añadirle más lenguas, era el siguiente:

\ This method is not suitable for languages that change the verb form
\ in the needed sentences. In order to make it easier to add new
\ languages, a different method is used.

h" You " dup h" Vi " h" Has "
create you_  languages,
: you_$  ( -- a u )  you_ language_string  ;

h" I " dup h" Mi " h" He "
create i_  languages,
: i_$  ( -- a u )  i_ language_string  ;

h" won the game." dup h" venkis la ludon." h" ganado la partida."
create won_what  languages,
: won_what$  ( -- a u )  won_what language_string  ;
: .won_what  won_what$ type  ;

: winner_who$  ( -- a u )  you_win_game?  if  you_$  else  i_$  then  ;
: .winner_who  winner_who$ type  ;
: .game_winner  .winner_who .won_what  ;

El nuevo método guarda las frases completas:

h" I won the game." dup
h" Mi venkis la ludon."
h" He ganado la partida."
create (i_won_game$) languages,
: i_won_game$  ( -- a u)  (i_won_game$) language_string  ;

h" You won the game." dup
h" Vi venkis la ludon."
h" Has ganado la partida."
create (you_won_game$) languages,
: you_won_game$  ( -- a u)  (you_won_game$) language_string  ;

: game_winner$  ( -- a u )
  you_win_game?  if  you_won_game$  else  i_won_game$  then
  ;
: .game_winner  game_winner$ type  ;

2012-04-15

init_once es movido a main para que la lengua predeterminada esté activa antes de imprimir el título y los créditos.

Pequeña simplificación del bucle principal.

Añadida la mención a la licencia GPL en about.

2012-04-16

Cambiado el formato de los nombres de los textos plurilingües. Modificadas las pausas en la impresión de los créditos y la licencia.

2012-05-02

Primera beta publicada.