Jogo de Alfinete (Pin Spin)
Atire agulhas em um disco giratório sem encostar nas outras — 10 fases com velocidade crescente
O que é o Jogo de Alfinete?
Pin Spin é um jogo casual de navegador da mesma família dos sucessos mobile AA e Pin Out. Ao centro da tela, um disco branco gira em velocidade constante e você dispara agulhas uma a uma a partir de baixo. Cada agulha precisa fincar no disco sem encostar em nenhuma das que já estão fincadas. Cada fase dá uma quantidade fixa de agulhas: encaixe todas para avançar, e qualquer colisão encerra a rodada na hora. As regras são simples o bastante para qualquer um pegar em segundos, mas a curva de dificuldade rapidamente vira um teste sério de timing e autocontrole. A ferramenta roda inteira no navegador, sem cadastro nem download — clique com o mouse no desktop, toque a tela no celular.
Como Usar
Passo a passo
- Ao abrir a página, o jogo começa automaticamente na fase 1 com o disco já girando
- Clique na área de jogo (ou pressione Espaço ou Enter) para disparar uma agulha
- A agulha voa até o centro e finca no disco; ela não pode encostar em nenhuma agulha já fincada
- Coloque todas as agulhas da fase com segurança para passar para a próxima
- Qualquer colisão encerra a rodada — escolha «Repetir fase» ou «Reiniciar» para continuar (observação: pressionar Espaço / Enter na tela de fim de jogo dispara «Reiniciar» e leva você de volta à fase 1, não «Repetir fase» — use o botão se quiser refazer a mesma fase)
Dicas para concluir as fases
- O disco gira em velocidade constante, então o ritmo importa mais do que a rapidez do clique: mire na linha do meio do espaço.
- Atire logo depois que uma agulha vizinha passou; a agulha leva uns 120 ms para chegar, então adiante um pouquinho.
- Até 3 disparos ficam em fila e saem em sequência; cliques além desse limite são descartados, não enfileirados, então não adianta martelar o clique.
- Nas fases avançadas os espaços ficam estreitíssimos e o giro acelera bastante; pular um disparo arriscado é melhor que uma colisão por ganância.
Dificuldade por fase
- Fases 1-3: muitas agulhas, espaços largos — bom para pegar o ritmo.
- Fases 4-7: já há mais agulhas no disco e é preciso prever a rotação.
- Fases 8-10: o disco fica quase cheio, todo disparo precisa de timing milimétrico.
Casos de uso
Princípio técnico
O loop principal é dirigido por requestAnimationFrame. A cada quadro, um valor interno rotation é incrementado por um speed fixo (graus por quadro) e, em seguida, todas as agulhas fincadas são percorridas para receber transform: rotate(baseAngle + rotation). Como transform é uma propriedade de camada de composição, o navegador entrega a rotação à thread de composição da GPU sem disparar layout nem paint, então a fase final, com todas as 15 agulhas girando, ainda mantém 60 fps com folga. A detecção de colisão usa a abordagem «diferença de ângulo convertida em comprimento de corda». Cada agulha fincada é representada por seu baseAngle no sistema de coordenadas do disco (um deslocamento fixo em relação ao disco, que não muda com a rotação). Quando uma agulha disparada chega ao centro, seu ângulo em coordenadas do mundo é TARGET_ANGLE = 0, o que mapeia em coordenadas do disco como normalizeAngle(0 - rotation). O novo baseAngle é então comparado a todos os baseAngle já existentes: a diferença angular Δθ é convertida no comprimento de corda 2R·sin(Δθ/2) entre os centros dos pontos da ponta das agulhas (R = 111px do centro de cada ponto-ponta até o centro do disco). Se ficar abaixo do limiar de 24px, o disparo é considerado colisão e a rodada termina. O disparo é regulado por uma pequena máquina de estados. shootLocked marca uma agulha como «em voo» e impede que tanto o evento transitionend quanto o setTimeout de reserva de 180 ms encerrem o mesmo disparo duas vezes. A transição CSS dura 120ms; o setTimeout de 180ms serve como rede de segurança para o caso raro em que transitionend não dispara (por exemplo, quando o DOM é alterado antes que a transição se complete). pendingShots enfileira os cliques recebidos enquanto uma agulha ainda está no ar, com limite de 3 — cliques além desse teto são descartados (não enfileirados), preservando a sensação de tiro rápido sem deixar várias agulhas convergirem ao centro ao mesmo tempo. Tanto a falha quanto a vitória definem gameOver como true: o loop principal continua rodando, mas para de atualizar rotation, congelando a cena. Para escalar a exibição, o jogo mantém um tamanho de design fixo de 420×720, e o invólucro externo calcula um fator de escala a partir do viewport e o aplica via transform: scale. A implementação atual limita a escala a 1, então viewports pequenos são reduzidos proporcionalmente enquanto viewports de desktop mantêm o tamanho original — não aumentam. Assim a detecção de colisão sempre opera no sistema de coordenadas de design com um único conjunto de limiares em pixels, e o veredito é idêntico em qualquer tela.
- Loop principal: requestAnimationFrame, a cada quadro rotation += speed, todas as agulhas recebem transform: rotate(baseAngle + rotation).
- Aceleração por GPU: a rotação usa transform em vez de left/top, então o compositor do navegador cuida disso — sem layout nem repintura.
- Detecção de impacto: o 0° em coordenadas do mundo é projetado de volta para coordenadas do disco como normalizeAngle(0 - rotation); o comprimento de corda 2R·sin(Δθ/2) é comparado a um limiar de 24px.
- Máquina de estados: shootLocked impede dupla finalização, pendingShots é limitado a 3 com excedente descartado (não enfileirado), gameOver congela as atualizações de rotação.
- Finalização do disparo: voo CSS de 120ms, transitionend como caminho principal + setTimeout de 180ms como rede de segurança para o caso raro em que transitionend não dispara.
- Escalonamento: sistema de design fixo 420×720; o invólucro aplica transform: scale conforme o tamanho do viewport, limitado a 1 para que desktops mantenham o tamanho original em vez de ampliar.
- Armazenamento: somente a melhor fase concluída é salva em localStorage. Nada é enviado ao servidor; sem contas, sem placar.
Exemplos
Velocidade e quantidade de agulhas nas 10 fases
Fase Início Disparos Velocidade (deg/frame)
1 2 8 1.35
2 3 9 1.50
3 3 10 1.65
4 4 11 1.80
5 4 12 2.00
6 5 13 2.20
7 5 14 2.45
8 6 15 2.70
9 7 15 3.00
10 8 15 3.35Uma rodada típica: falha na fase 5
Fase 5: 4 agulhas iniciais, 12 a disparar.
Disparos 1-7: encaixam direitinho, restam 5.
Disparo 8: feito antes da agulha anterior se acomodar, cedo demais.
Veredito: novo baseAngle a 8° do disparo 6, abaixo de DOT_HIT_DISTANCE.
Resultado: colisão, rodada termina com «Fim de jogo!».
Clique em «Repetir fase» para recomeçar a fase 5.Resumo de uma rodada completa
Tempo de conclusão: cerca de 2min 40s
Disparos no total: 117
Derrotas: 3
Melhor fase: 10
Mensagem: «Todas as fases concluídas!», botão exibe «Jogar de novo».
A melhor fase é gravada em localStorage e persiste mesmo recarregando a página.Perguntas frequentes
Cliquei em um espaço vazio, por que perdi mesmo assim?
A agulha não chega instantaneamente. Do clique até o contato real com o disco passam uns 120 ms, e o disco continua girando nesse intervalo. Se o vão estava largo no momento do clique, mas outra agulha girou até a linha-alvo durante o voo, a nova cai em cima dela. A solução simples é disparar logo depois que um vão cruzou a linha, deixando uma folga para a rotação.
Posso segurar a tela para tiro contínuo?
Você pode clicar rápido, mas o disparo é controlado: nenhum sai enquanto o anterior estiver no ar. Até 3 cliques durante o voo ficam em fila e saem automaticamente em sequência; qualquer coisa além disso é descartada, não enfileirada. Nas fases avançadas, é melhor reduzir o ritmo e deixar cada agulha se acomodar antes do próximo disparo, em vez de martelar o clique.
Como a dificuldade aumenta?
Cada fase tem sua própria velocidade de rotação, número de agulhas iniciais e total de disparos exigidos. A velocidade vai de 1,35 deg/frame na fase 1 até 3,35 deg/frame na fase 10 — cerca de 2,5×, sem chegar ao triplo. As agulhas iniciais sobem de 2 para 8, então os vãos sobreviventes encolhem. Nas fases finais, paciência conta mais do que velocidade pura.
Depois de perder, recomeço do zero ou repito a mesma fase?
As duas opções estão disponíveis, mas o que aciona cada uma importa. O botão «Repetir fase» reinicia a fase em que você perdeu, com o mesmo número de agulhas e a mesma velocidade; o botão «Reiniciar» — assim como pressionar Espaço / Enter na tela de fim de jogo — leva você de volta à fase 1. Não há atalho de teclado para «Repetir fase»; quem aperta Espaço por reflexo para continuar acaba caindo na fase 1. Sua melhor fase concluída fica sempre em localStorage.
Por que a área de jogo parece tão estreita?
O tamanho de design é 420×720, como um celular em modo retrato. No celular fica naturalmente em tela cheia; no desktop o invólucro reduz proporcionalmente, mas a escala é limitada a 1, então uma janela grande de desktop não amplia o jogo além de 420×720. A lógica do jogo não muda com o tamanho da janela.
Algum dado é enviado para um servidor?
Não. O jogo todo roda localmente no navegador. A única coisa armazenada é um número — sua melhor fase concluída — em localStorage. Apague os dados do site ou troque de navegador e ele some. Não temos placar e não rastreamos pontuação ou interações do nosso lado.
Algo a observar no celular?
Ative o modo de tela cheia do navegador para que o gesto de puxar para baixo não recarregue a página acidentalmente. Modo paisagem funciona, mas a proporção retrato original combina mais com a mecânica. Em celulares mais simples com queda eventual de quadros, feche outras abas e animações pesadas; o próprio jogo só faz alguns cálculos de transform por quadro, então a carga sobre o aparelho é mínima.