Problem Decomposition Guide: DiceyLab

Problem Decomposition Guide: DiceyLab

1. Understanding the Problem Domain

What Are We Building?

Before diving into code, understand the real-world scenario:

Questions to Ask Yourself:

  • What happens when you roll physical dice?
  • Why would we want to simulate this thousands of times?
  • What patterns might emerge from many rolls?
  • How do casinos or game designers use this information?

Exercise: Roll two physical dice 20 times and track the sums. What do you notice about which numbers appear most/least often? Why might this happen?

2. Identifying the Core Components

The Three-Class Architecture

This lab explicitly tells you to create three classes. But why these three?

Think About Separation of Concerns:

  1. Dice Class: Represents the physical dice

    • What does a die DO?
    • What information does it need to maintain?
    • How is rolling 2 dice different from rolling 5 dice?
  2. Bins Class: Collects and organizes results

    • What is a “bin” in this context?
    • Why do we need a separate class for this?
    • How is this different from just using an array?
  3. Simulation Class: Orchestrates the experiment

    • What does it coordinate?
    • Why separate this from the other classes?
    • What would happen if we put everything in one class?

Exercise: Draw three boxes representing these classes. Draw arrows showing which classes need to communicate with each other. What information flows along these arrows?

3. Breaking Down Each Component

Dice Class: The Random Number Generator

Key Questions:

  • How many dice are we simulating?
  • What values can each die show?
  • Do we need to know individual die values or just the sum?

Design Decisions to Consider:

  • Should tossAndSum() return the same value if called twice?
  • Do you need to store the last roll?
  • How do you ensure true randomness?

Edge Cases to Think About:

  • What if someone creates Dice with 0 dice?
  • What if someone creates Dice with 100 dice?
  • Are these valid? How should you handle them?

Bins Class: The Data Collector

Understanding “Bins”: Think of bins like labeled buckets:

  • If you roll 2 dice, possible sums are 2-12
  • You need 11 buckets labeled 2, 3, 4, …, 12
  • Each time you get a sum, drop a token in that bucket

Key Design Questions:

  • How do you map from a sum to its bin?
  • What if someone asks for a bin that doesn’t exist?
  • How do you initialize all bins to zero?

Data Structure Considerations:

  • Array? List? Map? What are the trade-offs?
  • How does your choice affect getBin() and incrementBin()?
  • Do bin numbers match array indices?

Simulation Class: The Experiment Runner

The Simulation Flow:

Initialize → Run many trials → Collect results → Format output

Planning Questions:

  • When do you create the Dice object?
  • When do you create the Bins object?
  • How do these objects interact during simulation?
  • Who is responsible for formatting the output?

4. The Output: Understanding the Histogram

Decoding the Sample Output

2 : 27917: 0.03 **

What each part means:

  • 2: The sum value
  • 27917: How many times this sum appeared
  • 0.03: The probability (27917/1000000)
  • **: Visual representation

Questions to Consider:

  • How do you calculate the probability?
  • How do you determine how many stars to print?
  • Why do 7s appear most often with 2 dice?

5. Incremental Development Strategy

Phase 1: Basic Dice

Goal: Create and test a single die roll
- Implement basic Dice class
- Test that it returns values 1-6
- No sums yet, just single die

Phase 2: Multiple Dice

Goal: Handle N dice and return sums
- Modify Dice to handle multiple dice
- Implement tossAndSum()
- Test with 2, 3, 5 dice
- Verify sum ranges are correct

Phase 3: Basic Bins

Goal: Track counts manually
- Create Bins class with constructor
- Implement incrementBin()
- Implement getBin()
- Test with hardcoded values

Phase 4: Simple Integration

Goal: Connect Dice and Bins
- Roll dice 100 times
- Track results in bins
- Print simple counts (no formatting)

Phase 5: Full Simulation

Goal: Complete the requirements
- Add probability calculations
- Format output with stars
- Run with 1,000,000 tosses

6. Testing Strategies

Unit Testing Each Component

Dice Class Tests:

  • Test single die stays in range 1-6
  • Test N dice sum is between N and 6*N
  • Test randomness (no constant values)

Bins Class Tests:

  • Test increment increases count by 1
  • Test new bins start at 0
  • Test getting bin values after increments

Think About:

  • How do you test randomness?
  • What makes a good test vs. a bad test?
  • Why test before integration?

Integration Testing

After individual components work:

  • Test 10 rolls recording correctly
  • Test 100 rolls showing expected distribution
  • Compare results with theoretical probability

7. Common Pitfalls and How to Avoid Them

Off-By-One Errors

Problem: Bins don’t match sums correctly Prevention: Draw out the mapping on paper first

Random Number Issues

Problem: Getting values outside expected range Prevention: Understand your random number generator’s bounds

Integer Division

Problem: Probability always shows 0.00 Prevention: Remember integer division truncates!

Performance Problems

Problem: Million iterations take forever Prevention: Avoid unnecessary object creation in loops

8. Thinking Exercises

Exercise 1: Probability Prediction

Before running your simulation:

  • Calculate the theoretical probability of rolling a 7 with 2 dice
  • Predict which sum will be most common with 3 dice
  • Draw the expected shape of the histogram

Exercise 2: Design Alternatives

What if you had to design this differently?

  • What if you couldn’t use classes?
  • What if you needed to track individual die values?
  • What if you wanted to simulate weighted dice?

Exercise 3: Extension Planning

How would you modify your design to:

  • Support different-sided dice (D20, D12, etc.)?
  • Track sequences (how often do you roll three 7s in a row)?
  • Compare experimental vs. theoretical probabilities?

9. Debugging Strategies

When Results Look Wrong

  1. Verify Small Cases First

    • Run with 10 tosses and manually verify
    • Print intermediate values
    • Check each component separately
  2. Check Your Math

    • Is your range correct? (Min/max possible sums)
    • Are you counting correctly?
    • Is your probability calculation right?
  3. Validate Randomness

    • Are you getting variety in results?
    • Do multiple runs give different distributions?
    • Is your random seed changing?

10. Connecting to Bigger Concepts

Why This Design Matters

Object-Oriented Principles:

  • Each class has a single responsibility
  • Classes hide their implementation details
  • Objects collaborate to solve complex problems

Data Analysis Concepts:

  • Empirical vs. theoretical probability
  • Law of large numbers
  • Statistical distributions

Software Engineering Practices:

  • Test-driven development
  • Incremental delivery
  • Separation of concerns

Final Thoughts: The Power of Simulation

This project demonstrates how computers can help us understand probability through experimentation. Instead of calculating complex formulas, we can simulate millions of trials and observe the patterns that emerge.

Remember: The goal isn’t just to make the tests pass—it’s to understand how breaking down problems into logical components makes complex tasks manageable!