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:
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?
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?
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 value27917
: How many times this sum appeared0.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
Verify Small Cases First
- Run with 10 tosses and manually verify
- Print intermediate values
- Check each component separately
Check Your Math
- Is your range correct? (Min/max possible sums)
- Are you counting correctly?
- Is your probability calculation right?
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!