Diseño de contrato Tic Tac Toe en Solidity

Resumen

Antes de escribir código, conviene saber cuándo un proyecto con contratos inteligentes en Solidity realmente vale la pena. Aquí defines los criterios para validar tu idea y arrancas la estructura de un juego de Tic Tac Toe que servirá de hilo conductor del curso.

¿Cuándo conviene resolver un proyecto con smart contracts?

No toda idea encaja con blockchain. Hay tres filtros básicos que te ayudan a decidir si tu propuesta merece vivir en un contrato o si otra tecnología la resuelve mejor.

  • Simplicidad de la solución: si el requerimiento se traduce en cientos de líneas de código y referencias enredadas, probablemente el contrato no sea el mejor camino. La lógica debe bajar a algo prolijo, como pasó con el contador de la clase anterior [02:00].
  • Costo de almacenamiento: cada dato guardado en un contrato cuesta gas, tanto al desplegarlo como cuando los usuarios interactúan. Si guardas información poco crítica (por ejemplo, el sabor favorito de helado de un usuario), encarecerás el proyecto y la gente migrará a otra opción [03:30].
  • Información sensible: lo que vive en el contrato es accesible aunque uses variables privadas. Si tu juego de piedra, papel o tijera deja ver la jugada del rival antes de tiempo, el incentivo se rompe y siempre ganaría quien lea primero [04:45].

¿Por qué no guardar todo en un smart contract? Porque cada variable consume gas y eleva el costo de despliegue e interacción. Solo almacena lo que necesite descentralización real.

¿Cómo planteamos el proyecto del Tic Tac Toe en Solidity?

El juego que acompañará el curso tiene reglas claras que se traducen directamente a requisitos del contrato. Definirlos antes de tipear evita reescribir lógica más tarde.

¿Qué reglas debe cumplir el contrato del juego?

  • Soportar múltiples partidas simultáneas entre las mismas o distintas cuentas, en lugar de una sola ejecución.
  • Asociar cada partida a dos direcciones específicas que representan a los jugadores.
  • Validar que solo las direcciones registradas en una partida puedan marcar un movimiento dentro de ella [06:30].

Con estas reglas claras, ya puedes saltar al editor y empezar a modelar los datos.

¿Cómo se modelan las partidas con un struct y un array dinámico?

La estructura inicial del contrato ya trae licencia, rango de versiones (pragma) y el nombre TicTacToe, además de apartados reservados para variables, constructor, funciones y modificadores. El siguiente paso es decidir qué datos representan una partida.

Usar variables sueltas obligaría a repetirlas por cada juego nuevo. La alternativa limpia es declarar un struct que agrupe toda la información de una partida y luego almacenarla dentro de una colección.

¿Qué campos lleva el struct Partida?

  • jugadorUno y jugadorDos: ambas como address, representan a los participantes.
  • ganador: una dirección que permite consultar después quién ganó esa partida puntual.
  • jugadas: una matriz de uint donde se guarda 1 para el jugador uno y 2 para el jugador dos. Se plantea de 4x4 para usar las posiciones 1, 2 y 3 sin convertir índices desde cero, aunque hay margen para optimizarla [09:50].

¿Por qué usar un struct en Solidity? Porque empaqueta varios datos relacionados bajo un solo tipo y permite guardarlos en arrays o mappings sin repetir variables sueltas.

¿Por qué un array dinámico para las partidas?

No sabes cuántas partidas se crearán. Un array dinámico de tipo Partida llamado partidas crece a medida que los usuarios la van usando, sin tamaño fijo desde el despliegue. El constructor queda vacío porque no hay datos previos que inicializar, pero el apartado se conserva por orden y futura extensión.

¿Qué funciones públicas necesita el contrato?

Dos funciones sostienen la mecánica del juego: una para crear partidas y otra para registrar movimientos. Definir el encabezado primero ayuda a ver qué piezas privadas vas a necesitar después.

¿Cómo se define la función de crear partida?

La función crearPartida recibe dos address (jugador uno y jugador dos), se declara public y retorna un uint con el identificador de la partida recién creada. Esto le da al usuario una referencia para jugar después.

¿Cómo se define la función jugar?

La función jugar recibe el identificador de la partida, la casilla horizontal y la casilla vertical. No pide la dirección del jugador porque la lee del msg.sender de la transacción. Es public y, por ahora, no retorna valor.

Dentro de jugar hay un orden lógico que conviene respetar:

  1. Validaciones: que quien llama sea uno de los jugadores, que la casilla esté libre, que las coordenadas estén dentro de la grilla y que la partida siga activa.
  2. Guardar la jugada en la matriz correspondiente.
  3. Chequear el resultado: ver si hay ganador o si la grilla está completa, y guardar al ganador en la partida si corresponde [15:20].

Las validaciones pueden vivir en modifiers o en funciones privadas. El chequeo de ganador encaja muy bien como función privada, igual que la rutina para guardar la jugada. Plantearlo como boceto antes de codear evita olvidar pasos como la verificación final del ganador.

Te dejo el desafío: completa el código de crearPartida y jugar con las validaciones y el chequeo de ganador. Cuéntame en los comentarios cómo lo resolviste y qué partes del struct o de la matriz mejorarías.