Escrevi um jogo de jogo da velha em Java e meu método atual de determinar o final do jogo leva em consideração os seguintes cenários possíveis para o fim do jogo:
- O tabuleiro está cheio e nenhum vencedor ainda foi declarado: o jogo acabou.
- Cross venceu.
- O círculo venceu.
Infelizmente, para fazer isso, ele lê um conjunto predefinido desses cenários de uma tabela. Isso não é necessariamente ruim, considerando que há apenas 9 espaços em um tabuleiro e, portanto, a mesa é um pouco pequena, mas existe uma maneira algorítmica melhor de determinar se o jogo acabou? Determinar se alguém ganhou ou não é a essência do problema, pois verificar se 9 vagas estão cheias é trivial.
O método da mesa pode ser a solução, mas se não, qual é? Além disso, e se o tabuleiro não fosse do tamanho n=9
? E se fosse uma placa muito maior, digamos n=16
, n=25
e assim por diante, fazendo com que o número de itens consecutivamente colocados para ganhar a ser x=4
, x=5
, etc? Um algoritmo geral para ser usado por todos n = { 9, 16, 25, 36 ... }
?
fonte
3
). Assim, você pode acompanhar as contagens de cada um e só começar a verificar as vitórias se forem maiores.Respostas:
Você sabe que uma jogada vencedora só pode acontecer depois que X ou O fizerem sua jogada mais recente, então você só pode pesquisar linha / coluna com diagramas opcionais contidos nessa jogada para limitar seu espaço de pesquisa ao tentar determinar uma placa vencedora. Além disso, como há um número fixo de movimentos em um jogo de jogo da velha empatado, uma vez que o último lance é feito, se não for um lance vencedor, é por padrão um jogo empatado.
editar: este código é para um tabuleiro n por n com n em uma linha para vencer (tabuleiro 3x3 requer 3 em uma linha, etc)
editar: código adicionado para verificar o anti diag, não consegui descobrir uma maneira sem loop de determinar se o ponto estava no anti diag, então é por isso que essa etapa está faltando
fonte
você pode usar um quadrado mágico http://mathworld.wolfram.com/MagicSquare.html se qualquer linha, coluna ou diagrama somar 15, então um jogador ganhou.
fonte
Que tal este pseudocódigo:
Depois que um jogador coloca uma peça na posição (x, y):
Eu usaria um array de char [n, n], com O, X e espaço para vazio.
fonte
i==1
en==3
,rdiag
devem ser verificados em(1, 3)
e(1, 3-1+1)
é igual às coordenadas corretas, mas(1, 3-(1+1))
não.Isso é semelhante à resposta de Osama ALASSIRY , mas troca espaço constante e tempo linear por espaço linear e tempo constante. Ou seja, não há loop após a inicialização.
Inicialize um par
(0,0)
para cada linha, cada coluna e as duas diagonais (diagonal e anti-diagonal). Esses pares representam o acumulado(sum,sum)
das peças na linha, coluna ou diagonal correspondente, ondeQuando um jogador colocar uma peça, atualize o par de linhas, o par de colunas e os pares diagonais correspondentes (se estiverem nas diagonais). Se qualquer linha, coluna ou par diagonal recém-atualizado for igual a
(n,0)
ou(0,n)
, A ou B venceram, respectivamente.Análise assintótica:
Para o uso de memória, você usa
4*(n+1)
números inteiros.Exercício: Você consegue ver como testar um empate em O (1) vez por movimento? Nesse caso, você pode encerrar o jogo no início do empate.
fonte
O(sqrt(n))
hora, mas tem que ser feito após cada jogada, onde n é o tamanho do tabuleiro. Então você acaba comO(n^1.5)
. Para esta solução, você obtémO(n)
tempo total.Aqui está minha solução que escrevi para um projeto no qual estou trabalhando em javascript. Se você não se importa com o custo de memória de alguns arrays, é provavelmente a solução mais rápida e simples que você encontrará. Pressupõe que você conhece a posição do último movimento.
fonte
Acabei de escrever isso para minha aula de programação C.
Estou postando porque nenhum dos outros exemplos aqui funcionará com qualquer tamanho de grade retangular e qualquer número N -em-uma-linha de marcas consecutivas para vencer.
Você encontrará meu algoritmo, tal como é, na
checkWinner()
função. Ele não usa números mágicos nem nada sofisticado para verificar se há um vencedor, ele simplesmente usa quatro loops for - o código é bem comentado, então vou deixá-lo falar por si, acho.fonte
Se o tabuleiro for n × n, então haverá n linhas, n colunas e 2 diagonais. Verifique cada um para ver se há todos os Xs ou Os para encontrar um vencedor.
Se levar apenas x < n quadrados consecutivos para vencer, então é um pouco mais complicado. A solução mais óbvia é a de verificar cada x × x quadrado para um vencedor. Aqui está um código que demonstra isso.
(Eu realmente não testar esta tosse * *, mas tinha de compilação na primeira tentativa, yay mim!)
fonte
Não conheço Java muito bem, mas conheço C, então tentei a ideia do quadrado mágico do adk (junto com a restrição de pesquisa de Hardwareguy ).
Compila e testa bem.
Foi divertido, obrigado!
Na verdade, pensando bem, você não precisa de um quadrado mágico, apenas uma contagem para cada linha / coluna / diagonal. Isso é um pouco mais fácil do que generalizar um quadrado mágico para matrizes
n
×n
, já que você só precisa contar atén
.fonte
A mesma pergunta foi feita em uma de minhas entrevistas. Minhas idéias: Inicialize a matriz com 0. Mantenha 3 matrizes 1) sum_row (tamanho n) 2) sum_column (tamanho n) 3) diagonal (tamanho 2)
Para cada movimento por (X) diminua o valor da caixa em 1 e para cada movimento por (0) aumente-o em 1. Em qualquer ponto se a linha / coluna / diagonal que foi modificada no movimento atual tiver a soma -3 ou + 3 significa que alguém ganhou o jogo. Para um empate, podemos usar a abordagem acima para manter a variável moveCount.
Você acha que estou perdendo alguma coisa?
Editar: O mesmo pode ser usado para a matriz nxn. A soma deve ser igual a +3 ou -3.
fonte
uma forma não circular para determinar se o ponto estava no anti diag:
fonte
Estou atrasado para a festa, mas gostaria de apontar um benefício que descobri ao usar um quadrado mágico , ou seja, ele pode ser usado para obter uma referência ao quadrado que causaria a vitória ou derrota na próxima jogada, ao invés de apenas sendo usado para calcular quando um jogo acabou.
Pegue este quadrado mágico:
Primeiro, configure uma
scores
matriz que é incrementada toda vez que um movimento é feito. Veja esta resposta para detalhes. Agora, se jogarmos ilegalmente X duas vezes seguidas em [0,0] e [0,1], ascores
matriz terá a seguinte aparência:E o quadro é assim:
Então, tudo o que temos que fazer para obter uma referência de qual quadrado vencer / bloquear é:
Na realidade, a implementação requer alguns truques adicionais, como manipular teclas numeradas (em JavaScript), mas achei muito simples e gostei da matemática recreativa.
fonte
Fiz algumas otimizações nas verificações de linha, coluna e diagonal. É decidido principalmente no primeiro loop aninhado se precisamos verificar uma determinada coluna ou diagonal. Assim, evitamos checar colunas ou diagonais economizando tempo. Isso causa grande impacto quando o tamanho do cartão é maior e um número significativo de células não é preenchido.
Aqui está o código Java para isso.
fonte
Eu gosto desse algoritmo porque ele usa uma representação do tabuleiro 1x9 vs 3x3.
fonte
Outra opção: gere sua tabela com código. Até a simetria, existem apenas três maneiras de vencer: linha da borda, linha do meio ou diagonal. Pegue esses três e gire-os de todas as maneiras possíveis:
Essas simetrias podem ter mais utilidades em seu código de jogo: se você chegar a um tabuleiro do qual já viu uma versão girada, pode apenas pegar o valor em cache ou o melhor movimento em cache daquele (e removê-lo de volta). Isso geralmente é muito mais rápido do que avaliar a subárvore do jogo.
(Inverter para a esquerda e para a direita pode ajudar da mesma forma; não foi necessário aqui porque o conjunto de rotações dos padrões vencedores é simétrico em espelho.)
fonte
Aqui está uma solução que eu vim, isso armazena os símbolos como chars e usa o valor int do char para descobrir se X ou O ganhou (veja o código do Árbitro)
Também aqui estão meus testes de unidade para validar se realmente funciona
Solução completa: https://github.com/nashjain/tictactoe/tree/master/java
fonte
Que tal uma abordagem a seguir para 9 slots? Declare 9 variáveis inteiras para uma matriz 3x3 (a1, a2 .... a9) onde a1, a2, a3 representam a linha-1 e a1, a4, a7 formariam a coluna-1 (você entendeu). Use '1' para indicar o jogador-1 e '2' para indicar o jogador-2.
Existem 8 combinações de vitória possíveis: Vitória-1: a1 + a2 + a3 (a resposta pode ser 3 ou 6 com base no jogador que ganhou) Vitória-2: a4 + a5 + a6 Vitória-3: a7 + a8 + a9 Vitória-4 : a1 + a4 + a7 .... Win-7: a1 + a5 + a9 Win-8: a3 + a5 + a7
Agora sabemos que se o jogador um cruza a1, então precisamos reavaliar a soma das 3 variáveis: Vitória-1, Vitória-4 e Vitória-7. Qualquer 'Win-?' a variável chega a 3 ou 6 primeiros ganha o jogo. Se a variável Win-1 atingir 6 primeiro, o Jogador 2 vence.
Eu entendo que esta solução não é escalonável facilmente.
fonte
Esta é uma forma muito simples de verificar.
}
fonte
Se você tiver o campo de fronteira 5 * 5, por exemplo, usei o próximo método de verificação:
Acho que é mais claro, mas provavelmente não é a maneira mais ideal.
fonte
Aqui está minha solução usando uma matriz bidimensional:
fonte
Solução de tempo constante, roda em O (8).
Armazene o estado da placa como um número binário. O menor bit (2 ^ 0) é a linha superior esquerda do tabuleiro. Então vai para a direita, depois para baixo.
IE
Cada jogador tem seu próprio número binário para representar o estado (porque o jogo da velha) tem 3 estados (X, O e branco), portanto, um único número binário não funcionará para representar o estado do tabuleiro para vários jogadores.
Por exemplo, uma placa como:
Observe que os bits para o jogador X são separados dos bits para o jogador O, isso é óbvio porque X não pode colocar uma peça onde O tem uma peça e vice-versa.
Para verificar se um jogador ganhou, precisamos comparar todas as posições cobertas por aquele jogador com uma posição que sabemos ser uma posição de vitória. Nesse caso, a maneira mais fácil de fazer isso seria alternar com AND a posição do jogador e a posição de vitória e ver se as duas são iguais.
por exemplo.
Observação:
X & W = W
então X está em um estado de vitória.Esta é uma solução de tempo constante, depende apenas do número de posições ganhadoras, porque a aplicação da porta AND é uma operação de tempo constante e o número de posições ganhadoras é finito.
Também simplifica a tarefa de enumerar todos os estados de placa válidos, apenas todos os números representáveis por 9 bits. Mas é claro que você precisa de uma condição extra para garantir que um número seja um estado de placa válido (por exemplo,
0b111111111
é um número de 9 bits válido, mas não é um estado de placa válido porque X acabou de dar todas as voltas).O número de posições de vitória possíveis pode ser gerado instantaneamente, mas aqui estão eles de qualquer maneira.
Para enumerar todas as posições do tabuleiro, você pode executar o seguinte loop. Embora eu deixe a decisão de determinar se um número é um estado válido da placa para outra pessoa.
NOTA: (2 ** 9 - 1) = (2 ** 8) + (2 ** 7) + (2 ** 6) + ... (2 ** 1) + (2 ** 0)
fonte
Não tenho certeza se esta abordagem já foi publicada. Isso deve funcionar para qualquer tabuleiro m * n e um jogador deve preencher a posição consecutiva do " vencedorPos ". A ideia é baseada na janela em execução.
fonte
Desenvolvi um algoritmo para isso como parte de um projeto de ciências uma vez.
Basicamente, você divide o tabuleiro de forma recursiva em um grupo de retalhos 2x2 sobrepostos, testando as diferentes combinações possíveis para vencer em um quadrado 2x2.
É lento, mas tem a vantagem de funcionar em placas de qualquer tamanho, com requisitos de memória bastante lineares.
Eu gostaria de poder encontrar minha implementação
fonte