Completar la lógica de un juego on-chain requiere resolver dos problemas fundamentales: persistir cada jugada en el almacenamiento del contrato y determinar si esa jugada produce un ganador. Aquí se recorre paso a paso cómo lograrlo en Solidity, cuidando buenas prácticas de gas, validaciones tempranas y organización del código.
¿Por qué convertir una función a pure ahorra gas?
Cuando el compilador emite un warning indicando que una función no accede al almacenamiento ni modifica el estado, conviene reducir su visibilidad. Si la función solo opera con datos que recibe por parámetro en memoria, se puede marcar como pure [01:00]. Esto le indica a la EVM que no necesita referencias al storage, lo que la hace más liviana y económica en gas.
- view: la función únicamente lee del almacenamiento, sin modificar estado.
- pure: la función ni lee ni escribe en el almacenamiento.
- Revisar siempre estas restricciones al crear funciones evita consumo innecesario de recursos.
¿Cómo se guarda el movimiento del jugador en el contrato?
Antes de almacenar cualquier dato, es clave validar que la casilla no esté ocupada [05:46]. Esa comprobación se coloca en el bloque de validaciones, no dentro de la función de guardado. La razón es simple: cuanto antes falle una condición inválida, menos pasos se ejecutan y menos gas se consume.
¿Qué estructura tiene la función guardar movimiento?
La función guardarMovimiento recibe el ID de partida, las coordenadas horizontal y vertical, y es de tipo private porque solo se invoca internamente [06:50]. No retorna ningún valor; su único propósito es escribir en el storage.
- Se comprueba si
msg.sender coincide con el jugador uno de la partida.
- Si es el jugador uno, se asigna el valor
1 en la posición jugadas[h][v].
- Si no lo es, se asigna
2, asumiendo que las validaciones previas ya confirmaron que el sender es participante válido.
Un consejo importante sobre nombres de variables [07:30]: abreviar horizontal a h puede ser aceptable si el contexto es claro, pero si existe la mínima duda, es preferible escribir el nombre completo. Escatimar caracteres a costa de legibilidad es un mal negocio en cualquier lenguaje, y en Solidity la longitud de los nombres no impacta significativamente el costo.
¿Cómo se organiza el contrato entre funciones públicas y privadas?
Una convención útil es colocar las funciones públicas en la parte superior del contrato y las privadas hacia el final [06:10]. Las funciones públicas suelen ser más sensibles y serán las primeras que un auditor o desarrollador revise; las privadas contienen lógica interna encapsulada.
¿Cómo se detecta al ganador del Tic Tac Toe?
La función obtenerGanador recibe la partida por memoria y devuelve un entero [09:20]. Si devuelve 0, no hay ganador aún; si devuelve 1 o 2, identifica al jugador victorioso.
¿Qué lógica usa la función chequear línea?
Para no repetir código, se crea una función auxiliar llamada chequearLinea que recibe el array de jugadas y las tres coordenadas de la línea a comprobar [10:50]. La comprobación aplica transitividad: si la casilla (x1, y1) es igual a (x2, y2) y esta es igual a (x3, y3), las tres coinciden.
- Si coinciden, retorna el valor almacenado en cualquiera de las tres casillas.
- Si no coinciden, retorna
0.
Esta misma función se reutiliza para verificar diagonales, columnas y filas [13:25], cambiando únicamente las coordenadas en cada llamada. El resultado es un código limpio donde cada combinación ganadora queda explícita y fácil de leer.
Un detalle técnico relevante: cuando se pasa un array como parámetro, Solidity exige indicar que viaja por memory [14:00], de lo contrario el compilador lanza una advertencia.
¿Cómo se persiste el ganador en el almacenamiento?
La función guardarGanador recibe el entero del ganador y el ID de partida [14:30]. Primero valida que el valor sea distinto de cero; si es cero, no hay nada que guardar. Si es 1, asigna la dirección del jugador uno al campo ganador de la partida en storage. Si es 2, hace lo mismo con el jugador dos.
Es fundamental acceder al almacenamiento y no a la copia en memoria, porque el objetivo es modificar el estado persistente del contrato.
Compilar frecuentemente durante el desarrollo ayuda a detectar errores pequeños —un return en lugar de returns, un paréntesis de más— antes de que se acumulen y compliquen la depuración [12:40]. Esta práctica de compilación incremental es especialmente valiosa en Solidity, donde cada error sintáctico puede pasar desapercibido hasta el despliegue.
Con estas funciones completadas, la lógica central del Tic Tac Toe queda lista. Si encontraste algún enfoque distinto para validar las combinaciones ganadoras o para optimizar el guardado, compártelo en los comentarios.