Estou tentando escrever um solucionador em C # .NET para um jogo conhecido como Flowerz. Para sua referência, você pode reproduzi-lo no MSN, aqui: http://zone.msn.com/gameplayer/gameplayer.aspx?game=flowerz . Estou escrevendo por diversão, não para qualquer tipo de tarefa ou qualquer coisa relacionada ao trabalho. Por esse motivo, o único limite é o meu computador (um núcleo intel i7, com 8 GB de RAM). Não precisa ser executado em nenhum outro lugar, no que me diz respeito.
Em suma, suas regras são assim:
- Há uma fila cheia de flores coloridas. Seu comprimento é arbitrário
- A fila não pode ser influenciada
- A fila é gerada no início do nível
- As flores têm uma ou duas cores.
- Se houver duas cores, haverá uma cor externa e uma cor interna. No caso de duas cores, a cor externa é usada para correspondência.
- Se houver uma correspondência, a cor externa desaparecerá e a flor agora será uma única cor com a mesma cor da flor interna
- O objetivo do jogo é criar partidas de três (ou mais) da mesma cor
- Quando uma flor de uma única cor faz parte de uma partida, é removida do campo de jogo, criando um espaço vazio
- Você pode combinar uma flor de cor única com a cor externa de uma flor de duas cores. Nesse caso, a flor de uma cor desaparece, a cor externa da flor de duas cores desaparece e a cor interna permanece
- Você vence a rodada quando a fila está vazia e há pelo menos um espaço vazio sobrando
- Jogos em cascata são possíveis. Uma cascata ocorre quando três (ou mais) flores externas desaparecem e quando suas cores internas formam outra cadeia de 3 (ou mais flores).
- O campo de jogo é sempre 7x7
- Alguns espaços no campo são cobertos por pedras
- Você não pode colocar flores nas rochas
- A fila também pode conter uma pá que você pode usar para mover qualquer flor colocada para um espaço desocupado
- Você precisa usar a pá, mas na verdade não precisa mover a flor: é perfeitamente legal colocá-la de volta de onde veio
- A fila também pode conter uma borboleta colorida. Quando você usa essa borboleta em uma flor, a flor fica com a cor da borboleta
- A aplicação de uma borboleta a uma flor com duas cores resulta na obtenção de apenas uma única cor, a saber, a da borboleta
- Você pode desperdiçar a borboleta em um espaço vazio ou em uma flor que já tenha essa cor
- Limpar o campo não ganha o jogo
O objetivo do solucionador é simples: encontre uma maneira de esvaziar a fila, com o maior número possível de espaços restantes no campo de jogo. Basicamente, a IA joga o jogo para mim. A saída do solucionador é uma lista com os movimentos encontrados. Não estou interessado em marcar, mas em sobreviver o maior tempo possível, portanto, estou interessado nos movimentos que deixam o maior número possível de espaços abertos.
Desnecessário dizer que o espaço de pesquisa cresce rapidamente quanto maior a fila, de modo que uma força bruta está fora de questão. A fila começa às 15 e cresce com 5 a cada dois ou três níveis, se bem me lembro. E, é claro, colocar a primeira flor em (0,0) e a segunda em (0,1) é diferente de colocar a primeira em (1,0) e a segunda flor em (0,0), especialmente quando o campo já está preenchido com flores de uma rodada anterior. Uma decisão tão simples pode fazer a diferença em fazê-lo ou não.
As perguntas que tenho são as seguintes:
- Que tipo de problema é esse? (pense em vendedor ambulante, mochila ou algum outro problema combinatório). Saber disso poderia tornar meu Google-fu um pouco melhor.
- Que tipo de algoritmo poderia me dar bons resultados, rápido?
Com relação a este último: no começo, tentei escrever meu próprio algoritmo heurístico (basicamente: como eu o resolveria se eu conhecesse a fila?), Mas isso resulta em muitos casos extremos e correspondência de pontuação que posso perder.
Eu estava pensando em usar um algoritmo genético (porque pelo menos sei como usá-lo ...), mas estou tendo alguns problemas para decidir sobre uma representação binária do quadro. Depois, há o problema do cruzamento, mas isso pode ser resolvido com um operador de cruzamento ordenado ou um tipo semelhante de operação.
Meu palpite é que o solucionador deve sempre conhecer a configuração da placa e a fila que está tentando esvaziar.
Conheço alguns outros algoritmos heurísticos, como redes neurais e sistemas lógicos nebulosos, mas não tenho experiência para saber qual é o melhor aplicável ou se existem outros que são mais adequados para a tarefa em questão.
Respostas:
À primeira vista , isso me parece um problema de busca de um único agente . Ou seja: você tem um agente (o "jogador" da IA). Há um estado do jogo que representa o estado do tabuleiro e da fila do jogo, e você tem uma função sucessora que pode gerar novos estados a partir de um determinado estado.
Há também um critério de objetivo que informa quando o estado é "resolvido". E um custo de caminho - o custo de avançar para um determinado estado (sempre "1 movimento" neste caso).
Um quebra-cabeça prototípico desse tipo é o 15 Puzzle . E a maneira típica de resolvê-lo é com uma pesquisa informada - por exemplo, a pesquisa heurística clássica A * e suas variantes.
No entanto, há um problema com essa abordagem à primeira vista. Algoritmos como A * são projetados para fornecer o caminho mais curto para um objetivo (por exemplo: menor número de movimentos). No seu caso, o número de movimentos é sempre fixa - não há caminho mais curto - para uma busca heurística vai apenas dar-lhe um caminho para um jogo completo.
O que você quer é uma sequência de movimentos que lhe proporcionem o melhor estado de jogo concluído.
Então, o que você deve fazer é mudar um pouco o problema. Em vez de o tabuleiro de jogo ser o "estado", a sequência de movimentos se torna o "estado". (Ou seja: coloque os itens na fila nas posições "D2, A5, C7, B3, A3, ...")
Isso significa que realmente não nos importamos como esses estados são gerados. O próprio conselho é incidental, necessário apenas para avaliar a qualidade de um determinado estado.
Isso transforma o problema em um problema de otimização , que pode ser resolvido com um algoritmo de pesquisa local (que basicamente significa criar estados em torno de um determinado estado e selecionar o melhor estado, sem se preocupar com o caminho entre os estados).
O quebra-cabeça prototípico desse tipo é o quebra-cabeça das Oito Rainhas .
Nesta classe de problema, você está pesquisando no espaço de estados para encontrar uma boa solução, onde "bom" é avaliado por uma função objetivo (também chamada de função de avaliação ou, para algoritmos genéticos, uma função de adequação ).
Para o seu problema, uma função objetivo pode retornar um valor entre 0 e N, para o número de itens na fila que foram usados antes de atingir um estado de falha (em que N é o comprimento da fila). E, caso contrário, um valor de N + M, em que M é o número de espaços em branco deixados no quadro após a fila estar vazia. Como tal - quanto maior o valor, "objetivamente melhor" a solução.
(Vale a pena notar, neste ponto, que você deve otimizar a porcaria do código que executa o jogo - que transforma um estado em um quadro pronto que pode ser usado para a função objetivo).
Quanto aos exemplos de algoritmos de pesquisa local : O padrão básico é uma pesquisa de escalada que pega um determinado estado, o modifica e se move para o próximo estado que fornece um resultado melhor.
Obviamente, isso pode ficar preso nos máximos locais (e similares). Nesta forma, é chamada de pesquisa local gananciosa . Existem várias variações para lidar com esse e outros problemas (a Wikipedia abordou ). Alguns dos quais (por exemplo: busca por feixe local ) acompanham vários estados ao mesmo tempo.
Uma variação específica disso é o algoritmo genético ( Wikipedia ). As etapas básicas para um algoritmo genético são:
Parece que uma solução de algoritmo genético pode ser apropriada para o seu problema - com alguns ajustes. A maior dificuldade que vejo é que, com a representação das cordas acima, você descobrirá que mudar as metades traseiras de estados com metades frontais muito diferentes provavelmente resultará em estados "mortos" (devido a movimentos conflitantes entre as duas metades, esse resultado em uma baixa pontuação de condicionamento físico).
Talvez seja possível superar esse problema. Uma idéia que vem à mente é tornar mais provável que estados com metades da frente semelhantes se tornem pares reprodutores. Isso pode ser tão simples quanto classificar a população reprodutora dos estados, antes de emparelhá-los. Também pode ajudar a mover gradualmente a posição provável do cruzamento, do início ao fim da cadeia, à medida que o número de geração aumenta.
Também pode ser possível apresentar uma representação de movimentos dentro de um estado que é mais resistente (talvez até totalmente imune) a encontrar o estado de falha "quadrado está cheio". Talvez representando movimentos como coordenadas relativas do movimento anterior. Ou, com movimentos, selecione o espaço vazio mais próximo da posição especificada.
Como em todos os problemas de IA não triviais como esse, será necessário algum conserto significativo.
E, como mencionei antes, o outro grande desafio é simplesmente otimizar sua função objetivo. Tornar isso mais rápido permitirá pesquisar uma grande quantidade de espaço e buscar soluções para jogos com filas mais longas.
Para esta resposta, particularmente para acertar toda a terminologia, tive que desenterrar meu livro de IA da universidade, "Inteligência Artificial: Uma Abordagem Moderna", de Russell e Norvig. Não tenho certeza se é "bom" (não tenho outros textos de IA para compará-lo), mas não é ruim. Pelo menos é bem grande;)
fonte
Categorização
A resposta não é fácil. A teoria dos jogos tem algumas classificações para os jogos, mas parece não haver uma combinação clara de 1: 1 para esse jogo com uma teoria especial. É uma forma especial de problema combinatório.
Não é um vendedor ambulante, que decidiria um pedido no qual você visita "nós" com algum custo para alcançar o próximo nó a partir do último. Você não pode reordenar a fila nem precisa usar todos os campos no mapa.
A mochila não corresponde porque alguns campos ficam vazios ao colocar alguns itens na "mochila". Portanto, talvez seja uma forma estendida disso, mas o mais provável é que os algoritmos não sejam aplicáveis por causa disso.
A Wikipedia fornece algumas dicas sobre categorização aqui: http://en.wikipedia.org/wiki/Game_theory#Types_of_games
Eu o categorizaria como "problema de controle ideal em tempo discreto" ( http://en.wikipedia.org/wiki/Optimal_control ), mas não acho que isso o ajude.
Algoritmos
Caso você realmente conheça a fila completa, poderá aplicar algoritmos de pesquisa em árvore. Como você disse, a complexidade do problema cresce muito rapidamente com o comprimento da fila. Sugiro usar um algoritmo como "Pesquisa de profundidade (DFS)", que não requer muita memória. Como a pontuação não importa para você, você pode parar depois de encontrar a primeira solução. Para decidir qual sub-ramificação pesquisar primeiro, você deve aplicar uma heurística ao pedido. Isso significa que você deve escrever uma função de avaliação (por exemplo: número de campos vazios; quanto mais sofisticado esse for, melhor), que fornece uma pontuação para comparar qual passo seguinte é o mais promissor.
Você precisa apenas das seguintes partes:
Aqui está uma implementação de referência incompleta para a pesquisa profunda:
Este código não está verificado para funcionar, nem é compilável ou completo. Mas isso deve lhe dar uma idéia de como fazê-lo. O trabalho mais importante é a função de avaliação. Quanto mais sofisticado, as "tentativas" erradas o algoritmo tentará (e precisará desfazer) mais tarde. Isso reduz extremamente a complexidade.
Se isso for muito lento, você também pode tentar aplicar alguns métodos de jogos para duas pessoas como HashTables. Para isso, você terá que calcular uma chave de hash (iterativa) para cada estado do jogo que avaliar e marcar estados que não levam a uma solução. Por exemplo, toda vez que o método Search () retornar "null", uma entrada HashTable deve ser criada e, ao entrar em Search (), você verificaria se esse estado já foi alcançado até agora sem resultado positivo e, em caso afirmativo, retornará "null" sem Investigação aprofundada. Para isso, você precisará de uma enorme tabela de hash e terá que aceitar "colisões de hash", o que pode causar o provavelmente não encontrar uma solução existente, mas isso é muito improvável, se suas funções de hash forem boas o suficiente e sua tabela for grande o suficiente (é um risco de risco calculável).
Eu acho que não há outro algoritmo para resolver esse problema (conforme descrito por você) mais eficiente, assumindo que sua função de avaliação é ideal ...
fonte