Friday, November 28, 2008

Dice

I was sitting in a hotel room without any Internet access and without much to do... and I made this simple script, to roll die. It uses the date to generate a random number, and then limits it to the specified range, from 0 to N.

Here's the code:
 
1 #!/bin/bash
2 #
3 # Die roll script.
4 #
5 # Quick and dirty way to generate random numbers between one and N,
6 # just like a die roll simulation.
7 #
8 # Parameters:
9 # Optionally, a number of sides for the die,
10 # but if this is not supplied, 6 is used by default;
11 # if a series of parameters is provided, the script
12 # rolls a die for each of the parameters.
13 # Author:
14 # Konrad Siek
15
16 # Generates the random number from a date.
17 function roll {
18 expr \( $(date +%N) % "$1" \) + 1
19 }
20
21 # Do the actual die rolls.
22 if [ "$1" == "" ]
23 then
24 # If no argument is given, then roll a six-sider.
25 roll 6
26 else
27 # Roll a die for each of the arguments.
28 while [ "$1" != "" ]
29 do
30 roll $1
31 shift
32 done
33
34 fi
35


As you can see, the script is easy and fun. It's also quite useful, if you happen to find yourself wanting to play some war games or something.

I didn't know though, if it's reliable, so I wrote another short script - this one testing the previous one, by rolling a whole lot of rolls and checking the distribution of the results.

The fun thing though, was to create a short function inside, which divides some numbers and presents the result in a, more or less, human readable form. It's a neat little trick, even if I say so myself.

Here's the code:
 
1 #!/bin/bash
2 #
3 # Die roll testing script.
4 #
5 # Roll a die for a number of times and check the distribution
6 # that comes out.
7 #
8 # Potential issues:
9 # Can take quite a long time to finish.
10 # Parameters:
11 # 1. Number of sides, or six, if not specified,
12 # 2. Number of tests, or 100, if not specified.
13 # Author:
14 # Konrad Siek
15
16 # Human readable division function.
17 # Takes two arguments and returns a pretty fraction.
18 function divide {
19 d=`expr $1 / $2`
20 m=`expr $1 % $2`
21 if [ $m != 0 ]
22 then
23 echo "$d + ( $m / $2 )"
24 else
25 echo "$d"
26 fi
27 }
28
29 # Establish number of sides for the die.
30 if [ "$1" == '' ]
31 then
32 sides=6
33 else
34 sides=$1
35 fi
36
37 # Establish number of tosses.
38 if [ "$2" == '' ]
39 then
40 rolls=100
41 else
42 rolls=$2
43 fi
44
45 # Command to use for testing.
46 command='./d'
47
48 # Maybe it's a global command, you never know.
49 $command > /dev/null
50 if [ $? != '0' ]
51 then
52 command='d'
53 fi
54
55 # Roll the die, and check results.
56 $command > /dev/null
57 if [ $? != '0' ]
58 then
59 # Exit if the command is not found.
60 echo "Could not run commnd 'd' or './d'. Exiting..."
61 exit 1
62 fi
63
64 # Instantiate the result array.
65 for i in $(seq 1 $sides)
66 do
67 control[$i]=0;
68 done
69
70 # Evaluate the results.
71 sum='0';
72 count='0';
73
74 # Evaluate expected value in each 'class'.
75 expected=`divide $(expr $sides + 1) 2`
76
77 # Roll the die, count the results, and display it at each step.
78 for i in $(seq 1 $rolls)
79 do
80 component=`$command $sides`
81 sum=`expr $sum + $component`
82 count=`expr $count + 1`
83 control[$component]=`expr ${control[$component]} + 1`
84 echo -e "$i\t$component\t$sum\t$(divide $sum $count)"
85 done
86
87 # Evaluate the global.
88 value=`divide $sum $count`
89
90 # Display results.
91 echo
92 echo -e "expected value:\t$expected"
93 echo -e "actual value:\t$value"
94 echo -en "rolled:\t"
95
96 for i in $(seq 1 $sides)
97 do
98 echo -en "$i: ${control[$i]}\t";
99 done
100 echo


The code is also available at GitHub as bash/die.

No comments: