So you're definitely going wrong when you do:
tr -dc ... </dev/urandom
You don't need to -d
elete anything. If your goal is randomness - then you should use all you get.
For example:
tr '\0-\377' '[H*64][D*64][C*64][S*]' </dev/urandom |...
...which would always return one of [HDCS]
w/out deleting any input, and would return it on a spread spectrum of random input bytes.
I wrote a function that will populate a shuffled deck:
deck()( HOME=/dev/null; ${deck:+"echo"}
tr=$(printf '[%s*19]' 1 2 3 4 5 6 7 8 9 a b c d)
tr '\0-\377' "[J*9]$tr" |
dd cbs=1 obs=2 conv=unblock |
paste -d'W\nX\nY\nZ' - ~ - ~ - ~ - ~ |
sed ' /^J/d;1!G;/^\(..\).*\1/d
h;s/\n/&/51;tq' -e'd;:q' -eq
) <"${1:-/dev/urandom}"
Doing...
time (deck|wc -l)
...prints...
52
( deck | wc -l; ) \
0.03s user 0.04s system 224% cpu 0.028 total
deck()
defaults to drawing its random data from the linux /dev/urandom
PRNG, but if it is called with an argument it will interpret that as a filename for an alternate source of random input.
And every card returned - (one per line) - is unique. It doesn't bother trying to randomize the suits and assigns them in round-robin order. It doesn't have to bother: the order of the card values is already random, and the cards are going to have to be pruned for uniques in a random order anyway, and so the result of the pruning operation is random suits.
sed
actually handles that. What sed
does is:
/^J/d
- clear all Jokers (byte values
0-$(((256%13)-1))
)
1!G
- On every line but the first it
G
ets a copy of h
old space appended to pattern space.
/^\(.*\)\n.*\1/d;h
- If there is another card in its current stack which matches the card just pulled in it
d
eletes pattern space and saves nothing...
- ...else, if the current line is so far unique, it will copy the current stack to
h
old space.
- The first line is always
h
eld.
/\(.*\n\)\{51\}/q;d
- If there are 51
\n
ewlines in pattern space at that time, sed
q
uits input and prints the deck to stdout...
- ...else it
d
eletes pattern space and prints nothing.
Now if you wanted to draw
...
draw() if [ -n "${1##*[!0-9]*}" ] || return 2
then case $((${#deck}>($1*3)))$deck in
(?*[!0-9W-Za-d[:space:]]*)
return 2;;
(0*) deck=$deck$(deck)
draw "$1";;
(1*) eval " hand='$(echo "$deck" |
sed "$1 N;s/\n/' deck='/")'"
esac; fi
...which is a function that would automatically populate the current shell variables $hand
and $deck
as necessary. It pulls from the top of $deck
the number of cards requested in $1
and puts those cards in $hand
. $deck
is trimmed from the top each time. If draw()
is called and $deck
is not sufficiently large enough to fill $hand
as requested, then $deck
is replenished with a new, shuffled $deck
first.
And last:
show() case $1 in
(*[!0-9W-Za-d[:space:]]*|'') return 2;;
(*) ( eval " $(printf "T='\t' E=\033 nl='\n'")"
str(){ m=$1 l=\$1$2 r=\$i$3 d=......
set 9 8 7 6 5 4 3 2 1
for i in d c b a "$@"
do [ "$i" = d ] && M=ROYAL || M=STRAIGHT M=${M#"$m"}
eval printf "\"\ts/.*$l$d$r.*/"'$M${M:+ }$m:/;t$a.1\n"'
[ "$1" = 1 ] && unset a r l d i m M && break
shift; done; }
knd(){ c=$1 m="$1 OF A KIND" IFS=$nl
shift; s="$*"; set -f .'\\1'
until [ "$#" -gt "$((c-1))" ]
do set "$@" "$@"
done; shift "$(($#-(c-1)))"
printf %b "\t/\([a-d0-9]\)$@/{\n"
for s in $s; do eval 'printf "\t\t%s\n" "'"$s\""; done
printf "\tt$a.1\n\t}\n"; unset l c a s IFS; }
br(){ case $a.$1 in
(.*|*[!0-9]*.) return 2;;
(*.-t) printf "\n:$a.0\n%s\n" \
"$a!b${n:-$((a+1)).0}";;
(*.-b) printf ":$a.1\n";;esac;shift
for s do eval 'printf "\t%s\n" "'"$s\"";done
unset n IFS a s; }
for k in k1.2,1.2 k1.1,1.1 uk1.1,1.1
do echo "$1" | sort -"$k"; echo
done| sed -ne:n -e'$!N;s/\n\(.\)/\1/;tn
x;/./!g;x;$G;s/\n$//p' |
sed -ne"$( a=1 br -t
a=1 str FLUSH '\(.\)' '\\\1'
a=1 br -b 's/.*\([W-Z]\).......\\1.*/FLUSH:/' /:/h /^\[RS]/be n
a=2 br -t
a=2 knd 4 s/.\*/\$m:/
a=2 knd 3 s/// '/\(.\)[W-Z]\1/!s/.*[W-Z]/$m:/' s//FULLHOUSE:/
a=2 knd 2 s///2 tP 's/.*/$m:/' :P 's/.*[W-Z]/2 PAIR:/'
a=2 br -b /^\[F4]/h x //h //be x /:/h n
n=e a=3 br -t
a=3 str STRAIGHT .
a=3 br -b /:/h x h)
:e" -e'5!n;5!be' -e'y/123456789abcd/234567891JQKA/
s/\(.\)\([W-Z]\)/ '"$E[0;3\2;47m \1 $E[m /g"'
s/W\([^ ]* \)/1\1♦ /g;s/X\([^ ]* \)/1\1♥ /g
s/Y\([^ ]* \)/0\1♠ /g;s/Z\([^ ]* \)/0\1♣ /g
s/ 1/10/g;x;s/.*[^:]//;/.\{8\}/!s/$/'"$T/;G;s/\n/$T/
s/\([^m]*m\)\{26\} /&\\$nl\\$nl$T$T/g;s/[[:space:]]*$//p"
) esac
Throughout both of deck()
and draw()
the cards are stored in sort order like this:
1 2 3 4 5 6 7 8 9 a b c d
2 3 4 5 6 7 8 9 10 J Q K A
And the suits as well...
W X Y Z
♦ ♥ ♠ ♣
This makes it easy - every card is 2 bytes, and they'll always sort appropriately. The cards in $hand
and $deck
are all \n
ewline delimited - always. So in show()
the hardest part is done as simply as...
sort -k1.1,1.1
...where we sort the hand on the first byte of each line. The rest is just comparing - which sed
does... a lot of. It handles 2,3,4 of a kind, fullhouse, 2 pair, royal straight, straight, royal flush, straight flush. It will prefer to report on them in the following order, regardless of how many cards are found in its first argument:
ROYAL FLUSH
STRAIGHT FLUSH
4 OF A KIND
FULLHOUSE
FLUSH
STRAIGHT
3 OF A KIND
2 PAIR
2 OF A KIND
Note that the show()
function is not necessarily linked to either of the other two - it can be called with an argument generated in any way which coincides with the encoding scheme mentioned above and it will produce the output desired. Each of the functions can stand on its own in a modular way when/if needed.
Note also that there is no 5 card limit for any of the three functions - they should all handle hands of any size. And they are all designed to work a persistent $deck
(with error checking) - and so can be used with some level of persistence.
It is at the very end of show()
that the encoding is decoded. All encoded values are rendered decoded in a single action:
y/123456789abcd/234567891JQKA/
It happens at once in a single y///
translation without any danger of mistakenly editing the value twice.
Its output looks like:

out.txt
contains no info about suits?m ♦ A
m ♠ 2
m ♣ J
m ♦ 7
m ♥ 2