Como posso saber se meu jogo de quebra-cabeça é sempre possível?

68

Eu fiz uma espécie de jogo de quebra-cabeça em que o objetivo é se livrar de todos os azulejos brancos. Você pode tentar no final da pergunta.

Cada vez, o tabuleiro é gerado aleatoriamente com ladrilhos brancos em lugares aleatórios em uma grade 5 * 5. Você pode clicar em qualquer ladrilho nessa grade e ele alternará a cor e todos os ladrilhos que estiverem tocando nas laterais. Meu dilema é o fato de não saber se isso irá gerar um quadro impossível. Qual é a melhor maneira de verificar coisas assim?

function newgame() {
 moves = 0;
    document.getElementById("moves").innerHTML = "Moves: "+moves;

  for (var i = 0; i < 25; i++) {
   if (Math.random() >= 0.5) {
$(document.getElementsByClassName('block')[i]).toggleClass("b1 b2")
   }
}
}
newgame();
function toggle(a,b) {  
  moves += 1;
  document.getElementById("moves").innerHTML = "Moves: "+moves;
$(document.getElementsByClassName('block')[a+(b*5)]).toggleClass("b1 b2");

if (a<4) {$(document.getElementsByClassName('block')[(a+1)+(b*5)]).toggleClass("b1 b2")}
  
  
if (a>0) {$(document.getElementsByClassName('block')[(a-1)+(b*5)]).toggleClass("b1 b2")}
  
  
if (b<4) {$(document.getElementsByClassName('block')[a+((b+1)*5)]).toggleClass("b1 b2")}
  
if (b>0) {$(document.getElementsByClassName('block')[a+((b-1)*5)]).toggleClass("b1 b2")}
}
body {
  background-color: #000000;
}

.game {
  float: left;
  background-color: #000000;
  width: 300px;
  height: 300px;
  overflow: hidden;
  overflow-x: hidden;
  user-select: none;
  display: inline-block;
}

.container {
  border-color: #ffffff;
  border-width: 5px;
  border-style: solid;
  border-radius: 5px;
  width: 600px;
  height: 300px;
  text-align: center;
}

.side {
  float: left;
  background-color: #000000;
  width: 300px;
  height: 300px;
  overflow: hidden;
  overflow-x: hidden;
  user-select: none;
  display: inline-block;
}

.block {
  transition: background-color 0.2s;
  float: left;
}

.b1:hover {
  background-color: #444444;
  cursor: pointer;
}

.b2:hover {
  background-color: #bbbbbb;
  cursor: pointer;
}

.row {
  width: 300px;
  overflow: auto;
  overflow-x: hidden;
}

.b1 {
  display: inline-block;
  height: 50px;
  width: 50px;
  background-color: #000000;
  border-color: #000000;
  border-width: 5px;
  border-style: solid;
}




.b2 {
  display: inline-block;
  height: 50px;
  width: 50px;
  background-color: #ffffff;
  border-color: #000000;
  border-width: 5px;
  border-style: solid;
}



.title {
  width: 200px;
  height: 50px;
  color: #ffffff;
  font-size: 55px;
  font-weight: bold;
  font-family: Arial;
  display: table-cell;
  vertical-align: middle;
}

.button {
  cursor: pointer;
  width: 200px;
  height: 50px;
  background-color: #000000;
  border-color: #ffffff;
  border-style: solid;
  border-width: 5px;
  color: #ffffff;
  font-size: 25px;
  font-weight: bold;
  font-family: Arial;
  display: table-cell;
  vertical-align: middle;
  border-radius: 5px;
  transition: background-color 0.3s, color 0.3s;
}

.button:hover {
  background-color: #ffffff;
  color: #000000;
}

.sidetable {
  padding: 30px 0px;
  height: 200px;
}


#moves {
  width: 200px;
  height: 50px;
  color: #aaaaaa;
  font-size: 30px;
  font-weight: bold;
  font-family: Arial;
  display: table-cell;
  vertical-align: middle;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<center>
  <div class="container">
  
  
  <div class="game"><div class="row"><div onclick="toggle(0,0);" class="block b1"></div><div onclick="toggle(1,0);" class="block b1"></div><div onclick="toggle(2,0);" class="block b1"></div><div onclick="toggle(3,0);" class="block b1"></div><div onclick="toggle(4,0);" class="block b1"></div></div><div class="row"><div onclick="toggle(0,1);" class="block b1"></div><div onclick="toggle(1,1);" class="block b1"></div><div onclick="toggle(2,1);" class="block b1"></div><div onclick="toggle(3,1);" class="block b1"></div><div onclick="toggle(4,1);" class="block b1"></div></div><div class="row"><div onclick="toggle(0,2);" class="block b1"></div><div onclick="toggle(1,2);" class="block b1"></div><div onclick="toggle(2,2);" class="block b1"></div><div onclick="toggle(3,2);" class="block b1"></div><div onclick="toggle(4,2);" class="block b1"></div></div><div class="row"><div onclick="toggle(0,3);" class="block b1"></div><div onclick="toggle(1,3);" class="block b1"></div><div onclick="toggle(2,3);" class="block b1"></div><div onclick="toggle(3,3);" class="block b1"></div><div onclick="toggle(4,3);" class="block b1"></div></div><div class="row"><div onclick="toggle(0,4);" class="block b1"></div><div onclick="toggle(1,4);" class="block b1"></div><div onclick="toggle(2,4);" class="block b1"></div><div onclick="toggle(3,4);" class="block b1"></div><div onclick="toggle(4,4);" class="block b1"></div></div></div>
    
    <div class="side">
      <center class="sidetable">
        <div class="title">Tiles</div>
        <br>
        <div class="button" onclick="newgame()">New Game</div>
        <br><br>
        <div id="moves">Moves: 0</div>
      </center>
    </div>
    
  </div>
    </center>

Qwerty
fonte
9
Se você está interessado nesse tipo de jogo, dê uma olhada na Portable Puzzle Collection de Simon Tatham . Além desse tipo (chamado Flip there), você pode encontrar variantes de muitos quebra-cabeças japoneses e outros. Tudo está sob licença BSD e provavelmente uma leitura interessante.
Dubu
10
Que tal engenharia reversa? Comece com um quadro em branco e depois automatize, digamos 20 cliques em quadrados aleatórios. Dessa forma, você sabe que deve haver uma solução no final.
precisa saber é o seguinte
3
Eu quero continuar jogando, mas devido à sua pergunta, a incerteza de saber se eu vou ou não ganhar está me consumindo! Fun game :)
MrDuk
@MrDuk codepen.io/qwertyquerty/pen/WMGwVW aqui está o projeto finalizado! Este é fixo e polido. Eu também fiz um aplicativo de elétrons.
Qwerty 09/02
@ Qwerty, quando tentei exibir sua caneta na exibição de página inteira, recebi a mensagem "O proprietário dessa caneta precisa verificar o endereço de e-mail para ativar a exibição de página inteira". Por favor, verifique seu endereço de e-mail no CodePen para que eu possa desfrutar do seu jogo em toda a janela! :)
stephenwade

Respostas:

161

Este é o tipo de jogo em que o mesmo movimento realizado duas vezes reverte o tabuleiro para o estado anterior. Portanto, para garantir que um tabuleiro seja solucionável, gere-o jogando ao contrário. Comece com um quadro (em branco) resolvido e inicie programaticamente "clicando" aleatoriamente um certo número de vezes ou até que o quadro tenha o número desejado de quadrados brancos. Uma solução é simplesmente executar os mesmos movimentos na ordem inversa. Outras soluções mais curtas podem existir, mas você tem a garantia de ter pelo menos uma.

Outra solução muito mais complexa é definir um algoritmo de solução que passe por todos os estados de jogo possíveis a partir da sua posição inicial, para tentar encontrar a solução. Isso levaria muito mais tempo para implementar e executar, mas permitiria que as placas fossem verdadeiramente geradas aleatoriamente. Não vou entrar em detalhes dessa solução, porque não é uma idéia tão boa.

Ed Marty
fonte
22
@ Qwerty: para o seu problema específico, clicar no mesmo quadrado duas vezes se cancela, para que nunca haja motivo para clicar em um quadrado mais de uma vez. Você pode escolher um determinado número de quadrados para clicar sem repetir, ou considerar uma solução que atribua uma chance de clique de XX% a cada quadrado no quadro. (Ed: resposta de Nice, +1)
Jeff Bowman
3
Eu fiz quase exatamente o mesmo jogo antes e acabei usando essa abordagem. Incluí uma animação no início, mostrando o estado resolvido indo rapidamente para o estado não resolvido; foi bonito.
Jared Goguen
11
@JaredGoguen estranho, eu adicionei isso e voltei aqui para ver o seu comentário.
21418 Qwerty
4
@JeffBowman De fato, o conjunto de jogos solucionáveis ​​pode ser tratado como um valor de 25 bits, com cada bit correspondendo a um quadrado que é o número de vezes que foi invertido, mod 2. Assim, é possível gerar um número aleatório no intervalo 0. .33,554,432 e, em seguida, calcule o valor de cada quadrado no quadro em pouco tempo.
Monty mais duro
7
Pelo que vale a pena, embora essa seja a resposta correta para a questão matemática de como responder a esse problema, essa geralmente é uma prática dúbia do ponto de vista do design. Esse tipo de geração, sem nenhum plano específico, geralmente leva a quebra-cabeças que parecem muito 'iguais', sem pontos de interesse específicos ou temas unificadores. É possível 'gerar processualmente' instâncias de problemas interessantes para um jogo de quebra-cabeça, mas geralmente requer uma análise muito mais detalhada das características interessantes dos seus quebra-cabeças.
Steven Stadnicki
92

Embora as respostas acima sejam inteligentes (e provavelmente como eu faria de qualquer maneira), esse jogo em particular é muito conhecido. Chama-se Lights Out e foi matematicamente resolvido. Existe uma solução se e somente se duas somas de vários elementos (fornecidas na página da wikipedia) adicionarem a zero mod 2 (ou seja, um número par). Em geral, um pouco de álgebra linear deve fornecer condições de solução semelhantes para jogos em qualquer tabuleiro.

Robert Mastragostino
fonte
2
É meio triste descobrir que já foi feito. Eu pensei que estava pensando em alguma coisa.
Qwerty
39
@ Qwerty, existem muito poucas idéias originais, e você certamente não precisa ter uma para ter sucesso (cf. Rovio, King).
Pare de prejudicar Monica
14
Este jogo em particular existe, mas você sempre pode expandir a ideia! Adicione mais recursos! Adicione regras diferentes para o que acontece quando você clica em algum lugar, como cores que se juntam com base em qual direção foi ativada / desativada. Adicione diferentes "ferramentas" que você deve usar. Adicione placas não retangulares! Muitas coisas divertidas para fazer. Lembre-se de que um movimento deve sempre se reverter.
Ed Marty
7
@OrangeDog: Até 'Lights Out' não era original, é apenas o nome da marca que ficou popular nos anos 90. O artigo da wikipedia, por exemplo, lista isso e isto
BlueRaja - Danny Pflughoeft
11
A quais respostas você está se referindo como "as respostas acima"? Não é totalmente claro, pois na minha tela há apenas uma resposta acima da sua. Lembre-se de que as respostas mudam de ordem, dependendo dos votos e das opções do usuário. Você sempre deve vincular a respostas específicas em vez de se referir a algo "acima".
David Richerby
13

Faça o contrário ao gerar seu quebra-cabeça.

Em vez de selecionar os ladrilhos aleatoriamente e transformá-los de branco em preto, comece com uma lousa em branco e selecione os ladrilhos, mas em vez de transformar esse ladrilho em preto, faça como se o usuário o selecionasse, resultando na inversão de todos os outros ladrilhos em torno dele.

Dessa forma, você terá a garantia de ter pelo menos uma solução: o usuário precisará desfazer o que o jogador "AI" fez para criar o nível.

Vaillancourt
fonte
7

Ed e Alexandre têm o direito disso.

Mas se você não quer saber se cada solução é possível, existem maneiras.

Há um número finito de quebra-cabeças possíveis

Clicar duas vezes no mesmo quadrado produz o mesmo resultado que não clicar nele, independentemente de quantos cliques foram feitos entre eles. Isso significa que toda solução pode ser descrita, atribuindo a cada quadrado um valor binário de 'clicado' ou 'não clicado'. Da mesma forma, cada quebra-cabeça pode ser descrito atribuindo a cada quadrado um valor binário de 'alternado' ou 'não alternado'. Isso significa que existem 2 ^ 25 quebra-cabeças possíveis e 2 ^ 25 soluções possíveis. Se você pode provar que cada solução resolve um quebra-cabeça único, deve haver uma solução para cada quebra-cabeça. Da mesma forma, se você encontrar duas soluções que resolvem o mesmo quebra-cabeça, não poderá haver uma solução para cada quebra-cabeça.

Além disso, 2 ^ 25 é 33.554.432. Isso é bastante, mas não é um número incontrolável. Um bom algoritmo e um computador decente provavelmente poderiam ter uma força bruta em algumas horas, especialmente quando você considera que metade dos quebra-cabeças é inversa da outra metade.

Lúpus Arcanista
fonte
4
Mais da metade é "inversa" - além das reflexões horizontais, você tem reflexões e rotações verticais.
Clockwork-Muse
@ Clockwork-Muse, sim, mas é mais difícil calcular um número exato, porque, embora os projetos assimétricos possam ser girados e invertidos em 8 permutações, os projetos simétricos têm menos permutações. Então, eu mencionei apenas a inversão de branco / preto, já que toda solução tem exatamente 1 inverso. (Embora para esse inversa para o trabalho, você tem que provar que você pode virar a placa inteira)
Arcanist Lupus
Acontece que, como Robert Mastragostino mencionou em sua resposta, esse é realmente um problema bem conhecido e bem estudado. Cada quebra-cabeça solucionável tem exatamente 4 soluções, e a maioria das placas aleatórias não é solucionável. Pesquisando todo esse espaço pode ser divertido, mas como já existe uma prova ( math.ksu.edu/math551/math551a.f06/lights_out.pdf ), você também pode criar alguns produtos de ponto e ter a mesma resposta em alguns microssegundos. :)
GrandOpener
Tempo de matemática: se você deseja calcular o número de placas distintas (independentemente da resolubilidade), levando em consideração todas as simetrias, o lema de Burnside é o caminho a seguir: existem 16 simetrias (uma trivial, três rotações, quatro reflexões e depois cada uma dessas 8 combinadas com inversão de ligar / desligar) e para cada uma dessas simetrias algum número de placas permanece totalmente inalterado. Se você tomar a média de placas totalmente inalteradas por simetria, será igual ao número de placas distintas.
187 Arthur Arthur
11
@ PeterTaylor Definitivamente levará muito mais tempo para codificar o simulador do que para executar os resultados.
CorsiKa
4

Resposta generalizada:

  1. Crie uma matriz de tamanho (# movimentos) x (# luzes).
  2. Coloque um 1 em uma célula se, ao fazer o movimento correspondente a essa linha, alternar a luz correspondente a essa coluna, 0 caso contrário.
  3. Realize a eliminação de Gauss-Jordan (módulo 2) na matriz.
  4. Se a matriz resultante tiver um único 1 em cada coluna e cada linha tiver no máximo um único 1, todas as grades serão solucionáveis.
Mark Tilford
fonte
1

Outros já mencionaram maneiras de descobrir se o quebra-cabeça gerado aleatoriamente é solucionável. a pergunta que você também deve estar perguntando é se você realmente deseja quebra-cabeças gerados aleatoriamente.

Todos os quebra-cabeças gerados aleatoriamente têm a mesma falha principal: a dificuldade deles é praticamente imprevisível. Os possíveis quebra-cabeças que você pode encontrar podem variar de já resolvidos, a triviais (a solução é óbvia) a difíceis (a solução não é óbvia) a impossíveis (o quebra-cabeça não é solucionável). Como a dificuldade é imprevisível, cria uma experiência insatisfatória para o jogador, especialmente se ele fizer vários quebra-cabeças seguidos. É altamente improvável que eles obtenham uma curva de dificuldade suave, o que pode deixá-los entediados ou frustrados, dependendo dos quebra-cabeças que recebem.

Outro problema da geração aleatória é que o tempo que leva para inicializar o quebra-cabeça é imprevisível. De um modo geral, você terá um quebra-cabeça solucionável (quase) imediatamente, mas com um pouco de azar, seus quebra-cabeças gerados aleatoriamente podem acabar em uma série de quebra-cabeças não resolvíveis.

Uma maneira de resolver ambos é disponibilizar vetores predefinidos de todos os quebra-cabeças solucionáveis, organizados em grupos de dificuldade e, em seguida, selecionar um quebra-cabeça aleatório dos quebra-cabeças solucionáveis ​​com base na dificuldade. Dessa forma, você estará certo de que todo quebra-cabeça é solucionável, que a dificuldade é previsível e que a geração será feita em tempo constante.

Nzall
fonte