Euskal Encounter 21 - AI Contest ================================================================================ FAROS LÁSER ================================================================================ v0.7 NOTA: esta especificación está sujeta a cambio. En particular, se reserva el derecho de cambiar los valores numéricos mencionados, que no son definitivos. ================================================================================ El Mapa ================================================================================ El juego se jugará en un tablero de dos dimensiones compuesto por una matriz de casillas. Cada partida transcurrirá sobre un mapa de forma y dimensiones fijadas al comienzo de ella. El mapa define qué subconjunto de las casillas forma el área jugable (la isla). En todos los casos las casillas del borde del mapa no formarán parte del área jugable (lo cual excluye la posibilidad de mapas con "wraparound"). Además, todas las casillas de la "isla" estarán conectadas entre sí (no son válidos los mapas con áreas disjuntas). Ejemplo de un mapa válido: XXXXXXXXXXXX XXXX X X XX X XX X XX XX XXXX XX XX XX XXXXXXXXXXXX Se considera que la casilla inferior izquierda es la casilla (0,0) y las coordenadas crecen hacia arriba y a la derecha. ================================================================================ Energía ================================================================================ La energía es indispensable para jugar el juego. Cada casilla de la isla tendrá una cantidad de energía disponible, inicialmente 0, que se irá incrementando durante la partida. Los jugadores que pisan una casilla obtienen la energía que hay en ella, que pasa de nuevo a 0. La energía siempre es un entero (no hay unidades fraccionarias). ================================================================================ Los jugadores ================================================================================ Cada partida la jugarán dos o más jugadores identificados por los números 0, 1, 2, etc. Cada jugador tiene una posición actual en el mapa. La posición al comienzo de la partida estará definida en el mapa. Los jugadores podrán viajar por la isla de forma libre, en cualquiera de las ocho direcciones (horizontal, vertical, y diagonal). Varios jugadores pueden ocupar la misma casilla en el mismo momento. No se podrá transcurrir por las casillas que no forman parte de la isla. Los jugadores dispondrán de una reserva de energía (0 al comienzo de la partida). La energía se obtendrá de las casillas. En principio no habrá límite sobre la energía de cada jugador, aunque se reserva el derecho a establecer un máximo. ================================================================================ Los faros ================================================================================ En el mapa se situarán una serie de faros. Cada faro puede ser neutro o estar controlado por un jugador. Cuando un faro está controlado por un jugador, tendrá una cantidad de energía asociada a el. Esta energía disminuirá con el tiempo, y cuando llega a 0 el faro pasará a ser neutro de nuevo. No hay un máximo de energía por faro. Los faros generan energía de forma continua en las casillas cercanas a ellos. Esta energía no proviene del faro en sí, es decir, es independiente de si el faro está controlado por un jugador, o si es neutro, y de su nivel actual de energía. ================================================================================ Conexiones ================================================================================ Los jugadores podrán conectar parejas de faros mediante haces láser cuando ambos están bajo su control. Sin embargo, está prohibido lanzar un haz entre dos faros que cruce un haz ya existente (tanto del mismo jugador como del enemigo). Si se hiciera, la densidad de flujo lumínico en la intersección sería tan alta que se iniciaría una fusión no controlada de la atmósfera y por lo tanto una catástrofe global. También está prohibido conectar dos faros si la conexión pasaría exactamente por un tercer faro. Para realizar una conexión entre dos faros se debe estar en la casilla de uno de ellos y tener la clave del otro faro. Para obtener la clave de un faro se deberá visitarlo, y sólo se puede tener una clave en cada momento, que se perderá al realizar una conexión (las claves son de un sólo uso, OTP). Cuando tres faros se conectan diréctamente entre sí formando un triángulo, se iluminan todas las casillas situadas en el interior de dicho triángulo. El objetivo del juego es tener el máximo número de casillas iluminadas por áreas triangulares entre faros bajo el control del mismo jugador. Las conexiones pueden cruzar casillas que no forman parte de la isla. Las casillas que no forman parte de la isla no se iluminan ni puntúan, pero pueden formar parte de un triángulo. ================================================================================ El Juego ================================================================================ La partida constará de una serie de rondas. Durante cada ronda primero será el turno del jugador 0, después el 1, etc. Al comienzo del juego, cada jugador recibirá la siguiente información: - Su número de jugador - El número de jugadores total - Su posición inicial - El mapa (es decir un mapa booleano de qué casillas forman parte de la isla) - Por cada faro: - Sus coordenadas Al comienzo de cada ronda ocurrirá lo siguiente: - Cada casilla incrementará su cantidad de energía libre disponible según la siguiente fórmula: energía += floor(5 - distancia a faro) Esto ocurre por cada faro, de forma que las casillas a menos de 5 unidades de distancia lineal a más de un faro se verán afectadas por todos ellos. Cada casilla se limitará a un máximo de 100 unidades de energía. - Cada jugador obtendrá de su casilla actual la cantidad de energía libre presente en ella, que se reducirá a cero. Si varios jugadores comparten casilla, cada uno recibirá la fracción correspondiente (si hay resto tras la división, la energía restante se pierde). - Si un jugador está situado sobre un faro, obtendrá su clave si no dispone ya de ella. Esto ocurre indistintamente de si el faro está controlado por el o no. - La energía de todos los faros se decrementa en 10 puntos. Si llega a cero, el faro pasa a ser neutro y desaparecen todas las conexiones con él. Antes de cada turno, el jugador recibe la siguiente información: - Su posición - Su puntuación - Su nivel de energía acumulada - Por cada casilla cuyo centro se haya a 3 unidades o menos del jugador (en línea recta): - La cantidad de energía libre disponible en ella. Lógicamente, el valor siempre será cero para la casilla del propio jugador, ya que esa energía ya ya sido obtenida. - Por cada faro: - Sus coordenadas (como identificador) - El jugador que lo controla (0, 1, 2, ... o ninguno=-1) - Su nivel de energía (cero si no está bajo control) - La lista de faros a los cuales está conectado (por lo tanto cada conexión se indicará por duplicado, desde ambos vértices). - Si se dispone de su clave o no. Cabe resaltar que los jugadores NO conocen la posición de los demás. Cada turno, cada jugador podrá realizar UNA de las siguientes acciones: - Nada - Mover a una casilla adyacente - Si se encuentra en la misma casilla que un faro: - Atacar o recargarlo. Atacar o recargar un faro es la misma acción: el jugador aporta una cantidad de energía de su elección. Si el faro no está bajo su control, se resta total o parcialmente de la energía presente en el faro. Cuando toda la energía del faro ha sido eliminada, el sobrante de energía aportada se suma de nuevo al faro y el faro pasa a estar controlado por el jugador actual. - Conectarlo a otro faro bajo el control del mismo jugador. Para ello, el jugador debe disponer de la clave del faro remoto, y la perderá al crear la conexión. ================================================================================ Puntuación ================================================================================ Cada jugador tendrá una puntuación (0 al comienzo de la partida), que es un número entero. Se obtendrán puntos al término de cada ronda de la siguiente forma: - 2 puntos por cada torre bajo control del jugador. - 2 puntos por cada pareja de faros conectados bajo el control del jugador. - Por cada 3 faros conectados entre sí bajo su control (formando un triángulo): - 1 punto por cada casilla cuyo centro esté dentro de dicho triángulo. Nota 1: es posible que las áreas se solapen, y, en ese caso, puntúan doble. Por ejemplo, en esta configuración de faros conectados existen 3 triángulos interiores y un triángulo exterior, de forma que la cantidad de puntos otorgados será equivalente al DOBLE del área del triángulo exterior. o-----o \`o'/ \|/ o Igualmente, es posible que un área de un jugador contenga completamente a un área de otro jugador. En este caso cada jugador obtiene sus puntos correspondientes. Es decir, la casilla se puede "iluminar" de una mezcla de colores. Nota 2: Cuando un lado del triángulo pasa por el centro de una casilla, se utilizará la regla de rasterización de OpenGL para determinar qué casillas se consideran como parte del área (cuentan las casillas superiores y a la izquierda): http://goo.gl/f1cxU ================================================================================ Fin del juego ================================================================================ El juego terminará tras un número predefinido de rondas. ================================================================================ Protocolo ================================================================================ Los bots se comunicarán por stdio/stdout. El protocolo consiste en mensajes JSON codificados en una sola línea, terminados por \n (newline). El motor del juego siempre es el responsable de iniciar las comunicaciones, y el bot contestará con su respuesta. Los bots pueden mostrar mensajes de debug por stderr, pero DEBEN prefijarlos por su nombre entre [] para su clara identificación: [TroloBot] mensaje... Nota: los siguiente ejemplos están formateados en varias líneas, pero en el protocolo real deberán estar contenidos exclusivamente en una línea. ---------------- Inicio del juego ---------------- El motor envía el siguiente mensaje (ejemplo): { "player_num": 0, "player_count": 2, "position": [1, 2], "map": [ [0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 1, 1, 0, 0], [0, 1, 1, 0, 0], [0, 0, 0, 0, 0]], "lighthouses": [ [1, 1], [3, 1], [2, 3], [1, 3] ] } Este mensaje indica que el bot es el primer jugador (jugador 0) de 2 en total (el otro sería el jugador 1), y describe el siguiente mapa de 5x5: v----- casilla (0, 4) XXXXX<-- casilla (4,4) X!!XX X0 XX X! !X XXXXX<-- casilla (4,0) ^----- casilla (0,0) ! - faro 0 - posición inicial del jugador 0 Nótese que el mapa se envía de abajo hacia arriba (0,0 es la primera casilla que se envía, pero es la esquina inferior izquierda). En la práctica la dirección en la que se visualice el mapa es inconsecuente para el juego, excepto en lo que respecta a la iluminación de casillas, que sigue la regla de bordes izquierdo y superior asumiendo que (0,0) es la esquina inferior izquierda. El bot contesta con el siguiente mensaje: { "name": "TroloBot" } El nombre se utilizará para mostrar el nombre del bot en pantalla. El bot debe inicializarse y contestar en un máximo de 2 segundos tras el envío del mensaje de inicio. ---------------- Turno ---------------- El motor envía el siguiente mensaje con el estado actual (ejemplo): { "position": [1, 3], "score": 36, "energy": 66, "view": [ [-1,-1,-1, 0,-1,-1,-1], [-1, 0, 0,50,23,50,-1], [-1, 0, 0,32,41, 0,-1], [ 0, 0, 0, 0,50, 0, 0], [-1, 0, 0, 0, 0, 0,-1], [-1, 0, 0, 0, 0, 0,-1], [-1,-1,-1, 0,-1,-1,-1] ], "lighthouses": [ { "position": [1, 1], "owner": 0, "energy": 30, "connections": [[1, 3]], "have_key": false }, { "position": [3, 1], "owner": -1, "energy": 0, "connections": [], "have_key": false }, { "position": [2, 3], "owner": 1, "energy": 90, "connections": [], "have_key": false }, { "position": [1, 3], "owner": 0, "energy": 50, "connections": [[1, 1]], "have_key": true } ] } "view" es un mapa de la energía disponible en las celdas cercanas al jugador. Nominalmente es de 7x7, conteniendo las casillas a distancia 3 o menos del jugador. Los bots deben tolerar que este valor varíe, pero siempre se dará el caso de que "view" es una matriz cuadrada de dimensión impar, y la posición del bot corresponde a la casilla del medio de la matriz. Las celdas fuera del radio 3 se devuelven como -1 para indicar que no hay datos. Las celdas que no forman parte de la isla se devuelven como 0 ya que nunca pueden contener energía (las casillas que sí forman parte de la isla pero no tienen energía también se devuelven como 0). Ya que el jugador obtiene la energía de la casilla central al comienzo de cada ronda, esta siempre tendrá un valor de 0. En este caso, la vista nos proporciona la siguiente información sobre el mapa: X X X X ?? X () 50 X X X 32 41 X ?? X 50 23 50 ?? ?? X ?? ?? ?? () - posición jugador 0 (energía 0) ?? - no hay datos (en este mapa, por ser pequeño, resulta que todas estas casillas no forman parte de la isla; este no sería el caso en mapas mayores) X - casilla no forma parte de la isla, pero entra dentro del área visible (distancia 3 o menor). Este hecho viene del mapa del mensaje inicial. El mensaje describe los faros siguientes: X X X X X X 50 90 X X X | X X X 30 0 X X X X X X Donde cada número indica la energía del faro. Los faros con energía == 50, 30 están controlados por el jugador 0 y conectados entre sí, y el faro con energía == 90 está controlado por el jugador 1. Bajo estas condiciones, el jugador 0 obtiene 6 puntos por ronda, y el jugador 1 obtiene 2 por ronda. Además, el jugador 0 tiene la clave del faro con energía 50 (es de suponer que visitó el faro 30 para obtener su clave, y luego el 50 para conectarlo al 30, perdiendo la clave del 30 en el proceso, pero ahora dispone de la clave del 50 por estar situado sobre el). El bot debe contestar con uno de los siguientes mensajes (tiempo máximo por turno: 100ms desde que se recibe el estado hasta que se contesta): { "command": "pass" } No hacer nada (pasar el turno) { "command": "move", "x": -1, "y": 1 } Mover a una casilla adyacente (en este caso, arriba y hacia la izquierda). Obviamente x e y deben ser -1, 0, o 1. Mover 0,0 es equivalente a pasar el turno. { "command": "attack", "energy": 80 } Atacar o recargar el faro sobre el cual se encuentra el jugador. Por ejemplo, si el faro está controlado por otro jugador con energía 50, entonces pasaría a estar controlado por el jugador actual con energía 30. Si el faro está controlado por otro jugador con energía 90, entonces seguiría controlado por el mismo jugador con energía 10. Si el faro está controlado por el jugador actual con energía 40, entonces pasa a tener energía 120. Si el faro está controlado por otro jugador con energía 80, entonces pasa a ser neutro (no controlado por ningún jugador) con energía 0. Al perderse o cambiar el control de un faro, se destruyen todas las conexiones actuales y por extensión dejan de existir todas las áreas (triángulos) que tienen ese faro como vértice (aunque es posible que un área que contenga íntegramente al faro no se vea afectada). La energía suministrada debe ser igual o menor a la energía que posee el jugador actualmente (si no lo fuera, se limita automáticamente al total disponible). Atacar con energía 0 es equivalente a pasar el turno. { "command": "connect", "destination": [1, 1] } Conectar el faro de la posición actual al faro (1, 1). El jugador debe poseer la clave del faro de destino, y ambos deben estar controlados por el jugador. La conexión no debe existir ya, ni cruzarse contra ninguna otra conexión existente, ni pasar por el centro de un tercer faro (la conexión se puede solapar con la casilla de un tercer faro siempre y cuando no lo haga por su centro exacto). El motor del juego contesta con el resultado de la operación: { "success": true } O bien (ejemplo de error): { "success": false, "message": "Player does not have the destination key" } En caso de error, el resultado es el mismo que si el jugador hubiera pasado el turno. ---------------- Fin del juego ---------------- Al término del juego, el motor simplemente cierra stdin y stdout. El bot debe cerrarse correctamente al detectar una condición de EOF en stdin. En resumen, las comunicaciones con el bot siguien la siguiente progresión: << Inicialización (mapa, faros, etc.) >> Hello (nombre del bot) << Estado al comienzo del turno (faros, conexiones, energía, etc.) >> Jugada (comando) << Resultado de la jugada << Estado al comienzo del turno (faros, conexiones, energía, etc.) >> Jugada (comando) << Resultado de la jugada (...) << Estado al comienzo del turno (faros, conexiones, energía, etc.) >> Jugada (comando) << Resultado de la jugada << [EOF] >> [EOF]