|
51
|
Programación / Programación General / ¿Cómo hacer un programa que aprenda a jugar expertamente Signos en línea?
|
en: 24 Marzo 2025, 07:12 am
|
Aquí pueden ver a la máquina jugando al azar: https://youtu.be/NtGWCFXvfk8En vez de 4 en línea puede ser 3, 5, etc. Lo que tengo no es suficiente y no lo apliqué: // Signos en línea en tablero de Lado*Lado: // Este programa tiene básicamente 2 etapas, // que se pueden resumir así: // 1: // Desde la situación inicial probar las acciones, // guardando las situaciones nuevas, // y probando acciones en ellas también. // Guardar las situación-acción que causan una victoria, // y las que causan un empate. Es el RE de esa situación, // resultado esperado (porque en otros casos es el que... // se espera o supone si el rival juega bien). // 2: ACTUALIZAR ESTA // En las situaciones no resueltas, probar las acciones. // Si las consecuencias tienen RE, anotar cuantas de ellas... // son victoria de X, 0 o empate y anotar la acción que... // cause la RE más favorable, considerando secundariamente... // las cantidades. // JuntosRequeridos = 2; CasillerosPorFila = 2; // Contando desde 0: MaxColumna = CasillerosPorFila-1; // Los casilleros están numerados así: // 0 1 2 // 3 4 5 // 6 7 8 Casilleros = CasillerosPorFila*CasillerosPorFila; MaxCasillero = Casilleros-1; // Uso arrays porque es más fácil así modificar partes... // de los valores de variables (los casilleros): // Situación a experimentar nro 1, signo en los casilleros // (a este array se le agregarán valores): SaE1 = []; ValoraAgregar = -1; do { ValoraAgregar++; // v significa vacío SaE1.push("v"); // trace("ValoraAgregar es "+ValoraAgregar); } while (ValoraAgregar < MaxCasillero); delete (ValoraAgregar); // Situaciones a experimentar (cuántas son): SaE = 1; NroDeSExperimentandose = 0; NoEvaluedSaE = "SaE1"; SaEEvaluedyJoined = "S"+(eval(NoEvaluedSaE).join("")); // Usada para fácilmente saber de qué signo es turno: set (SaEEvaluedyJoined+"Turno", "X"); // Con LastAPosible, usada para evitar probar... // acciones innecesarias y saber si sólo queda 1: set (SaEEvaluedyJoined+"FirstAPosible", 0); set (SaEEvaluedyJoined+"LastAPosible", MaxCasillero); // Usada a continuación y en varios casos: function PrepararExperimentosEnOtraS () { // trace("Ejecutando PrepararExperimentosEnOtraS"); NroDeSExperimentandose++; trace("Se inician experimentos en otra situación, la anotada nro "+NroDeSExperimentandose); // Usada para evitar concatenar repetidamente: NoEvaluedSaE = "SaE"+NroDeSExperimentandose; SituacionBase = eval(NoEvaluedSaE); SaEEvaluedyJoined = "S"+(SituacionBase.join("")); Turno = eval(SaEEvaluedyJoined+"Turno"); FirstActionPosible = eval(SaEEvaluedyJoined+"FirstAPosible"); LastActionPosible = eval(SaEEvaluedyJoined+"LastAPosible"); Action = FirstActionPosible; } PrepararExperimentosEnOtraS(); // Usada en frame 2 repetidamente: function BuscarBuenasSituacionesEt1 () { // Plantear situación porque puede modificarse pero... // podría requerirse su estado original luego: Situacion = SituacionBase.slice(); // trace("La situación experimentándose es "+Situacion); EvaluarAplicabilidadEt1(); } // Usada luego de que cambia la SaE y/o la acción: function EvaluarAplicabilidadEt1 () { // trace("Ejecutando EvaluarAplicabilidadEt1"); if (Situacion[Action] == "v") { // trace("La acción "+Action+" es realizable."); AplicarAccionEt1(); } else { // trace("La acción "+Action+" no es realizable."); BuscarAccionEt1(); } } // Usada luego de EvaluarAplicabilidadEt1, // si una acción es realizable: function AplicarAccionEt1 () { // trace("Ejecutando AplicarAccionEt1"); Situacion[Action] = Turno; // trace("Se realizó la acción "+Action+"; ahora la situación es "+Situacion); // ¿Ganó? // Si no se detecta victoria vertical, // revisa de otros modos (ver las funciones Chequear): ChequearVertical(); if (Win == "Sí") { // Sí, crear datos sobre eso: // trace("Ganó, ahora se sabe que en la situación "+SituacionBase+" se gana usando la acción "+Action); set (SaEEvaluedyJoined+"BestAction", Action); set (SaEEvaluedyJoined+"RE", Turno); // Se borra la situación de la lista a experimentar, // pues ya se halló la mejor acción en ella: BorrarSaE(); BuscarOtraSituacionEt1(); } else if (FirstActionPosible == LastActionPosible) { // sino si es empate (no hay más acciones posibles): // Parecido, crear datos sobre eso: // trace("Empató, ahora se sabe que en la situación "+SituacionBase+" se empata usando la acción "+Action); // (y es la mejor acción porque no hay otra) set (SaEEvaluedyJoined+"BestAction", Action); set (SaEEvaluedyJoined+"RE", "Empate"); // Se borra la situación de la lista a experimentar, // pues ya se halló la mejor acción en ella: BorrarSaE(); BuscarOtraSituacionEt1(); } else { EvaluarConocimientoSobreSEt1(); // ¿Queda alguna acción sin probar... // en la situación? BuscarAccionEt1(); } } // Usada luego de AplicarAccionEt1, // si causa victoria o empate: function BorrarSaE () { // La última SaE ponerla en la actual: // trace("ATENCIÓN: "+NoEvaluedSaE+", que es "+SaEEvaluedyJoined); set (NoEvaluedSaE, eval("SaE"+SaE).slice()); // trace("...ahora es: "+eval(NoEvaluedSaE)); // Borrar la última: delete (eval("SaE"+SaE)); // Indicar que hay una menos: SaE--; // También se borran datos prácticos que ya no se usarán: // trace("SaEEvaluedyJoined: "+SaEEvaluedyJoined); // trace("SaEEvaluedyJoined+Turno: "+eval(SaEEvaluedyJoined+"Turno")); delete (eval(SaEEvaluedyJoined+"Turno")); // trace("SaEEvaluedyJoined+Turno: "+eval(SaEEvaluedyJoined+"Turno")); delete (eval(SaEEvaluedyJoined+"FirstAPosible")); delete (eval(SaEEvaluedyJoined+"LastAPosible")); // Indicar que vuelva a invertigarse la del número actual, // pues ahora es otra: NroDeSExperimentandose--; } // Usada luego de EvaluarAplicabilidadEt1, // cuando una acción no es realizable: function BuscarAccionEt1 () { // trace("Ejecutando BuscarAccionEt1"); if (Action < LastActionPosible) { // Si queda alguna acción sin probar... // en la situación, probarla: Action++; // Se ejecutará BuscarBuenasSituaciones. } else { // trace("No hay más acciones realizables."); BuscarOtraSituacionEt1(); } } // Usada luego de que hay victoria, empate o si no hay más acciones posibles: function BuscarOtraSituacionEt1 () { // trace("Ejecutando BuscarOtraSituacionEt1"); if (SaE > NroDeSExperimentandose) { PrepararExperimentosEnOtraS(); } else { trace( "No hay más situaciones a experimentar; inicia la etapa 2."); // IniciarEtapa2(); gotoAndStop(4); } } // Usada en AplicarAccionEt1 cuando no hay victoria ni empate: function EvaluarConocimientoSobreSEt1 () { // trace("Ejecutando EvaluarConocimientoSobreSEt1"); SituacionJoined = Situacion.join(""); NombreDeSituacion = "S"+SituacionJoined; ResumenDeBest = NombreDeSituacion+"BestAction"; // ¿Se ha llegado antes a la situación? if (eval(ResumenDeBest) == undefined) { // No, anotar que debe investigarse: SaE++; // Copiar array (porque no se puede normalmente): set ("SaE"+SaE, Situacion.slice()); // trace("Esa situación no está anotada; se anota como nro "+SaE); set(ResumenDeBest, "Desconocida"); if (Turno == "X") { set (NombreDeSituacion+"Turno", "0"); } else { set (NombreDeSituacion+"Turno", "X"); } // Averiguar cual es su 1er acción posible: AccionRevisandose = -1; do { AccionRevisandose++; if (Situacion[AccionRevisandose] == "v") { set (NombreDeSituacion+"FirstAPosible", AccionRevisandose); break; } } while (true); // Averiguar cual es su última acción posible: AccionRevisandose = Casilleros; do { AccionRevisandose--; if (Situacion[AccionRevisandose] == "v") { set (NombreDeSituacion+"LastAPosible", AccionRevisandose); break; } } while (true); } } function ObtenerColumnayFilaDelMarcado () { FilaDelMarcado = Math.floor(Action/CasillerosPorFila); ColumnaDelMarcado = Action%CasillerosPorFila; } function ChequearVertical () { // trace ("Ejecutando ChequearVertical"); // // SOLO FUNCIONA SI CasillerosPorFila >= JuntosRequeridos. // // Se revisará la vertical. Desde el casillero más arriba... // que pueda estar incluído en la línea, // hasta el casillero marcado. Si se halla alguno que no... // tenga la marca requerida, se repite el proceso mirando... // desde uno más abajo del fallado, y mirando ahora hasta... // más abajo, según corresponda: Desde = Action-CasillerosPorFila*(JuntosRequeridos-1); // Mientras el casillero no exista: while (Desde<0) { // trace ("Desde es "+Desde+", se aumentará"); // Indicar que se empiece a mirar desde más abajo: Desde = Desde+CasillerosPorFila; } do { Hasta = Desde+CasillerosPorFila*(JuntosRequeridos-1); if (Hasta>MaxCasillero) { // Para ganar se necesitaría más casilleros hacia... // abajo de los que hay, terminar análisis: // trace ("No hay suficientes cuadraditos abajo"); break; } Puntero = Desde; // trace ("Comenzando chequeo desde "+Desde+" hasta "+Hasta); // Puede cambiar: Win = "Sí"; do { // trace ("Chequando el casillero "+Puntero); if (Situacion[Action] != Situacion[Puntero]) { Win = "No"; Desde = Puntero+CasillerosPorFila; break; } Puntero = Puntero+CasillerosPorFila; } while (Puntero<=Hasta); } while (Desde<=Action && Win == "No"); if (Win == "No") { ChequearHorizontal(); } } function ChequearHorizontal () { // trace ("Ejecutando ChequearHorizontal"); // Es similar al chequeo vertical, pero aquí no sirve... // sumar o restar a un puntero que marque el casillero... // porque puede existir ese casillero pero no estar en... // un costado. En vez de eso, se obtiene su columna y... // fila, se modifica la columna si es posible, // y se calcula cual es el casillero a mirar: ObtenerColumnayFilaDelMarcado(); DesdeColumna = ColumnaDelMarcado-JuntosRequeridos+1; // Para que no mire cuadraditos inexistentes en la izq: if (DesdeColumna<0) { DesdeColumna = 0; } do { HastaColumna = DesdeColumna+JuntosRequeridos-1; if (HastaColumna>MaxColumna) { // Para ganar se necesitaría más casilleros hacia... // la derecha de los que hay, terminar análisis: // trace ("No hay suficientes cuadraditos a la derecha"); break; } Puntero = DesdeColumna; // trace ("Comenzando chequeo desde "+DesdeColumna+" hasta "+HastaColumna); // Puede cambiar: Win = "Sí"; do { CasilleroaMirar = FilaDelMarcado*CasillerosPorFila+Puntero; // trace ("Chequando el casillero "+CasilleroaMirar); if (Situacion[Action] != Situacion[CasilleroaMirar]) { Win = "No"; DesdeColumna = Puntero+1; break; } Puntero++; } while (Puntero<=HastaColumna); } while (DesdeColumna<=ColumnaDelMarcado && Win == "No"); if (Win == "No") { ChequearDesdeUpIzq(); } } // La diagonal así \ function ChequearDesdeUpIzq () { // trace ("Ejecutando ChequearDesdeUpIzq"); DesdeColumna = ColumnaDelMarcado-JuntosRequeridos+1; // trace("DesdeColumna: "+DesdeColumna); DesdeFila = FilaDelMarcado-JuntosRequeridos+1; // trace("DesdeFila: "+DesdeFila); // Para que no mire cuadraditos inexistentes: if (DesdeColumna<DesdeFila) { Sumar = DesdeColumna; } else { Sumar = DesdeFila; } // trace("Sumar: "+Sumar); if (Sumar<0) { // trace("Sumando."); DesdeColumna = DesdeColumna-Sumar; // trace("DesdeColumna: "+DesdeColumna); DesdeFila = DesdeFila-Sumar; // trace("DesdeFila: "+DesdeFila); } do { HastaColumna = DesdeColumna+JuntosRequeridos-1; if (HastaColumna>MaxColumna) { // Para ganar se necesitaría más casilleros hacia... // la derecha de los que hay, terminar análisis: // trace ("No hay suficientes cuadraditos a la derecha"); break; } HastaFila = DesdeFila+JuntosRequeridos-1; // Sirve usar MaxColumna en vez de crear MaxFila... // porque como el tablero es cuadrado serían iguales: if (HastaFila>MaxColumna) { // Para ganar se necesitaría más casilleros hacia... // abajo de los que hay, terminar análisis: // trace ("No hay suficientes cuadraditos abajo"); break; } PunteroDeColumna = DesdeColumna; PunteroDeFila = DesdeFila; // trace ("Comenzando chequeo desde columna "+DesdeColumna+" y fila "+DesdeFila); // trace ("hasta columna "+HastaColumna+" y fila "+HastaFila); // Puede cambiar: Win = "Sí"; do { CasilleroaMirar = PunteroDeFila*CasillerosPorFila+PunteroDeColumna; // trace ("Chequando el casillero "+CasilleroaMirar); if (Situacion[Action] != Situacion[CasilleroaMirar]) { Win = "No"; DesdeColumna = PunteroDeColumna+1; DesdeFila = PunteroDeFila+1; break; } PunteroDeColumna++; PunteroDeFila++; } while (PunteroDeColumna<=HastaColumna); } while (DesdeColumna<=ColumnaDelMarcado && Win == "No"); if (Win == "No") { ChequearDesdeDownIzq(); } } // La diagonal así / function ChequearDesdeDownIzq () { // trace ("Ejecutando ChequearDesdeDownIzq"); DesdeColumna = ColumnaDelMarcado-JuntosRequeridos+1; // trace("DesdeColumna: "+DesdeColumna); DesdeFila = FilaDelMarcado+JuntosRequeridos-1; // trace("DesdeFila: "+DesdeFila); // Para que no mire cuadraditos inexistentes: ColumnasInexistentes = 0; if (DesdeColumna<0) { ColumnasInexistentes = DesdeColumna*-1; } FilasInexistentes = 0; // Está bien usar MaxColumna porque MaxFila sería igual: if (DesdeFila>MaxColumna) { FilasInexistentes = DesdeFila-MaxColumna; } if (ColumnasInexistentes>=FilasInexistentes) { Ajuste = ColumnasInexistentes; } else { Ajuste = FilasInexistentes; } // trace("Ajuste: "+Ajuste); if (Ajuste>0) { // trace("Ajustando."); DesdeColumna = DesdeColumna+Ajuste; // trace("DesdeColumna: "+DesdeColumna); DesdeFila = DesdeFila-Ajuste; // trace("DesdeFila: "+DesdeFila); } do { HastaColumna = DesdeColumna+JuntosRequeridos-1; if (HastaColumna>MaxColumna) { // Para ganar se necesitaría más casilleros hacia... // la derecha de los que hay, terminar análisis: // trace ("No hay suficientes cuadraditos a la derecha"); break; } HastaFila = DesdeFila-JuntosRequeridos+1; if (HastaFila<0) { // Para ganar se necesitaría más casilleros hacia... // arriba de los que hay, terminar análisis: // trace ("No hay suficientes cuadraditos arriba"); break; } PunteroDeColumna = DesdeColumna; PunteroDeFila = DesdeFila; // trace ("Comenzando chequeo desde columna "+DesdeColumna+" y fila "+DesdeFila); // trace ("hasta columna "+HastaColumna+" y fila "+HastaFila); // Puede cambiar: Win = "Sí"; do { CasilleroaMirar = PunteroDeFila*CasillerosPorFila+PunteroDeColumna; // trace ("Chequando el casillero "+CasilleroaMirar); if (Situacion[Action] != Situacion[CasilleroaMirar]) { Win = "No"; DesdeColumna = PunteroDeColumna+1; DesdeFila = PunteroDeFila-1; break; } PunteroDeColumna++; PunteroDeFila--; } while (PunteroDeColumna<=HastaColumna); } while (DesdeColumna<=ColumnaDelMarcado && Win == "No"); // trace("Win: "+Win); }
Resumen:  Mi idea con eso es hallar las mejores acciones en cada posible situación de un tablero de cierto tamaño cuando se requieren cierta cantidad de signos en línea, es decir, si por ejemplo en el "aprendedor" se configura que el tablero tiene 6 casilleros de largo y se requieren 5 signos en línea, el programa aprendería a jugar así, no en 5 casilleros de largo, ni 7, etc, ni con 6 signos requeridos, etc. Así que en ese sentido no voy por buen camino, ya que no puedo correr el programa poniendo cada pares posibles de valores. Y aunque pudiera programar que los varíe, los resultados son de este estilo: SvX0vBestAction = 0 es decir el nombre de la variable indica la situación, lo que sucederá es que serán un montón de variables con nombres cada vez más largos, es mejor algo más deductivo, unas reglas generales, siempre y cuando den buenos resultados. La etapa 2 creo que debería tener en cuenta probabilidades, es decir, que cuando por ejemplo el resultado esperado es un empate, busque llegar a situaciones en que la mayoría de opciones del rival lo lleven a una derrota. Las probabilidades se basarían en la cantidad de opciones y las que salvan al rival, o a uno mismo (si el resultado esperado es derrota, aunque en este juego una IA no debería llegar a eso), no en cuan evidentes para un humano son las opciones mejores, porque eso parece subjetivo y complicado, ni lo intento. Aún no tengo del todo claro cómo hacer la etapa 2. Y he estado cambiando cosas de la 1. En eso empecé a creer que en la etapa 1 debería guardar las posibilidades o en la 2nda no podría saber las probabilidades, o sería complicado. Creo que debería poner 3 etapas... 1- Se pueban acciones en las posibles situaciones, se van anotando las situaciones, y se anota "la mejor acción" y resultado cuando en la situación sólo hay 1 acción posible. 2- En las situaciones que quedaron sin resolver, se prueban acciones... No sé bien... en ese punto una acción puede: - Llevar a una situación resuelta, que luego será un empate o derrota. - Llevar a una situación no resuelta. - Llevar a una victoria. Supongamos que está en: 0XX XX0 0AB Turno del 0. Si pone en A, lleva a una situación resuelta, sólo queda una posible jugaba, B, y es empate. Si pone en B, también es resuelta, pero es derrota. Entonces deben crearse unas variables digamos así: S0XXXX00vvWinX = 1 S0XXXX00vvEmpate = 1 marcando que en esa situación hay 1 posibilidad de que gane X y otra de que haya empate. La idea es que en la etapa 3, cuando haya que elegir cual es la mejor acción en una situación anterior a esta, cuando se llegue a esta se vea que X tiene 1/2 de probabilidad de ganar, aunque el resultado esperado sea empate, logrando entonces que el programa deduzca que para X es una situación más conveniente a llegar que otras, por ejemplo: 0CX XX0 0AB Turno de X. Si 0 juega bien, será empate, pero X poniendo en C o A tiene probabilidades de ganar, poniendo en B no. Se trata de guardar posibilidades y luego guardar las acciones que lleven a situaciones en que haya más probabilidades de ganar. Otra posibilidad es que lleve a una victoria, ejemplo: 0Xv 0Xv vvv Turno del X. Lo que le conviene a X es evidente, pero... 0 debe tener en cuenta la posibilidad de que no haga eso. En tableros más grandes, puede que se deba elegir entre 2 situaciones inconvenientes, quizá en una X tiene 2 formas de ganar y en la otra sólo 1, por lo tanto debe grabarse la 2nda. Para eso debe haber datos sobre qué puede suceder, y por eso aunque la mejor acción aquí sea evidente, la situación no se puede resolver mientras no se tengan datos de las próximas, para saber exactamente qué tan probable es que gane X en esta (parece 1/5 pero es más complejo), para que previamente 0 pueda saber si esta situación es peor que otra alternativa, como expliqué. Todo esto es como muy rebuscado, creo que sólo tiene sentido en tableros grandes. Normalmente la IA no llegaría a situaciones en que pueda perder. A ver un caso más realista: vvvv vXvv vvvv vvvv Turno de 0, se requieren 3 para ganar. Parece que no importa lo que haga, perderá, pero la idea es que tenga las mayores probabilidades posibles de ganar o al menos empatar. En fin, parece que entonces es cuestión de ir hacia atrás, en orden, es decir, resolver 1ero las situaciones en que sólo 1 acción es posible, luego las que sólo tienen 2, etc. Aunque en algunas sea obvio cómo ganar, no se pueden considerar resueltas antes de tiempo. Así que sería: Etapa 1: Las acciones pueden llevar a situaciones nuevas, no resueltas, empates o victorias. Etapa 2: Las acciones pueden llevar a situaciones no resueltas, resueltas o victorias. Etapa 3: Las acciones pueden llevar a situaciones no resueltas o resueltas. Considero importantes las diferencias para evitar ifs en vano... Una posible mejora del programa sería, en vez de partir de un tablero vacío e ir grabando situaciones a las que se llegue, generar las posibles situaciones finales, resolverlas, generar las situaciones previas, resolverlas, y así. O en vez de tener 1 lista de situaciones, tener una por cada cantidad de signos puestos, o sea: Una lista de situaciones en que sólo 1 acción es posible. Otra en que sólo 2 acciones son posibles. Etc. Son mejoras alternativas. Sirven para evitar que el programa analice repetidamente situaciones que sabemos que aún no podrá resolver. ¿Ustedes cómo lo harían? Un programa que aprenda a jugar expertamente Signos en línea.
|
|
|
52
|
Programación / Programación General / Captar victoria en juego estilo 3 en línea. 2 contiguos solamente.
|
en: 20 Enero 2025, 19:40 pm
|
La cantidad de casilleros por fila y columna es configurable.
La cantidad de contiguos para ganar, también.
Pero por ahora quiero resolver el caso de 2 contiguos simplemente.
La situación se guarda en un array, es decir lo que tiene cada casillero, por ejemplo esta: vv0 vXv vvv Es: v, v, 0, v, X, v, v, v, v donde v es vacío
Mi idea es que, cuando un casillero se marque, se mire arriba, luego arriba-derecha, y así sucesivamente como las agujas de un reloj.
El reto es averiguar qué casillero en el array es el de arriba, y demás. Y si es que existe, sino capaz que mira alguno que no debe, no sé.
Si por ejemplo el casillero marcado es el nro 0, el de arriba no existe, el de arriba-derecha tampoco, el de la derecha es su +1... Pero si se marca el nro 2, el de su derecha no es el +1, sino que no existe.
Quizá la mejor forma de resolver esto es creando variables estilo: Casillero0_0 = "v" Casillero1_0 = "v" Casillero2_0 = "0"
Pero entre más grande sea el tablero, más variables así habría y son una copia de las otras así que no parece buena idea.
Debe haber una forma de calcular en qué posición del array está el casillero contiguo que se quiere mirar ¿cómo?
|
|
|
53
|
Programación / Programación General / Indicar que una acción no debe usarse.
|
en: 20 Enero 2025, 04:16 am
|
Intento hacer una IA que aprenda a poner una X en un tablero y luego otra en la derecha o izquierda. El problema que intento resolver en cuanto a eso, es que puede poner una X donde ya hay, debo evitarlo. Sé que debo poner algo como: if (Situacion[Action] == "v") { // Es realizable, realizarla... sino // No, ver si hay otra...
Pero no sé bien qué pasa, es como que si hago eso tendría que poner código igual en distintos lados (aunque a ese código lo convierta en una función, siento que es innecesario que se repita), innecesariamente. Aún no tengo el código completo. Díganme cómo pondrían uds ese if, dónde, o cómo harían. // 2 en línea horizontal, en tablero de 2x2, sólo X: // // Uso arrays porque es más fácil así modificar los nombres... // de variables: // // Los casilleros están numerados así: // 0 1 // 2 3 // Situación inicial (v es casillero vacío): SituacionBase = ["v", "v", "v", "v"]; // Ponerla en la lista de situaciones a experimentar: SaE1 = SituacionBase.slice(); // Situaciones a experimentar (cuántas son): SaE = 1; NroDeSExperimentandose = 1; NombreDeSE = "S"+(SaE1.join("")); // Acción a realizarse (casillero a marcar): Action = 0; function BuscarAccionesGanadoras () { PlantearSyActuar(); // ¿Ganó? // Para averiguarlo, calcular fila (contando desde 0): Fila = Math.floor(Action/2); // trace(" Fila: "+Fila); if (Situacion[Fila*2] == Situacion[Fila*2+1]) { // trace("Situacion[Fila*2] == Situacion[Fila*2+1]: "+Situacion[Fila*2]+" == "+Situacion[Fila*2+1]); // Sí, crear datos sobre eso: // En la situación base conviene la acción realizada: set (NombreDeSE+"BestAction", Action); // En esa situación se requiere 1 acción para ganar: set (NombreDeSE+"DistanciaaGanar", 1); trace("Ganó, ahora se sabe que en la situación "+SituacionBase+" se gana usando la acción "+Action); // ¿Aún hay situaciones a experimentar? if (SaE > NroDeSExperimentandose) { IniciarExperimentosEnOtraS(); } else { // Comenzar la etapa 2. IniciarEtapa2(); } } else { EvaluarConocimientoSobreSyEtc(); } } function PlantearSyActuar () { // Plantear situación porque puede modificarse pero... // podría requerirse su estado original luego: Situacion = SituacionBase.slice(); trace(" La situación experimentándose es "+Situacion); // Aplicación de la acción: Situacion[Action] = "X"; trace(" Se realizó la acción "+Action+"; ahora la situación es "+Situacion); } function IniciarExperimentosEnOtraS () { NroDeSExperimentandose++; SituacionBase = eval("SaE"+NroDeSExperimentandose); // trace("SituacionBase: "+SituacionBase); // Reset de la acción a probar: Action = 0; trace("Se inician experimentos en otra situación"); } function IniciarEtapa2 () { DistanciaaGanarAceptada = 1; trace("Se experimentaron todas las situaciones; se reexperimentarán buscando resultados menos óptimos"); trace("Son "+SaE+" situaciones; se acepta que la distancia a la victoria sea "+DistanciaaGanarAceptada); gotoAndStop (3); } function EvaluarConocimientoSobreSyEtc () { SituacionHechaTexto = Situacion.join(""); NombreDeSituacion = "S"+SituacionHechaTexto; Resumen = NombreDeSituacion+"BestAction"; if (eval(Resumen) == undefined) { // No, anotar que debe investigarse: SaE++; set ("SaE"+SaE, Situacion.slice()); trace("Esa situación no está anotada; se anota como nro "+SaE); // Esto para saber que está anotada: set (Resumen, "Desconocida"); } // ¿Queda alguna acción sin probar? if (Action < 3) { // Sí, probar otra: Action++; } else if (SaE > NroDeSExperimentandose) { // Sino si aún hay situaciones a experimentar: trace("No hay más acciones posibles en la situación"); IniciarExperimentosEnOtraS(); } else { // Comenzar la etapa 2. IniciarEtapa2(); } }
Ah, BuscarAccionesGanadoras se repite hasta que empiece la etapa 2.
|
|
|
54
|
Programación / Programación General / ¿Qué lío he hecho con mi generador de tableros estilo 3 en línea?
|
en: 19 Enero 2025, 23:57 pm
|
Así funcionaba: function HacerTablero () { MaxCasillero = CasillerosPorFila*CasillerosPorFila; LargoDeCasilleros = 384/CasillerosPorFila; CasillerosColocados = 0; // Sus columnas y filas se cuentan desde 0, // sirven para posicionarlos más fácil: Columna = 0; Fila = 0; do { CasillerosColocados++; CasilleroaColocar = "Casillero"+CasillerosColocados; // v es vacío. set ("SignoEn"+CasilleroaColocar, "v"); // sCasillero tiene código. attachMovie("sCasillero", CasilleroaColocar, CasillerosColocados); setProperty (CasilleroaColocar, _x, LargoDeCasilleros*Columna); setProperty (CasilleroaColocar, _y, LargoDeCasilleros*Fila); setProperty (CasilleroaColocar, _width, LargoDeCasilleros); setProperty (CasilleroaColocar, _height, LargoDeCasilleros); Columna = Columna+1; if (Columna == CasillerosPorFila) { Columna = 0; Fila = Fila+1; } // Para que al presionar el botón se sepa qué modificar: set (CasilleroaColocar+".CasilleroAsociado", CasillerosColocados); } while (CasillerosColocados<MaxCasillero); }
Intenté poner que se cuenten desde 0 en vez desde 1 (o sea, que en vez de Casillero1, Casillero2, etc, sean Casillero0, Casillero1, etc) y ahora anda mal, pone uno menos y además lo borra mal (eso es otro tema): function HacerTablero () { // Esto se irá especificando en el while: Situacion = []; PosiblesAcciones = []; // Los casilleros se cuentan desde 0: MaxCasillero = CasillerosPorFila*CasillerosPorFila-1; LargoDeCasilleros = 384/CasillerosPorFila; NroDeCasillerosaColocar = -1; // Sus columnas y filas se cuentan desde 0, // sirven para posicionarlos más fácil: Columna = 0; Fila = 0; do { NroDeCasilleroaColocar++; // v es vacío: Situacion.push("v"); PosiblesAcciones.push(NroDeCasilleroaColocar); CasilleroaColocar = "Casillero"+NroDeCasilleroaColocar; // set ("SignoEn"+CasilleroaColocar, "v"); // sCasillero tiene código. attachMovie("sCasillero", CasilleroaColocar, NroDeCasilleroaColocar); setProperty (CasilleroaColocar, _x, LargoDeCasilleros*Columna); setProperty (CasilleroaColocar, _y, LargoDeCasilleros*Fila); setProperty (CasilleroaColocar, _width, LargoDeCasilleros); setProperty (CasilleroaColocar, _height, LargoDeCasilleros); Columna = Columna+1; if (Columna == CasillerosPorFila) { Columna = 0; Fila = Fila+1; } // Para que al presionar el botón se sepa qué modificar: set (CasilleroaColocar+".CasilleroAsociado", NroDeCasilleroaColocar); trace("Colocado el "+NroDeCasilleroaColocar); } while (NroDeCasilleroaColocar<MaxCasillero); }
Si el tablero es de 3*3, se deben colocar 9, pero el máximo será 8 porque se cuentan desde 0. Y puse que el 1er colocado sea el 0. Pero el trace no lo indica, el 1er trace es del 1: Colocado el 1 Colocado el 2 ... Colocado el 8 ¿Qué pasa? Edit: Creo que era una s de más, sorry >___< NroDeCasillerosaColocar = -1; debe ser: NroDeCasilleroaColocar = -1;
|
|
|
55
|
Programación / Programación General / Busco testers para mi generador de dibujos, hallar errores, paso el editable también.
|
en: 15 Enero 2025, 16:18 pm
|
El siguiente link tiene un exe, un fla que es el archivo editable, y un swf que es como un exe menos peligroso pero sólo sirve si tienen Flash Player. Actualización de acuerdo al momento en que el post fue editado: 25-01-15, 16:18 https://drive.google.com/file/d/17Mn04J7GUnn4CGaqjnE70UeGHISAVqbN/view?usp=sharingNota: Al cliquear un dibujo, muta. Errores conocidos que intentaré arreglar más adelante: 1: La flecha de abajo que apunta a la izquierda, cuando se usa en la 1er pantalla quiero que lleve a las opciones de la última, pero aún no existe, así que está mal, no la usen en la 1er pantalla, saca las opciones y el programa se vuelve inusable. 2: Los botones de colores en la 2nda pantalla (flecha de abajo que apunta a la derecha) no deberían estar ahí. No los usen porque aún no llegué a arreglar esa parte, esos botones quedaron ahí por error. 3: No hay 3era pantalla así que tocar dos veces el botón de la derecha es mala idea. 4: Si habiendo X colores definidos (por ejemplo 3) se define uno salteando intermedios (por ejemplo habiendo 3, se define el 5), entonces al producirse los dibujos los colores intermedios serán negro. Solución planeada: Se elegirán colores al azar para esos. 5: Aparentemente, si mientras se generan los dibujos señalas un botón de colores preestablecidos y quitas el mouse antes de que el proceso termine, el botón quedará parpadeando. Es un efecto que debería ocurrir sólo mientras el mouse apunta al botón, pero como el programa estaba haciendo otras cosas no notó que el mouse se fue de ahí, supongo. En PC rápidas posiblemente no ocurra por poca cosa, tendrían que aumentar la cantidad de dibujos y cuadraditos, tal vez. 6: Puede que se vean pequeñas líneas entre los cuadraditos, como si no ocuparan todo el espacio que deben. Un problema del largo y ancho supongo, que es calculado, si el resultado es en decimales ya saben que pueden pasar ese tipo de cosas. 7: Si bajas la cantidad de cuadraditos por fila, la mínima cantidad de colores requeridos debe bajar, porque si por ejemplo es 3 pero sólo hay 2 cuadraditos pintables (el resto son simétricos), entonces podrá haber 3 colores en el dibujo. Eso se corrige pero la corrección hace que a partir de ahí el último color definido se elimine, o se deje de usar, aunque en el la pantalla se vea que está definido. 8: Si agregas un color, luego aumentas la cantidad de mínimos requeridos, y luego bajas la cantidad de cuadraditos por fila, el programa se tranca por lo que expliqué en el punto anterior. No sé por qué no lo corrige en este caso. Estoy corrigiendo, me toma tiempo...
|
|
|
56
|
Programación / Python / Ayuda para poner prints en código que no entiendo bien.
|
en: 14 Enero 2025, 15:32 pm
|
El programa genera las combinaciones de 2 letras. Carga un diccionario en inglés y otro en español. Anota las combinaciones de 2 letras que hay en las palabras. Luego anota las combinaciones de 2 letras que ocurren cuando 2 palabras se unen. Así por ejemplo se obtiene zh, por nariz y hoy. Finalmente, muestra las combinaciones que no ocurrieron, lo hace quitando de la 1er lista (la del inicio) las que anotó. Eso si funciona bien, no sé. Lo que quiero es que cuando anote una combinación, me diga cual anotó. Así me entero de cómo se forma qq por ejemplo. import itertools def cargar_diccionario(ruta): """Carga el diccionario desde un archivo .txt y devuelve un conjunto de palabras en minúsculas.""" with open(ruta, 'r', encoding='utf-8') as archivo: return set(palabra.strip().lower() for palabra in archivo) def generar_combinaciones(alfabeto): """Genera todas las combinaciones posibles de dos letras del alfabeto.""" return {a + b for a, b in itertools.product(alfabeto, repeat=2)} def buscar_combinaciones(diccionario, combinaciones): """Busca combinaciones dentro de las palabras del diccionario.""" presentes = set() # Buscar combinaciones dentro de palabras individuales for palabra in diccionario: for i in range(len(palabra) - 1): presentes.add(palabra[i:i+2]) # Buscar combinaciones entre palabras consecutivas lista_palabras = sorted(diccionario) # Ordenar las palabras for i in range(len(lista_palabras) - 1): ultima_letra = lista_palabras[-1] primera_letra = lista_palabras[i + 1][0] presentes.add(ultima_letra + primera_letra) return combinaciones - presentes # Paso 1: Cargar diccionarios diccionario_es = cargar_diccionario('diccionario_es.txt') diccionario_en = cargar_diccionario('diccionario_en.txt') # Paso 2: Generar combinaciones posibles alfabeto_es = 'abcdefghijklmnñopqrstuvwxyz' alfabeto_en = 'abcdefghijklmnopqrstuvwxyz' combinaciones_es = generar_combinaciones(alfabeto_es) combinaciones_en = generar_combinaciones(alfabeto_en) # Paso 3: Buscar combinaciones inexistentes en cada diccionario inexistentes_es = buscar_combinaciones(diccionario_es, combinaciones_es) inexistentes_en = buscar_combinaciones(diccionario_en, combinaciones_en) # Paso 4: Combinar resultados y mostrar combinaciones inexistentes en ambos idiomas inexistentes_totales = inexistentes_es.intersection(inexistentes_en) print(f"Combinaciones inexistentes en ambos idiomas ({len(inexistentes_totales)}):") print(sorted(inexistentes_totales)) input("Presione Enter para continuar ...")
|
|
|
58
|
Foros Generales / Dudas Generales / ¿Qué es un archivo .nsm?
|
en: 13 Enero 2025, 21:42 pm
|
No conseguí la respuesta.
Al abrirlo con el bloc de notas no aparece algo claro, es mucho espacio vacío y caracteres raros.
Es de 2002 dice, aunque quién sabe.
Pesa 22,9 KB (23.530 bytes).
El nombre prefiero no decirlo por las dudas, pero no es algo vergonzoso ni algo así xP
¿Cómo averiguo qué es?
|
|
|
59
|
Media / Diseño Gráfico / Reducir tamaño de la hoja, no de la imagen.
|
en: 13 Enero 2025, 20:41 pm
|
Hace mucho, cuando pegaba una imagen en Paint más grande que la hoja actual, me parecía absurdo que me preguntara si quería agrandar la hoja ¡obvio que sí!
Pero con el tiempo, me empezó a servir poner que no, porque si tomaba una foto pero sólo quería una parte, y luego tomaba otras fotos, porque era un video por ejemplo, me servía que el tamaño de la hoja se mantuviera como lo dejé, para guardar los archivos más rápido, de modo que se viera lo que yo quería y no la foto entera.
Desde que tengo Windows 7, Paint agranda la hoja sin preguntarme, y no veo la opción de modificarle el tamaño (la opción que veo modifica también la imagen, antes creo que no era así, yo no quiero que se agrande/achique la imagen, sólo la hoja), así que ¿con qué programa y cómo podría lograr lo que quiero?
Son varias fotos de un archivo de Flash, que al tomar foto sale el menú y las herramientas, podría modificar algunas cosas pero si cada vez que quiera tomar fotos debo hacer eso no me conviene.
Tomo una foto, supongamos que es así: xxxx xAAx xAAx xxxx Yo sólo quiero guardar la parte de las A. Para eso pego la imagen en Paint, la muevo hacia arriba e izquierda para quitar las x de esos sitios, y me queda así: AAxb AAxb xxxb bbbb Donde b es blanco, y yo ahora quiero reducir la hoja de modo que sólo queden las A. Debería ser fácil...
|
|
|
60
|
Programación / Programación General / IA para 2 en línea horizontal.
|
en: 12 Enero 2025, 23:55 pm
|
El juego es una simplificación del 3 en línea: - El tablero en vez de ser de 3x3 casilleros es de 2x2. - Lo que debe formarse, en vez de ser una línea horizontal, vertical o diagonal de 3 signos iguales, debe ser de 2 pero horizontal. Claro que es menos interesante que el original, es que se me complicó. Hay a grandes rásgos 3 tipos de IA, díganme otras si me faltan: A- La que sabe jugar como el programador le dijo, no aprende. B- La que sabe jugar según lo que entrenó, aprendió todo lo necesario. C- La que está aprendiendo, aprende mientras juega. El problema de A es que si el juego es complejo, no hay persona que sepa el modo ideal de jugar, y tampoco es fácil expresarlo. B requiere más tiempo lograrse entre más complejo sea el juego, por lo que llaman explosión combinatoria o algo así. C, entre más complejo sea el juego, menos útil es, porque difícilmente llegue a aprender todo lo necesario para jugar bien. El jugador podría hacer cosas distintas hasta el cansancio y en tales casos la IA sería como si recién empezara. Supongo que pueden pulirse de distintos modos, pero no sé bien cómo. A, en este caso simple sería así creo: Si IA usa X: // Intentar ganar: Si Casillero1_1 == "" && Casillero1_2 == "X" Poner X en 1_1 sino si Casillero1_1 == "X" && Casillero1_2 == "" Poner X en 1_2 Si Casillero2_1 == "" && Casillero2_2 == "X" Poner X en 2_1 sino si Casillero2_1 == "X" && Casillero2_2 == "" Poner X en 2_2 sino // Ya no es posible ganar (perder nunca fue): Poner en cualquiera vacío. sino // Evitar perder: Si Casillero1_1 == "" && Casillero1_2 == "X" Poner 0 en 1_1 sino si Casillero1_1 == "X" && Casillero1_2 == "" Poner 0 en 1_2 Si Casillero2_1 == "" && Casillero2_2 == "X" Poner 0 en 2_1 sino si Casillero2_1 == "X" && Casillero2_2 == "" Poner 0 en 2_2 sino // Ya no es posible perder: Poner en el único vacío que queda. Sobre C no pensé mucho. B es más difícil de lo que creí, a ver si me dan ideas. Mi idea es algo así: - En la situación inicial (tablero todo vacío), se prueban las acciones posibles y si se llega a una situación que no está en la lista de situaciones no resueltas, se agrega. Al principio esa lista está vacía, ahora habrá 4 porque 4 son las posibles consecuencias de las 4 posibles acciones. - Se genera una de las situaciones de la lista. Probamos las posibles acciones, ahora el signo es 0 en vez de X. Si obtenemos situaciones que no estén en la lista, de nuevo, las anotamos. - Repetimos eso con cada situación, el signo a poner depende de cuales sean, y llegará un punto en que poner un signo causará una victoria, por ejemplo en esta: XV 0V (V es vacío) La acción nro 2 (casillero 1_2, al lado de la X) causa victoria. Cuando eso ocurre, creamos unas variables cuyos nombres implican la situación anterior a la victoria, en el ejemplo sería XV0V, y ponemos una S delante para menos lío, sabemos que S significa situación. Bueno, las variables van a ser por ejemplo: SXV0VBestAction = 2 SXV0VDistanciaDeVictoria = 1 Significa que en esa situación conviene la acción 2 y que hace falta sólo 1 acción para llegar a la victoria. Eliminamos esa acción de la lista de situaciones sin resolver. - Cuando hayamos recorrido la lista de situaciones, comenzamos a recorrerla de nuevo, pero no sé bien qué debería hacerse ahora específicamente. En programas "similares" que había hecho, lo que se hacía era que si se llega a una situación cuya DistanciaDeVictoria fuese 1, a la anterior situación se le asignaba que DistanciaDeVictoria fuese 2, y así se repite realizar acciones, asignar "puntajes" a las situaciones, eliminarlas de la lista, y en cada nueva repetición aumentar el número asignado a DistanciaDeVictoria, hasta que ya no queden situaciones a investigar. Pero en este caso no se puede porque hay turnos, hay rivales, a ver: Si sólo hubiera X, sería así: Comenzamos investigando la situación VV VV Probamos una acción XV VV Anotamos esa situación en la lista de no resueltas. Probamos otra acción en la situación que estamos investigando, nos queda: VX VV En fin, en cierto punto vamos a investigar esta: XV VV Realizamos una acción que causa victoria XX VV Entonces definimos: SXVVVBestAction=2 SXV0VDistanciaDeVictoria = 1 Y en otro punto, cuando volvamos a investigar situaciones aún no resueltas, como esta: VV VV Cuando hagamos esto: XV VV Llegamos a una que la tenemos evaluada como 1, entonces a la anterior (la que puse arriba, todo V) le vamos a poner 2. Está clarísimo, se requieren 2 acciones para ganar desde el inicio. Pero hay 0, así que luego de hallar todas las victorias posibles, cuando volvamos a investigar situaciones aún no resueltas, como esta: XV VV Sabemos que es turno del 0, que si pone en 2_1 o 2_2 se llegará a una situación cuya DistanciaDeVictoria es 1, pero para X, para 0 es 1 de la derrota, así que el programa no puede ponerle 2, eso se interpretaría como que a 0 le hacen falta 2 acciones para ganar, que es segurto que ganará, lo cual no es así. ¿La solución es crear otra variable? ¿que exista DistanciaDeVictoriaDeX y DistanciaDeVictoriaDe0? Hay que tener en cuenta la explosión combinatoria, no conviene crear variables en lo posibñe, además no veo bien cómo eso lo resolvería. A ver, si no llega a una situación cuya DistanciaDeVictoriaDe0 sea 1 (cosa imposible, 0 no puede ganar), nada se anota al respecto. Y cuando la distancia de X es 1, se anota que de 0 es -1, es decir que está a 1 acción de ser derrotado. La distancia ideal es positiva y en 2nda instancia lo más cercana a 0 posible. Bueno... cuando en una búsqueda no se anoten cosas, es decir no se llegue a situaciones resueltas, se comenzaría a anotar las que lleguen a distancias -1, luego a -2, y así, podría ser que se llegue a resolver todas las situaciones. ¿Creen que eso funcionará? ¿no, por qué? ¿Es posible usar sólo una distancia? ¿sí, cómo? ¿Cómo sería un método más rápido y eficiente? Imagino que deduciendo en base a rotar el tablero o considerar simetría, pero creo que debe haber algo como aprender la regla, aprender un código a seguir, no esto de aprender qué hacer en la situación 1, 2, 3... Cuando juegue, sería bueno que siga un código como el que puse al inicio, en vez de "llamar a la función SXVVV" (o sea, cargar los datos de esa situación a ver qué acción conviene en ella), porque cuando sean MUCHAS posibles situaciones, es un disparate hacerlo así, por la explosión ¿no? Imagina un 5 en línea en un tablero de 25x25 con 5 jugadores, por decir algo.
|
|
|
|
|
|
|