Worse yet is a fumble with randLte(). The name stands for "RANDom Less Than or Equal". Not only that, but a comment specifies that it generates values less than or equal to a provided upperBound argument. Except it doesn't. It produces values up to but excluding upperBound. So it should be renamed randLt, the comment changed to reflect what it really does, and the programmer should pay more attention to how the modulo operator actually works. (Hilariously, despite the fact that I misunderstood what my function did, what it actually did was what I needed it to do, which was also different than what I thought I needed it to do. Thus why you shouldn't code while tired, I guess.)
 One way to fix this might be to use an array of 16 numbers representing the indexes that still can be selected, rather than using the array to represent the indexes that have already been selected. When one is selected, move a number from the end of the array into the position where the old one was. The i counter can be used to keep track of how many usable items are left in the array, and the unused portion can be ignored. So something like this might work (this is untestedfixed and tested):
int remainingIdxs[16]remainingDice[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
for (i = 15;i16;i >= 0;i1;i--) {
    idx = randLte(i);
    letter = randLte(6);
    board[i]board[i - 1] = dice[remainingIdxs[idx]][letter];
    remainingIdxs[idx] = remainingIdxs[i];remainingIdxs[i - 1];
}
 
                