Como embaralhar as cartas para um jogo de cartas?


13

Estou tentando desenvolver um jogo de cartas para Android. Alguém pode me sugerir como escrever um código para baralhar efetivamente as cartas de jogar?

Respostas:


21

Baralhar cartas é um algoritmo que é fácil de escrever intuitivamente e, ao fazê-lo, é totalmente errado. Há uma boa referência para implementar o embaralhamento de cartões corretamente na Wikipedia . O que estou apresentando aqui é uma versão muito simplificada do algoritmo abordado nessa página em O algoritmo moderno .

Aqui está a idéia básica, em inglês simples:

Considere um baralho de cartas. Para esta discussão, você pode ter qualquer número de cartas no baralho, e elas podem começar em qualquer ordem.

Nós estaremos falando sobre "posição" no baralho, onde "posição" é quantas cartas são maiores no baralho do que a carta nessa posição. Por exemplo, a carta no topo do baralho está na posição 0, a carta abaixo dela está na posição 1 (porque há 1 carta mais alta que ela - a carta superior) e, em um baralho padrão de 52 cartas, a parte inferior a carta está na posição 51, pois 51 são mais altas do que no baralho.

Agora, consideramos cada posição no baralho, uma de cada vez, começando do fundo e subindo até o topo.

Para cada posição, selecionamos aleatoriamente uma das cartas que está nessa posição ou em uma posição de número mais baixo (lembre-se, o topo do baralho é 0, e estamos trabalhando a partir da parte inferior do baralho, então para cada posição, você escolhe efetivamente todas as cartas nessa posição e escolhe aleatoriamente uma dessas cartas).

Quando fizemos a seleção aleatória, trocamos a carta na posição que estamos considerando atualmente pela carta que selecionamos aleatoriamente. Se selecionarmos aleatoriamente o cartão que já estava nessa posição, nenhum swap será realizado.

Depois de trocar (ou não trocar, se selecionarmos aleatoriamente a carta que já estava na posição que estávamos considerando), passamos para a próxima posição no baralho e continuamos.

Em pseudocódigo, com n sendo o número de cartas do baralho, e um sendo uma matriz que representa o convés, o algoritmo se parece com isso:

for each i in [n .. 1] do
     j  random integer in [ 0 .. i ]
     exchange a[j] and a[i]

1
O algoritmo também é bem visualizado aqui: bost.ocks.org/mike/algorithms/#shuffling
Felsir

9

Primeiro, você define uma sequência de todas as cartas que deseja embaralhar:

List<Card> shuffled = new ArrayList<Card>();
shuffled.addAll(allCards);

Depois, você percorre todas as posições da sequência e atribui-lhe um cartão aleatoriamente.

Random random = new Random();
for (int i = shuffled.size() - 1; i >= 0; i--) {
    int j = random.nextInt(i + 1);

    /* swap cards i,j */
    Card card = shuffled.get(i);
    shuffled.set(i, shuffled.get(j));
    shufflet.set(j, card);
}

Agora shuffledé uma sequência aleatória de todos os seus cartões.


3
isso é conhecido como aleatório de Knuth: en.wikipedia.org/wiki/Knuth_shuffle
krolth

2

Eu gostaria de mencionar e mencionar "o formato preservando a criptografia" como um método para embaralhar as cartas em um jogo.

Essencialmente, o que você tem é um algoritmo de criptografia que leva um valor de 0 a 51 e uma chave (shuffle seed) e cospe um valor de 0 a 51. Como a criptografia é reversível por definição, significa que quaisquer 2 números de entrada não podem criptografar para o mesmo número de saída, o que significa que, se você criptografar de 0 a 51, obterá de 0 a 51 como saída em uma ordem diferente. Em outras palavras, você tem seu shuffle e nem precisa fazer nenhum shuffling real.

Nesse caso, você teria que criar ou encontrar um algoritmo de criptografia que capturasse 6 bits e cuspisse 6 bits (0-63). Para extrair a próxima carta do baralho, você teria uma variável de índice iniciada em zero, criptografaria esse índice, aumentaria o índice e examinaria o valor que saiu da cifra. Se o valor for> = 52, você o ignora e gera um novo número (e incrementa o índice novamente, é claro). Como a criptografia de 0 a 63 resultará em 0 a 63 como saída, em uma ordem diferente, você estará ignorando qualquer valor que saia> = 52 para que você tenha seu algoritmo que aceita 0 a 51 e cospe 0 a 51.

Para reorganizar o deck, defina o índice novamente como zero e altere a chave de criptografia (shuffle seed).

Seu algoritmo não precisa ter qualidade criptográfica (e não deveria ser, porque isso seria computacionalmente caro!). Uma maneira realmente boa de criar um algoritmo de criptografia de tamanho personalizado como esse seria usar uma rede feistel, que permite personalizar tamanho e qualidade, dependendo de suas necessidades. Para a função round da rede feistel, eu recomendaria algo como murmurhash3 porque é rápido e tem um bom efeito de avalanche, o que faria os shuffles parecerem bem aleatórios.

Confira minha postagem no blog para obter informações e códigos-fonte ainda mais detalhados: http://blog.demofox.org/2013/07/06/fast-lightweight-random-shuffle-functionality-fixed/


Essa resposta, como é formulada atualmente, não ajuda muito quando a URL inevitavelmente cai da superfície da Internet. Considere elaborar na resposta os pontos mais importantes do artigo vinculado, para que a resposta possa se sustentar por si mesma.
Lars Viklund

1
Bom ponto Lars, atualizado com mais informações para que um leitor possa pelo menos procurar mais informações sobre todos os componentes específicos de uma solução para baralhar cartões usando o formato que preserva a criptografia. Obrigado!
Alan Wolfe

1

O tutorial java 1.5 enum tem uma maneira interessante de implementar um baralho de cartas, construindo o baralho, baralhando e negociando. Tudo muito simples, utilizando enums eCollections

public class Card {
    public enum Rank { DEUCE, THREE, FOUR, FIVE, SIX,
        SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE }

    public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }

    private final Rank rank;
    private final Suit suit;
    private Card(Rank rank, Suit suit) {
        this.rank = rank;
        this.suit = suit;
    }

    public Rank rank() { return rank; }
    public Suit suit() { return suit; }
    public String toString() { return rank + " of " + suit; }

    private static final List<Card> protoDeck = new ArrayList<Card>();

    // Initialize prototype deck
    static {
        for (Suit suit : Suit.values())
            for (Rank rank : Rank.values())
                protoDeck.add(new Card(rank, suit));
    }

    public static ArrayList<Card> newDeck() {
        return new ArrayList<Card>(protoDeck); // Return copy of prototype deck
    }
}

E a turma para gerenciar o convés.

public class Deal {
    public static void main(String args[]) {
        int numHands = Integer.parseInt(args[0]);
        int cardsPerHand = Integer.parseInt(args[1]);
        List<Card> deck  = Card.newDeck();
        Collections.shuffle(deck);
        for (int i=0; i < numHands; i++)
            System.out.println(deal(deck, cardsPerHand));
    }

    public static ArrayList<Card> deal(List<Card> deck, int n) {
         int deckSize = deck.size();
         List<Card> handView = deck.subList(deckSize-n, deckSize);
         ArrayList<Card> hand = new ArrayList<Card>(handView);
         handView.clear();
         return hand;
     }
}


-2
    ArrayList deckCards = new ArrayList<Card>();
    //add your cards to the deck
    deckCards.add(card1);
    deckCards.add(card2);
    deckCards.add(card3);
    ....
    //shuffle the array list
    Collections.shuffle(deckCards);

1
As respostas somente de código são desencorajadas.
SurvivalMachine
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.