Minimax para Bomberman

11

Estou desenvolvendo o clone do jogo Bomberman e estou experimentando diferentes tipos de IA. Primeiro, usei a pesquisa no espaço de estados com A * e agora quero tentar uma abordagem diferente com o algoritmo Minimax. Meu problema é que todos os artigos que encontrei no minimax supõem jogadores alternados. Mas no Bomberman, todo jogador faz alguma ação ao mesmo tempo. Eu acho que eu poderia gerar todos os estados possíveis para um jogo, mas com quatro jogadores e 5 ações básicas (4 movimentos e local da bomba), ele fornece 5 ^ 4 estados no primeiro nível da árvore do jogo. Esse valor aumentará exponencialmente a cada próximo nível. Estou esquecendo de algo? Existem maneiras de implementá-lo ou devo usar um algoritmo totalmente diferente? Obrigado por todas as sugestões

Billda
fonte
1
Embora esse tópico seja um pouco estranho, uma coisa que gosto de fazer com a IA é usar objetivos ou personalidades para a IA. Podem ser coisas como acumular power-ups, não-agressivas, buscar vingança, pressa etc. Com objetivos como esse, você pode dizer em que direção deve se mover e soltar uma bomba se avançar seu progresso em direção ao objetivo (se é razoavelmente perto de um jogador que você está caçando ou de um bloco que deseja destruir).
Benjamin Perigo Johnson
2
Sim, você está perdendo algumas coisas, mas não vai me agradecer por indicá-las porque elas pioram. Não há 5 ações básicas. Alguns quadrados têm 5 "movimentos" (4 direções e ficam imóveis); outros têm 3 (porque estão bloqueados em duas direções); em média, é 4. Mas você pode soltar uma bomba enquanto corre , portanto, em média, o fator de ramificação é 8. E alguém com um power-up de alta velocidade pode fazer mais movimentos, aumentando efetivamente o fator de ramificação.
Peter Taylor
Dei-lhe a resposta na sua pergunta usando a pesquisa de árvores de Monte Carlo.
SDwarfs
Minimax simplesmente não é útil em uma situação com tantas opções quanto Bomberman. Você esgotará sua capacidade de pesquisar antes de ir longe o suficiente para ver se um movimento é sensato ou não.
Loren Pechtel

Respostas:

8

Jogos de estratégia em tempo real como o homem-bomba têm dificuldades com a IA. Você quer que seja inteligente, mas ao mesmo tempo não pode ser perfeito.

Se a IA for perfeita, seus jogadores ficarão frustrados. Ou porque sempre perdem ou você recebe 0,3 quadros por segundo.

Se não for inteligente o suficiente, seus jogadores ficarão entediados.

Minha recomendação é ter duas funções de IA, uma que determina para onde a IA vai, a outra que determina quando é melhor lançar uma bomba. Você pode usar coisas como previsão de movimento para determinar se um inimigo está se movendo em direção a um ponto que será perigoso se uma bomba cair no local atual.

Dependendo da dificuldade, você pode modificar essas funções para melhorar ou diminuir a dificuldade.

UnderscoreZero
fonte
2
Tempo, frustração e tédio não são problema. Estou escrevendo uma tese de bacharel sobre diferentes abordagens de IA no Bomberman e comparando-as. Então, se é perfeito, é melhor. Estou preso com essa minimax agora
Billda
1
O problema que você encontrará no algoritmo minimax é o tempo de processamento. Você precisará acompanhar todas as ações inimigas e determinar seu estilo de jogo e seu estilo de contra-jogo. Parece que você já está ciente disso, mas isso pode ser uma tarefa bastante assustadora para um jogo em tempo real sem diminuir a velocidade do jogo. Em vez de construir uma árvore de brincadeiras, você precisará determinar suas ações em tempo real, talvez criar um algoritmo de aprendizado de máquina que melhore quanto mais ela toca?
precisa
4

Como você notou, Bomberman é muito complexo para ser simulado como um jogo baseado em turnos. Extrapolar qualquer decisão própria possível mais todas as decisões possíveis de qualquer outro jogador simplesmente não funciona.

Em vez disso, você deve usar uma abordagem mais estratégica.

Você deve se perguntar: como um jogador humano toma decisões enquanto joga homem-bomba? Normalmente, um jogador deve seguir quatro prioridades básicas:

  1. evitar áreas de explosão de bombas
  2. colocar bombas para que outros não possam evitar suas áreas de explosão
  3. coletar upgrades
  4. coloque bombas para explodir rochas

A primeira prioridade pode ser cumprida criando um "mapa de perigo". Quando uma bomba é colocada, todas as peças cobertas por ela devem ser marcadas como "perigosas". Quanto mais cedo a bomba explodir (lembre-se das reações em cadeia!), Maior será o nível de perigo. Sempre que a IA perceber que está em um campo com alto perigo, ela deve se afastar. Quando traçar um caminho (por qualquer motivo), campos com alto nível de perigo devem ser evitados (podem ser implementados adicionando artificialmente um custo maior ao caminho).

O cálculo do mapa de perigo pode ser aprimorado ainda mais para proteger a IA de decisões estúpidas (como entrar em áreas difíceis de escapar quando outro jogador está próximo).

Isso já deve criar uma IA defensiva razoável. Então, o que dizer de ofensa?

Quando a IA perceber que está razoavelmente segura no momento, deve planejar manobras ofensivas: deve considerar como pode aumentar o mapa de perigo em torno dos outros jogadores, colocando as próprias bombas. Ao escolher um local para plantar uma bomba, ele deve preferir locais próximos para não precisar se mover tão longe. Também deve desconsiderar os locais das bombas quando o mapa de perigo resultante não permitir uma rota de fuga razoável.

Philipp
fonte
Minha experiência limitada em jogar é que você geralmente precisa colocar várias bombas para matar um oponente competente - uma estratégia precisa levar isso em consideração. Eu joguei contra IAs com aproximadamente sua estratégia, eles são bastante ineficazes em matá-lo, a menos que você possa ser encurralado.
Loren Pechtel
4

Eu acho que eu poderia gerar todos os estados possíveis para um jogo, mas com quatro jogadores e 5 ações básicas (4 movimentos e local da bomba), ele fornece 5 ^ 4 estados no primeiro nível da árvore do jogo.

Corrigir! Você precisa pesquisar todas as ações de 5 ^ 4 (ou até 6 ^ 4, pois você pode caminhar em 4 direções, parar e "colocar uma bomba"?) Para cada ação do jogo. MAS, quando um jogador já decidiu se mudar, leva algum tempo até que a jogada seja executada (por exemplo, 10 ticks do jogo). Durante esse período, o número de possibilidades diminui.

Esse valor aumentará exponencialmente a cada próximo nível. Estou esquecendo de algo? Existem maneiras de implementá-lo ou devo usar um algoritmo totalmente diferente?

Você pode usar uma tabela de hash para calcular apenas o mesmo estado de jogo "subárvore" uma vez. Imagine que o jogador A anda para cima e para baixo, enquanto todos os outros jogadores "esperam", você acaba no mesmo estado do jogo. É o mesmo que para "esquerda-direita" ou "direita-esquerda". Também mover "para cima, então para a esquerda" e "para a esquerda e para cima" resulta no mesmo estado. Usando uma tabela de hash, você pode "reutilizar" a pontuação calculada para um estado de jogo que já foi avaliado. Isso reduz bastante a velocidade de crescimento. Matematicamente, reduz a base da sua função de crescimento exponencial. Para ter uma idéia de quanto isso reduz a complexidade, vamos analisar os movimentos possíveis para apenas um jogador em comparação com as posições alcançáveis ​​no mapa (= diferentes estados do jogo) se o jogador puder apenas mover para cima / baixo / esquerda / direita / parada .

profundidade 1: 5 movimentos, 5 estados diferentes, 5 estados adicionais para esta recursão

profundidade 2: 25 movimentos, 13 estados diferentes, 8 estados adicionais para esta recursão

profundidade 3: 6125 movimentos, 25 estados diferentes, 12 estados adicionais para esta recursão

Para visualizar isso, responda a si mesmo: quais campos no mapa podem ser alcançados com um movimento, dois movimentos, três movimentos. A resposta é: Todos os campos com uma distância máxima = 1, 2 ou 3 da posição inicial.

Ao usar uma HashTable, você só precisa avaliar cada estado do jogo acessível (no nosso exemplo 25, na profundidade 3) uma vez. Considerando que, sem uma HashTable, é necessário avaliá-las várias vezes, o que significaria 6125 avaliações em vez de 25 no nível de profundidade 3. O melhor: depois de calcular uma entrada da HashTable, você pode reutilizá-la em etapas posteriores ...

Você também pode usar subárvores de "corte" de aprofundamento incremental e poda alfa-beta que não valem a pena pesquisar em mais profundidade. No xadrez, isso reduz o número de nós pesquisados ​​para cerca de 1%. Uma breve introdução à poda alfa-beta pode ser encontrada em vídeo aqui: http://www.teachingtree.co/cs/watch?concept_name=Alpha-beta+Pruning

Um bom começo para mais estudos é http://chessprogramming.wikispaces.com/Search . A página está relacionada ao xadrez, mas os algoritmos de pesquisa e otimização são os mesmos.

Outro (mas complexo) algoritmo de IA - que seria mais adequado ao jogo - é o "Aprendizado de diferença temporal".

Saudações

Stefan

PS: Se você reduzir o número possível de estados de jogo (por exemplo, tamanho muito pequeno do mapa, apenas uma bomba por jogador, nada mais), há uma chance de pré-calcular uma avaliação para todos os estados de jogo.

--editar--

Você também pode usar os resultados calculados offline dos cálculos de minimax para treinar uma rede neuronal. Ou você pode usá-los para avaliar / comparar estratégias implementadas manualmente. Por exemplo, você pode implementar algumas das "personalidades" sugeridas e algumas heurísticas que detectam, em quais situações a estratégia é boa. Portanto, você deve "classificar" situações (por exemplo, estados do jogo). Isso também pode ser tratado por uma rede neuronal: Treine uma rede neuronal para prever qual das estratégias codificadas manualmente está desempenhando melhor na situação atual e execute-a. Isso deve produzir extremamente boas decisões em tempo real para um jogo real. Muito melhor do que uma pesquisa com limite de profundidade baixo que pode ser alcançada de outra forma, pois não importa quanto tempo demoram os cálculos offline (eles são antes do jogo).

- editar # 2 -

Se você apenas recalcular seus melhores movimentos a cada 1 segundo, também poderá tentar fazer um planejamento de nível mais alto. O que quero dizer com isso? Você sabe quantos movimentos você pode fazer em 1 segundo. Assim, você pode fazer uma lista de posições alcançáveis ​​(por exemplo, se isso for 3 movimentos em 1 segundo, você terá 25 posições alcançáveis). Então você pode planejar como: vá para a "posição xe coloque uma bomba". Como alguns outros sugeriram, você pode criar um mapa de "perigo", que é usado para o algoritmo de roteamento (como ir para a posição x? Qual caminho deve ser preferido [existem algumas variações possíveis na maioria dos casos]). Isso consome menos memória em comparação com uma enorme HashTable, mas produz resultados menos ideais. Porém, como usa menos memória, pode ser mais rápido devido aos efeitos de armazenamento em cache (melhor uso dos caches de memória L1 / L2).

ADICIONALMENTE: Você pode fazer pesquisas prévias que contêm apenas movimentos para um jogador cada para classificar as variações que resultam em perda. Portanto, tire todos os outros jogadores do jogo ... Armazene quais combinações cada jogador pode escolher sem perder. Se houver apenas movimentos perdidos, procure as combinações de movimentos em que o jogador permanece vivo por mais tempo. Para armazenar / processar esse tipo de estrutura de árvore, você deve usar uma matriz com indicadores de índice como este:

class Gamestate {
  int value;
  int bestmove;
  int moves[5];
};

#define MAX 1000000
Gamestate[MAX] tree;

int rootindex = 0;
int nextfree = 1;

Cada estado possui um "valor" de avaliação e vincula-se aos próximos Gamestates ao se mover (0 = parar, 1 = para cima, 2 = para a direita, 3 = para baixo, 4 = para a esquerda) armazenando o índice da matriz dentro de "árvore" em movimentos [0 ] para mover [4]. Para construir sua árvore recursivamente, isso pode ser assim:

const int dx[5] = { 0,  0, 1, 0, -1 };
const int dy[5] = { 0, -1, 0, 1,  0 };

int search(int x, int y, int current_state, int depth_left) {
  // TODO: simulate bombs here...
  if (died) return RESULT_DEAD;

  if (depth_left == 0) {
    return estimate_result();
  }

  int bestresult = RESULT_DEAD;

  for(int m=0; m<5; ++m) {
    int nx = x + dx[m];
    int ny = y + dy[m];
    if (m == 0 || is_map_free(nx,ny)) {
      int newstateindex = nextfree;
      tree[current_state].move[m] = newstateindex ;
      ++nextfree;

      if (newstateindex >= MAX) { 
        // ERROR-MESSAGE!!!
      }

      do_move(m, &undodata);
      int result = search(nx, ny, newstateindex, depth_left-1);
      undo_move(undodata);

      if (result == RESULT_DEAD) {
        tree[current_state].move[m] = -1; // cut subtree...
      }

      if (result > bestresult) {
        bestresult = result;
        tree[current_state].bestmove = m;
      }
    }
  }

  return bestresult;
}

Esse tipo de estrutura de árvore é muito mais rápido, pois a alocação dinâmica de memória é realmente muito lenta! Mas, armazenar a árvore de pesquisa também é bastante lento ... Isso é mais uma inspiração.

SDwarfs
fonte
0

Ajudaria a imaginar que todos se revezam?

Tecnicamente, no sistema subjacente, eles realmente funcionam, mas, como as coisas são intercaladas e sobrepostas, elas parecem estar funcionando simultaneamente.

Lembre-se também de que você não precisa executar a IA após cada quadro de animação. Muitos jogos casuais bem-sucedidos executam o algoritmo da IA ​​apenas uma vez a cada segundo, fornecendo aos personagens controlados pela IA informações sobre onde eles devem ir ou o que devem fazer; essas informações são usadas para controlar os personagens da AI nos outros quadros.

Raceimaztion
fonte
Eu não estou calculando AI todos os quadros de animação, mas cada segundo. A cada segundo, meu ambiente coleta ações de todos os jogadores e envia a eles um novo estado atualizado.
Billda