C 程序 - 为什么我的洗牌功能不能正常工作?

C Program - Why is my card shuffling function not working correctly?

我正在创建一个纸牌洗牌函数,它遍历传递的数组,调用另一个函数 swap 以将传递的每个元素更改为另一个,并将该元素存储到另一个名为 shuffledDeck 的数组中。然后我从函数中返回洗牌后的牌组。我在调用该函数时得到了一些洗牌,但大多数都留在同一个地方。我不确定为什么甲板的其余部分不像其他人那样移动。谁能指出我的函数有什么问题可以解释输出?

初始化的卡片组与我从我的函数中获得的输出。

Unshuffled 

[AH] [2H] [3H] [4H] [5H] [6H] [7H] [8H] [9H] [10H] [JH] [QH] [KH] 
[AD] [2D] [3D] [4D] [5D] [6D] [7D] [8D] [9D] [10D] [JD] [QD] [KD] 
[AC] [2C] [3C] [4C] [5C] [6C] [7C] [8C] [9C] [10C] [JC] [QC] [KC] 
[AS] [2S] [3S] [4S] [5S] [6S] [7S] [8S] [9S] [10S] [JS] [QS] [KS] 

Shuffled 

[AD] [AH] [2H] [3H] [4H] [5H] [6H] [7H] [8H] [9H] [10H] [JH] [QH] 
[KH] [KH] [2D] [3D] [4D] [5D] [6D] [7D] [8D] [9D] [10D] [JD] [QD] 
[KD] [AC] [2C] [3C] [4C] [5C] [6C] [7C] [8C] [9C] [10C] [JC] [QC] 
[KC] [AS] [2S] [3S] [4S] [5S] [6S] [7S] [8S] [9S] [10S] [JS] [QS]

我的功能

void swap(char *(*deck)[13], int r, int c)
{
    time_t t;
    srand((unsigned) time(&t));

    int rowToSwap = -1;  //will hold generated row and column to swap
    int colToSwap = -1;
    
    while(rowToSwap < 0 || rowToSwap > 3 && colToSwap < 0 || colToSwap > 12){ //while row to swap and col to swap are outside appropriate ranges
        rowToSwap = rand() % (3 + 1 - 0);
        colToSwap = rand() % (12 + 1 - 0);
    }
    char *temp = deck[rowToSwap][colToSwap];    //swap by elements using a temp holder
    deck[rowToSwap][colToSwap] = deck[r][c];
    deck[r][c] = temp;
}


char *(*shuffleDeck(char *(*deck)[13]))[13]
{
    char *(*shuffledDeck)[13] = malloc(4 * sizeof(*shuffledDeck));

    for(int i = 0; i < 4; i++){
        for(int j = 0; j < 13; j++){
            swap(deck, i, j);
            shuffledDeck[i][j] = deck[i][j];
        }
        
    }
    return shuffledDeck;
}

首先,您可以删除 while 循环,只为行和列调用一次 rand % value。其次,您应该用常量替换那些整数。为什么用 3+1-0 而不是 NUM_ROWS?第三,您应该只调用 srand 一次,否则您会重置随机数生成器的种子。

最后,你代码中的逻辑错误。我认为你错过了最后一个标签的函数定义,但看起来问题是在交换中,你交换了卡片,并且在你的 shuffle_deck 函数中你还设置 shuffled_deck 等于洗牌值。要么就地交换牌组中的牌(即交换),要么复制一份并洗牌。

@chux 建议使用 Fisher-Yates 洗牌。 Fisher-Yates 的“由内而外”变体允许新牌组在从旧牌组复制时洗牌。 Fisher-Yates 在一维数组上工作,但可以使用一点数学来修改它以用于二维数组:

char *(*shuffleDeck(char *(*deck)[13]))[13]
{
    char *(*shuffledDeck)[13] = malloc(4 * sizeof(*shuffledDeck));

    /* Use "inside out" Fisher-Yates shuffle, modified for 2-D array. */
    for(int i = 0; i < 4; i++) {
        for(int j = 0; j < 13; j++) {
            int b = randInt(i * 13 + j + 1);
            int a = b / 13;
            b -= 13 * a;
            /*
             * Note: a*13+b <= i*13+j,
             * so &shuffledDeck[a][b] <= &shuffledDeck[i][j]
             */
            if (a != i || b != j) {
                shuffledDeck[i][j] = shuffledDeck[a][b];
            }
            shuffledDeck[a][b] = deck[i][j];
        }
    }
    return shuffledDeck;
}

上面使用的b = randInt(i * 13 + j + 1)函数调用returns一个小于其参数值的随机整数。参数 i * 13 + j + 1 比目前洗牌的张数多 1。 a = b / 13; b -= a * 13; 语句将该随机数转换为行 a、列 b。至关重要的是,a * 13 + b <= i * 13 + j,因此随机位置 (a,b) 小于或等于位置 (i,j)。

如果随机位置(a,b)小于当前位置(i,j),则将洗好的牌组中的一张牌从位置(a,b)复制到位置(i,j)。那么洗牌后的随机位置 (a,b) 要么从未设置过(如果随机位置与当前位置相同),要么包含一个陈旧的副本。在任何一种情况下,未洗牌的当前位置 (i,j) 的牌都会被复制到洗牌后的随机位置 (a,b)。

最后,未洗牌的所有牌都被复制到洗牌后随机但唯一的位置。

下面是上面使用的 randInt 函数的实现:

#include <stdlib.h>

/* Return random integer from 0 to n-1 (for n in range 1 to RAND_MAX+1u) */
int randInt(unsigned int n) {
    unsigned int x = (RAND_MAX + 1u) / n;
    unsigned int limit = x * n;
    int s;
    do {
        s = rand();
    } while (s >= limit);
    return s / x;
}

请注意,简单的 rand() % n 通常会导致略有偏差的结果。上面的 randInt(n) 函数使用 rand() 但会产生无偏差的结果。

随机数生成器需要由 srand 函数播种。这应该在程序中只完成一次,例如来自 main 函数:

#include <stdlib.h>
#include <time.h>

int main(void)
{
    srand(time(NULL));
    /* other stuff */
}