๐ŸŽฒ Dice Roll Statistics โ€” Mini Project
0%
Mini Project  ยท  Arrays ยท Functions ยท Pointers

๐ŸŽฒ Dice Roll Statistics
Mini Project in C

Roll a dice 100 times. Count frequencies in an array. Walk it with pointers. Analyse with functions. Print a live ASCII bar chart. Six focused build steps โ€” one satisfying program.

int array[6] pointer walk function pointer rand() + srand() pass by pointer ASCII bar chart
S1
Setup & Seed
S2
Roll & Count
S3
Pointer Walk
S4
Analyse
S5
Bar Chart
S6
Full Program

๐ŸŽฒ  Project Overview โ€” What We Are Building

A dice simulator that rolls a six-sided die N times, records how many times each face (1โ€“6) appears in a frequency array, then analyses the data โ€” finding the hottest face, coldest face, and deviation from perfect fairness โ€” and prints a real ASCII bar chart of the results. Every concept is used for a real purpose, not just to demonstrate syntax.

ARRAY โ€” frequency storage POINTER โ€” walk & analyse FUNCTIONS โ€” modular design PASS BY POINTER โ€” modify array FUNCTION POINTER โ€” pluggable display
data flow through the project
rand() % 6
โ†’
freq[face]++
โ†’
int *ptr walk
โ†’
findMax(ptr,n)
โ†’
printChart(ptr,n)

Roll โ†’ store in array โ†’ pointer traversal โ†’ analysis functions โ†’ visual output. Each arrow is a function call passing a pointer.

step 1 โ€” setup and seed
S1
๐ŸŒฑ Setup โ€” The Frequency Array & Random Seed
int freq[6] is the heart of the project โ€” index 0 = face 1, index 5 = face 6
Setup
The entire project revolves around one array: int freq[6]. Each index stores how many times that die face was rolled โ€” freq[0] for face 1, freq[5] for face 6. We initialise it to all zeros. The index offset (freq[roll - 1]) is the key mapping to memorise.

srand(time(NULL)) seeds the random number generator with the current time so each run produces a different sequence. Without seeding, rand() always returns the same sequence โ€” useful for testing, useless for a dice simulator.
s1_setup.c
C
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
    int freq[6] = {0};          /* index 0-5 โ†’ face 1-6 */

    srand(time(NULL));          /* seed once โ€” never inside loop */

    /* Roll 20 times โ€” proof of concept */
    for (int i = 0; i < 20; i++) {
        int face = rand() % 6;   /* 0-5 โ€” direct array index */
        freq[face]++;
        printf("Roll %2d: face %d\n", i+1, face+1);
    }

    printf("\nRaw freq array:\n");
    for (int i = 0; i < 6; i++)
        printf("  Face %d : %d times\n", i+1, freq[i]);
    return 0;
}
sample output (varies each run)
Roll  1: face 3
Roll  2: face 1
Roll  3: face 6
...
Raw freq array:
  Face 1 : 4 times
  Face 2 : 3 times
  Face 3 : 5 times
  Face 4 : 2 times
  Face 5 : 3 times
  Face 6 : 3 times
Why freq[face]++ not freq[face+1]++? We use rand() % 6 โ†’ 0 to 5 directly as the index. Face 1 is stored at index 0, face 6 at index 5. We only add 1 when printing โ€” not when indexing. Mixing these up is the most common off-by-one bug in this project.
step 2 โ€” roll function
S2
๐ŸŽฒ rollDice() โ€” Filling the Array via Pointer
Function receives int *freq โ€” rolls N times โ€” increments array in-place
Function + Pointer
The rolling logic becomes a function: rollDice(int *freq, int rolls). We pass the freq array as a pointer โ€” the function writes directly into the caller's array, no copy made. This is the classic pass-by-pointer to modify pattern. Inside, freq[face]++ works exactly the same whether freq is a declared array or a pointer โ€” because array indexing is pointer arithmetic.
s2_roll_function.c
C
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* freq is a pointer to the caller's array โ€” no copy made */
void rollDice(int *freq, int rolls) {
    for (int i = 0; i < rolls; i++) {
        int face = rand() % 6;      /* 0-5  */
        freq[face]++;                  /* same as *(freq + face)++ */
    }
}

/* Print raw counts โ€” const: function only reads */
void printRaw(const int *freq, int n) {
    for (int i = 0; i < n; i++)
        printf("  Face %d : %3d\n", i+1, freq[i]);
}

int main() {
    int freq[6] = {0};
    srand(time(NULL));

    rollDice(freq, 60);   /* freq decays to &freq[0] automatically */

    printf("After 60 rolls:\n");
    printRaw(freq, 6);

    rollDice(freq, 40);   /* add 40 more โ€” accumulates in same array */
    printf("\nAfter 100 rolls:\n");
    printRaw(freq, 6);
    return 0;
}
output
After 60 rolls:
  Face 1 :  12
  Face 2 :   9
  Face 3 :  11
  Face 4 :   8
  Face 5 :  10
  Face 6 :  10

After 100 rolls:
  Face 1 :  18
  Face 2 :  15
  Face 3 :  17
  Face 4 :  14
  Face 5 :  18
  Face 6 :  18
Proof the pointer works: calling rollDice twice on the same freq accumulates โ€” the second 40 rolls add on top of the first 60. The function modified the real array both times. If we had passed by value (impossible for arrays, but conceptually) each call would work on a copy and the counts would reset.
step 3 โ€” pointer walk
S3
๐Ÿšถ Pointer Walk โ€” Traversing the Frequency Array
int *ptr = freq โ€” move ptr++ through each slot โ€” demonstrate two traversal styles
Pointer Arithmetic
Now we walk the frequency array using an explicit pointer variable. int *ptr = freq sets ptr pointing at freq[0]. Each ptr++ advances by sizeof(int) bytes โ€” landing on the next frequency slot. Dereferencing with *ptr reads the count at the current position. This style โ€” pointer walk instead of index loop โ€” makes the underlying memory model visible. Both produce identical machine code; only the source notation differs.
s3_pointer_walk.c
C
#include <stdio.h>

/* Walk via pointer โ€” compute total rolls */
int totalRolls(const int *freq, int n) {
    int sum = 0;
    const int *ptr = freq;             /* ptr starts at freq[0]     */
    const int *end = freq + n;         /* one-past-last sentinel     */
    while (ptr < end) sum += *ptr++;   /* read *ptr, then advance   */
    return sum;
}

/* Compare both traversal styles side by side */
void compareMethods(const int *freq, int n) {
    printf("  %-6s  %-14s  %-14s  Match?\n",
           "Face", "Index freq[i]", "Ptr *(freq+i)");
    printf("  ------------------------------------------\n");
    for (int i = 0; i < n; i++) {
        int by_index = freq[i];
        int by_ptr   = *(freq + i);
        printf("  %-6d  %-14d  %-14d  %s\n",
               i+1, by_index, by_ptr,
               by_index == by_ptr ? "YES" : "NO");
    }
}

void rollDice(int *freq, int rolls){
    for(int i=0;i<rolls;i++) freq[rand()%6]++;
}

#include <stdlib.h>
#include <time.h>
int main() {
    int freq[6] = {0};
    srand(time(NULL));
    rollDice(freq, 100);

    printf("Both traversal methods produce identical values:\n\n");
    compareMethods(freq, 6);

    printf("\nTotal rolls counted by pointer walk: %d\n",
           totalRolls(freq, 6));
    return 0;
}
output
Both traversal methods produce identical values:

  Face    Index freq[i]    Ptr *(freq+i)    Match?
  ------------------------------------------
  1       17               17               YES
  2       14               14               YES
  3       19               19               YES
  4       16               16               YES
  5       18               18               YES
  6       16               16               YES

Total rolls counted by pointer walk: 100
sum += *ptr++ โ€” operator precedence: Post-increment ptr++ evaluates after *ptr is read. So this single expression reads the current value, adds it to sum, then moves the pointer forward. It is the idiomatic C pointer-walk idiom โ€” compact and efficient.
step 4 โ€” analysis functions
S4
๐Ÿ” Analysis โ€” findMax, findMin, calcAverage, calcFairness
Four functions โ€” each receives const int *freq โ€” returns a result โ€” no modification
Analysis Functions
Four pure analysis functions โ€” all take const int *freq (read-only, efficient) and return a computed result. findMax and findMin return the index of the most and least rolled face by walking the pointer. calcAverage totals via pointer walk then divides. calcFairness computes how much each face deviates from the perfect expected count โ€” a fair die rolled 600 times should show each face exactly 100 times.
s4_analysis.c
C
#include <stdio.h>
#include <stdlib.h>   /* abs() */

/* Returns INDEX (0-5) of most-rolled face */
int findMax(const int *freq, int n) {
    int maxIdx = 0;
    for (int i = 1; i < n; i++)
        if (*(freq + i) > *(freq + maxIdx)) maxIdx = i;
    return maxIdx;
}

/* Returns INDEX (0-5) of least-rolled face */
int findMin(const int *freq, int n) {
    int minIdx = 0;
    for (int i = 1; i < n; i++)
        if (*(freq + i) < *(freq + minIdx)) minIdx = i;
    return minIdx;
}

/* Average rolls per face via pointer walk */
float calcAverage(const int *freq, int n) {
    int sum = 0;
    const int *p = freq;
    const int *e = freq + n;
    while (p < e) sum += *p++;
    return (float)sum / n;
}

/* Total deviation from perfect fairness */
int calcFairness(const int *freq, int n) {
    float expected = calcAverage(freq, n);
    int deviation = 0;
    for (int i = 0; i < n; i++)
        deviation += abs(freq[i] - (int)expected);
    return deviation;
}

void rollDice(int *f, int r){#include <time.h>
    srand(time(NULL));
    for(int i=0;i<r;i++) f[rand()%6]++;
}

int main() {
    int freq[6] = {0};
    rollDice(freq, 600);

    int hot = findMax(freq, 6);
    int cold = findMin(freq, 6);

    printf("Rolls     : 600\n");
    printf("Average   : %.2f per face (perfect = 100.00)\n",
           calcAverage(freq, 6));
    printf("Hottest   : Face %d (%d times)\n", hot+1, freq[hot]);
    printf("Coldest   : Face %d (%d times)\n", cold+1, freq[cold]);
    printf("Deviation : %d (0 = perfectly fair)\n",
           calcFairness(freq, 6));
    return 0;
}
output
Rolls     : 600
Average   : 100.00 per face (perfect = 100.00)
Hottest   : Face 3 (112 times)
Coldest   : Face 5  (88 times)
Deviation : 54 (0 = perfectly fair)
Why const int *freq in every analysis function? These functions only read. The const keyword documents that intent and lets the compiler catch any accidental write. It also allows the caller to pass a const-qualified array without a warning. Always add const to pointer parameters that don't need to write.
step 5 โ€” ascii bar chart
S5
๐Ÿ“Š ASCII Bar Chart โ€” printChart() via Function Pointer
Draw bars with pointer walk โ€” plug in via function pointer for swappable display styles
Function Pointer
The bar chart function walks the frequency array with a pointer, printing one row per face. Bar length is computed proportionally: barLen = (freq[i] * MAX_BAR) / maxCount โ€” so the most-rolled face always gets a full bar and others scale accordingly. We then introduce a function pointer: void (*printFn)(const int*, int) can hold either the bar chart printer or a plain number printer โ€” the caller swaps behaviour by passing a different function.
s5_bar_chart.c
C
#include <stdio.h>

void printChart(const int *freq, int n) {
    /* find max via pointer walk */
    int maxVal = freq[0];
    for (const int *p = freq+1; p < freq+n; p++)
        if (*p > maxVal) maxVal = *p;

    printf("\n  Dice Roll Frequency  (n=%d total)\n",
           maxVal * n / maxVal);  /* placeholder โ€” real total in full proj */
    printf("  %-4s %5s  %-32s\n", "Face", "Count", "Distribution");
    printf("  -----------------------------------------\n");

    for (int i = 0; i < n; i++) {
        int bar = (freq[i] * 30) / maxVal;   /* scale to 30 cols */
        printf("  [%d]  %4d  |", i+1, freq[i]);
        for (int b = 0; b < bar; b++)  printf("#");
        if (freq[i] == maxVal) printf(" <-- HOT");
        printf("\n");
    }
    printf("  -----------------------------------------\n");
}

void printNumbers(const int *freq, int n) {
    printf("\n  Face  Count  Pct\n");
    int total = 0;
    for (int i=0;i<n;i++) total+=freq[i];
    for (int i = 0; i < n; i++)
        printf("   %d    %3d   %5.1f%%\n",
               i+1, freq[i], (float)freq[i]*100/total);
}

/* Function pointer โ€” swappable display */
void runDisplay(const int *freq, int n,
               void (*displayFn)(const int*, int)) {
    displayFn(freq, n);            /* call whichever fn was passed */
}

#include <stdlib.h>
#include <time.h>
int main() {
    int freq[6] = {0};
    srand(time(NULL));
    for(int i=0;i<120;i++) freq[rand()%6]++;

    printf("=== Bar Chart Mode ===\n");
    runDisplay(freq, 6, printChart);    /* pass bar function */

    printf("\n=== Numbers Mode ===\n");
    runDisplay(freq, 6, printNumbers);  /* swap to numbers โ€” same caller code */
    return 0;
}
output
=== Bar Chart Mode ===

  Dice Roll Frequency
  Face  Count  Distribution
  -----------------------------------------
  [1]    22  |###################### <-- HOT
  [2]    18  |##################
  [3]    20  |####################
  [4]    19  |###################
  [5]    21  |#####################
  [6]    20  |####################
  -----------------------------------------

=== Numbers Mode ===

  Face  Count  Pct
   1     22   18.3%
   2     18   15.0%
   3     20   16.7%
   4     19   15.8%
   5     21   17.5%
   6     20   16.7%
Function pointer swap in action: runDisplay(freq, 6, printChart) and runDisplay(freq, 6, printNumbers) call the same runDisplay body โ€” only the function stored in displayFn differs. This is the exact same mechanism used by qsort's comparator โ€” plug-in behaviour through a function pointer.
step 6 โ€” complete project
S6
๐Ÿ Complete Project โ€” All Six Steps Together
Full dice roll statistics program โ€” every concept connected in one clean file
Full Program
Everything from Steps 1โ€“5 unified into one complete, well-commented program. Rolls 300 times, analyses the frequency array with pointer-walking functions, and prints both the ASCII bar chart and the statistical summary. Every concept โ€” array, pointer, function, function pointer, pass by pointer โ€” appears in a real, purposeful role.
dice_statistics.c
C โ€” Complete Project
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define FACES   6
#define ROLLS   300
#define BAR_MAX 30

/* โ”€โ”€ 1. ROLL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
void rollDice(int *freq, int rolls) {
    for (int i = 0; i < rolls; i++)
        freq[rand() % FACES]++;   /* pointer โ€” modifies caller's array */
}

/* โ”€โ”€ 2. POINTER WALK โ€” total โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
int totalRolls(const int *freq, int n) {
    int s = 0;
    const int *p = freq, *e = freq + n;
    while (p < e) s += *p++;        /* walk โ€” read then advance */
    return s;
}

/* โ”€โ”€ 3. FIND MAX / MIN (return index) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
int findMax(const int *freq, int n) {
    int idx = 0;
    for (int i = 1; i < n; i++)
        if (*(freq+i) > *(freq+idx)) idx = i;
    return idx;
}
int findMin(const int *freq, int n) {
    int idx = 0;
    for (int i = 1; i < n; i++)
        if (*(freq+i) < *(freq+idx)) idx = i;
    return idx;
}

/* โ”€โ”€ 4. FAIRNESS โ€” deviation from expected โ”€โ”€โ”€โ”€โ”€ */
void printFairness(const int *freq, int n, int total) {
    float expected = (float)total / n;
    int totalDev = 0;
    printf("\n  %-6s %6s %8s %8s\n",
           "Face", "Count", "Expected", "Diff");
    printf("  --------------------------------\n");
    for (int i = 0; i < n; i++) {
        int diff = freq[i] - (int)expected;
        totalDev += diff < 0 ? -diff : diff;
        printf("   [%d]  %5d %8.1f %+8d\n",
               i+1, freq[i], expected, diff);
    }
    printf("  --------------------------------\n");
    printf("  Total deviation: %d  ", totalDev);
    printf("(%s)\n", totalDev < 20 ? "Very Fair!"
                     : totalDev < 40 ? "Fairly Normal"
                     :                  "Skewed Roll");
}

/* โ”€โ”€ 5. BAR CHART โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
void printChart(const int *freq, int n) {
    int maxVal = freq[findMax(freq, n)];
    printf("\n  Face  Count  Distribution (scaled)\n");
    printf("  ------------------------------------------\n");
    for (int i = 0; i < n; i++) {
        int bar = (freq[i] * BAR_MAX) / maxVal;
        printf("  [%d] %4d |", i+1, freq[i]);
        for (int b = 0; b < bar; b++) printf("#");
        if (freq[i] == maxVal) printf(" HOT");
        printf("\n");
    }
    printf("  ------------------------------------------\n");
}

/* โ”€โ”€ 6. SUMMARY VIA FUNCTION POINTER โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
void printSummary(const int *freq, int n) {
    int   total = totalRolls(freq, n);
    int   hot   = findMax(freq, n);
    int   cold  = findMin(freq, n);
    float avg   = (float)total / n;

    printf("\n  === SUMMARY ===\n");
    printf("  Total Rolls : %d\n",   total);
    printf("  Average/Face: %.2f\n", avg);
    printf("  Hottest Face: %d (%d times)\n",  hot+1,  freq[hot]);
    printf("  Coldest Face: %d (%d times)\n",  cold+1, freq[cold]);
}

/* โ”€โ”€ MAIN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
int main() {
    int freq[FACES] = {0};   /* THE array โ€” lives in main's stack */
    srand(time(NULL));

    printf("Rolling dice %d times...\n", ROLLS);
    rollDice(freq, ROLLS);    /* freq passes as pointer โ€” fills it */

    /* function pointer โ€” pluggable display */
    void (*display)(const int*, int) = printChart;
    display(freq, FACES);

    printFairness(freq, FACES, totalRolls(freq, FACES));
    printSummary(freq, FACES);

    /* swap display โ€” same call, different function */
    printf("\n--- Double the rolls, reanalyse ---\n");
    rollDice(freq, ROLLS);         /* accumulate 300 more */
    display = printChart;          /* still bar chart */
    display(freq, FACES);
    printSummary(freq, FACES);
    return 0;
}
output โ€” 300 rolls
Rolling dice 300 times...

  Face  Count  Distribution (scaled)
  ------------------------------------------
  [1]   53  |##############################  HOT
  [2]   47  |##########################
  [3]   51  |############################
  [4]   49  |###########################
  [5]   48  |###########################
  [6]   52  |#############################
  ------------------------------------------

  Face   Count Expected     Diff
  --------------------------------
   [1]     53     50.0       +3
   [2]     47     50.0       -3
   [3]     51     50.0       +1
   [4]     49     50.0       -1
   [5]     48     50.0       -2
   [6]     52     50.0       +2
  --------------------------------
  Total deviation: 12  (Very Fair!)

  === SUMMARY ===
  Total Rolls : 300
  Average/Face: 50.00
  Hottest Face: 1 (53 times)
  Coldest Face: 2 (47 times)

--- Double the rolls, reanalyse ---

  Face  Count  Distribution (scaled)
  ------------------------------------------
  [1]  106  |##############################  HOT
  [2]   97  |###########################
  [3]   99  |############################
  [4]   99  |############################
  [5]   98  |############################
  [6]  101  |#############################
  ------------------------------------------

  === SUMMARY ===
  Total Rolls : 600
  Average/Face: 100.00
  Hottest Face: 1 (106 times)
  Coldest Face: 2  (97 times)
checklist โ€” tick each concept when understood
  • S1 โ€” Array setup: int freq[6] = {0} โ€” six slots, all zeroed. Index 0 = face 1. srand(time(NULL)) seeds once before the loop. rand() % 6 gives 0โ€“5.
  • S2 โ€” Pass by pointer: rollDice(int *freq, int rolls) receives address of caller's array. freq[face]++ writes directly โ€” same as *(freq+face)++. No copy. Changes persist.
  • S3 โ€” Pointer walk: int *p = freq; while(p < freq+6) sum += *p++. Post-increment reads then advances. freq[i] and *(freq+i) are identical โ€” proven by output.
  • S4 โ€” Analysis functions: All take const int *freq โ€” read-only, no copy. findMax / findMin return index via pointer arithmetic. calcFairness measures deviation from expected count.
  • S5 โ€” Function pointer: void (*displayFn)(const int*, int) stores address of a function. Assign = printChart or = printNumbers. Call same way: displayFn(freq, 6). Swap behaviour without changing the call.
  • S6 โ€” Full program: All concepts together โ€” one array, five functions, one function pointer, pointer walks for analysis, two-pass accumulation. rollDice called twice accumulates in the same freq because pointer reaches original.