Patrones para reglas de juego

elkaoD

Esto es una pregunta abierta así que van a surgir varias ideas interesantes seguro. No sé si alguna vez habéis intentando programar algún juego a base de "reglas" (un ajedez, por ejemplo) pero estoy teniendo bastantes problemas para llegar a una solución que me convenza. Me atormenta sobre todo la reusabilidad, como por ejemplo poder crear fácilmente nuevos sets de reglas (ajedrez 960, bughouse...) desde una misma base de código.

Mi aproximación actual despues de varias pruebas ha sido aplicar MVC+patrón comando (en juegos de tablero para hacer undo/redo, log de movimientos, etc.) pero he acabado siempre con controladores monolíticos, pesados y mal extensibles... en definitiva: una mierda de diseño.

En ciertos casos me ha resultado útil aplicar patrón observador pero no vale para todo... no todos los eventos del controlador son "observables" (sin desgranar en chopocientas capas tan ligeras que empiezan a resultar inútiles) y además aumentas el acoplamiento (aunque el encapsulado sea más lógico.) Ni siquiera así he llegado deshacer lo monolítico del controlador sin entremezclar los observadores (las diferentes reglas que se "influyen" entre sí), disminuir el encapsulado (clases friend y demás) o con interfaces monstruosos.

En otros casos me ha resultado útil el patrón cadena de responsabilidad, pero el problema es el mismo: en cuanto hay reglas complejas se convierte más bien en un árbol de responsabilidad en el que los propios procesadores crean nuevos comandos en una espiral de locura.

La "solución" que usan en juegos complejos es un sistema de mensajes-eventos pero me parece excesivo para un simple juego de tablero, es casi peor que el controlador monolítico (infierno de debuggear.)

¿Habéis hecho esto alguna vez? ¿Se os ocurre algo? ¿O es que simplemente el dominio del problema es inherentemente complejo y "desordenado"? Soy consciente de que no todo se puede arreglar a base de patrones pero tiene que haber ALGO que se me escapa. Es el problema de siempre: simplicidad vs. completitud, y en este caso aún más exagerado (reglas simples = juegos aburridos... reglas complejas = código inmantenible... ¡no acabo de encontrar el punto de equilibrio!)

BLZKZ

yo las reglas las implementaría en los modelos o al menos por debajo de los controladores.

Tienes una clase ficha que tiene mover, de esa hereda caballo, tu le pasas origen y destino desde el controlador y es la propia ficha la que le dice al controlador si puede o no.

Asi mover_ficha (por ejemplo) que tengas en el controlador le sudará los cojones que sea un peón, o estés jugando a las damas.

Lo malo de estos juegos es la diversidad de reglas, de ahí que haya partes que nunca puedas reutilizar, pero me parece la forma más factible :/

Aún así mañana reviso cosas de hace unos años y comento como lo hice (ni me acuerdo)

Pero vamos usaria el patrón de fabrica abstracta.

1 respuesta
elkaoD

#2 así es como lo tengo hecho. El problema es cuando se entremezclan comportamientos entre las piezas y el juego. Aún así, mantengo que me parece mejor dejar el modelo aparte y quedarse con las reglas en el controlador (siendo más fácil reusar el mismo modelo para diferentes conjuntos de reglas.)

Ejemplo rápido: en el ajedrez se puede comer al paso (en passant) que consiste en que, si en el turno anterior el oponente abre un peon moviéndolo dos espacios, puedes comer el peon como si sólo lo hubiera movido uno.

Aquí se presenta el problema: el juego está acoplado con el peon sí o sí. ¿Cómo abstraígo esta relación? Sólo se me ocurre la distribución de responsabilidades/cadena de observadores y demás cosas que comento en #1, pero me parecen algo chapus...

Por cierto, no veo el uso que se podría dar a la fábrica abstracta, ponme un ejemplo please.

1 respuesta
BLZKZ

#3 lo de la fabrica abstracta seria para poder atacar a la superclase ficha (general) en vez de a una concreta (aunque lo mismo ya lo estás haciendo así por lo que dices xD)

y eso que comentas son caracteristicas de la pieza, puedes guardar la posición anterior en la misma ficha (aunque visto así es un poco cacota xD)

Como ya te digo mañana le echo un ojo a esto mas en profundidad y comento (si no has encontrado otra solución antes)

1 respuesta
elkaoD

#4 no puedes guardar la posición en la pieza porque es la pieza del oponente la que tienes que comprobar si se movió dos espacios en el movimiento anterior.

1. Puedes hacer que la pieza se acople de observador de todos los movimientos del tablero (y por tanto pueda guardar el movimiento del anterior) pero es CHAPUZA TOTAL (no es responsabilidad del peón saber el movimiento anterior.)
2. Puedes añadir un nuevo método al interfaz de la pieza pero... ¡estás acoplando piezas entre sí! Aún peor que acoplarlas con el tablero... y encima tienes que buscar la última pieza movida en el tablero desde otra pieza (doble acoplamiento.)
3. Puedes añadir un nuevo método al interfaz del tablero que te devuelva el movimiento anterior, pero... ¿dónde está el límite de funcionalidad a añadir al tablero? ¿Es legítimo añadir un método sólo para una regla específica que en otras implementaciones lo más probable es que no se use?
4. Puedes añadir la regla como un observador en sí mismo (actuando como behaviour del controlador) pero entonces acoplas la regla para que modifique directamente el modelo (y cualquier cambio en el modelo mandará a tomar por culo las reglas.)

En su día me quedé con la 3 por cuestión de esfuerzo/utilidad, pero es que el ejemplo de comer al paso es el más simple y ya tengo que andar con historias raras, con reglas medio complejas se complica aún más.

Espero ansioso la respuesta :P

EnZo

Es curioso que toques este tema jeje. Estoy haciendo un juego que se llama conkis y es un juego por turnos y se me han presentado este problema. Aunque no sé si lo habre solucionado correctamente.

Lo que yo he hecho ha sido crear una clase que se encarga de la logica (Logic) y que chupa del controlador y del modelo. Basicamente Logic es una extension al controlador.

Ademas del patron MVC. El modelo tiene una extension que maneja los turnos y guarda los datos de cada movimiento. (En mi caso es mas complejo porque puedes mover mas de una ficha en un mismo turno, además de que hay varios players). Pero en ajedrez cada movimiento es un turno.
Es decir el modelo.turns guardaria el movimiento del peon (#2) hacia la casilla (#A4).

Y el procedimiento mas o menos seria:

  • El usuario 1 mueve el peon 2 posiciones,
  • lanza un evento hacia el controlador.logic
  • Se almacena en la clase turnos el movimiento
  • Es turno del usuario 2, y selecciona un peon para mover
  • Logic mira el ultimo turno realizado por el enemigo
  • Y logic activa en la vista las casillas hacia donde puedes mover con tu peon seleccionado

Mi diagrama lo tengo aqui, para ver si lo tienes algo mas claro. (Esta sin actualizar...)
http://i.imm.io/hzXP.png

El problema que planteas es que no puedes crear una clase que defina las reglas generales de toooodos los posibles juegos de tablero. Sino que tu clase de logica va a ser unica para cada juego. Pero con este diseño todo está desacoplado de todo y puedes modificar reglas o ampliarlas solo modificando la clase Logic.

eisenfaust

Te estás complicando mucho. Prueba con un sistema de multiple dispatch.

1 respuesta
elkaoD

#7 te refieres a esto? http://en.wikipedia.org/wiki/Multiple_dispatch No veo donde aplicarlo, ¿puedes poner un ejemplo?

A no ser que te refieras a multiple dispatch + chain of responsibility.

Le he estado dando vueltas y creo que programación funcional puede ayudar aquí, voy a ver si logro darle el enfoque porque lo he atacado todo el rato desde estructurado+POO.

Usuarios habituales

  • elkaoD
  • eisenfaust
  • EnZo
  • BLZKZ