This exemplar WPF application demonstrates a two-player snooker game with realistic physics simulation. It showcases fundamental programming concepts required for AQA 7517 A-Level Computer Science NEA projects, including object-oriented programming, complex mathematical models, and game loop architecture.
Note on Authorship: This entire project, including all code, documentation, and this README, was generated by Claude AI initially with documentation using Github Copilot. It was created as an educational resource for A-Level Computer Science teachers and students. It is intended to demonstrate programming concepts and techniques, not to serve as a template for student NEA projects.
This project must NOT be submitted as your own work OR copied from for your AQA 7517 NEA coursework.
This exemplar is designed to help you:
- Understand how complex game projects are structured
- Learn physics simulation techniques
- See how OOP principles are applied in practice
- Recognise patterns you might use in your own original project
Your NEA must be your own original work, addressing a problem you have identified and designed yourself.
SnookerGame/
├── Models/
│ ├── Vector2D.cs - 2D vector mathematics class
│ ├── Ball.cs - Abstract base class for all balls
│ ├── CueBall.cs - Player-controlled white ball
│ ├── ColouredBall.cs - Red and coloured balls (21 total)
│ ├── Table.cs - Snooker table with pockets and cushions
│ ├── Pocket.cs - Pocket detection and positioning
│ └── Player.cs - Player scoring and statistics
├── Engine/
│ ├── PhysicsEngine.cs - Physics simulation (movement, collisions, friction)
│ └── GameManager.cs - Game rules, turns, and state management
├── Views/
│ ├── MainWindow.xaml - User interface layout (XAML markup)
│ └── MainWindow.xaml.cs - Code-behind (input handling, rendering)
└── README.md - This documentation file
This exemplar demonstrates specific skills from the AQA specification (Section 4.14.3.4.1) that are commonly needed in game and simulation projects.
| Skill | Where Demonstrated | Explanation |
|---|---|---|
| Complex user-defined OOP | Ball.cs, CueBall.cs, ColouredBall.cs |
Inheritance hierarchy with abstract base class, virtual methods, and polymorphism |
| Complex mathematical model | Vector2D.cs, PhysicsEngine.cs |
Trigonometry for aiming, elastic collision equations, vector mathematics |
| Recursive/complex algorithms | PhysicsEngine.CheckBallCollisions() |
Iterative collision resolution handling chain reactions |
| Complex data structures | GameManager.cs, Table.cs |
Collections managing game objects, state machines |
| Skill | Where Demonstrated | Explanation |
|---|---|---|
| Simple user-defined OOP | Player.cs, Pocket.cs |
Encapsulated classes with properties and methods |
| User-defined methods | Throughout all classes | Parameterised methods with return values |
| Selection statements | GameManager.CheckForFouls() |
Complex conditional logic for rule enforcement |
| Iteration | PhysicsEngine.Update() |
Loops processing all balls each frame |
| Data validation | Table.IsInD(), CueBall.PlaceBall() |
Validating ball placement positions |
| String handling | Player.GetStatusString() |
Formatting score displays |
The snooker game naturally maps to objects:
-
Balls are objects - Each ball has position, velocity, colour, and behaviours (move, collide, pot). The abstract
Ballclass captures common properties whilstCueBallandColouredBalladd specific behaviours. -
Inheritance reduces duplication - Rather than repeating physics code for each ball type, the base class handles movement and collision detection. Subclasses only implement what's different.
-
Encapsulation protects state - Ball velocities are modified only through controlled methods (
SetVelocity,ApplyFriction), preventing invalid states. -
Polymorphism enables flexibility - The physics engine processes a
List<Ball>without knowing whether each is a cue ball or coloured ball.
Mathematical operations are encapsulated in Vector2D for several reasons:
// Without Vector2D (scattered, error-prone):
double newPosX = posX + velX * deltaTime;
double newPosY = posY + velY * deltaTime;
double distance = Math.Sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
// With Vector2D (clean, reusable):
position = position.Add(velocity.Multiply(deltaTime));
double distance = position.DistanceTo(other.Position);Benefits:
- Reusability - Vector operations used throughout physics, rendering, input
- Readability - Code reads like mathematical notation
- Testability - Vector maths can be unit tested in isolation
- Operator overloading - Natural syntax:
position = position + velocity * dt
The Ball class is abstract because:
- Prevents invalid instantiation - You cannot create a generic "Ball"; every ball must be specifically a cue ball or coloured ball
- Enforces implementation - Subclasses must implement
Draw()andReset()methods - Shares common code - Physics calculations (position update, friction, collision detection) are written once
public abstract class Ball
{
// Shared implementation
public virtual void Update(double deltaTime) { ... }
public void ApplyFriction(double coefficient, double deltaTime) { ... }
// Must be implemented by subclasses
public abstract void Draw(Canvas canvas);
public abstract void Reset();
}Separation of concerns - Each class has a single responsibility:
| Class | Responsibility |
|---|---|
PhysicsEngine |
"How do balls move?" - Position updates, collisions, friction |
GameManager |
"What are the rules?" - Turns, scoring, fouls, frame management |
This separation means:
- Physics can be tested without game rules
- Rules can be modified without touching physics
- Code is easier to understand and maintain
The game uses a DispatcherTimer to create a consistent update cycle:
private void GameLoop_Tick(object sender, EventArgs e)
{
// 1. Calculate time since last frame
double deltaTime = (currentTime - lastUpdateTime).TotalSeconds;
// 2. Update game state
if (ballsMoving)
{
UpdatePhysics(deltaTime);
}
// 3. Handle input
if (isAiming)
{
UpdateAiming();
}
// 4. Render
Render();
}Why delta time? Frame rates can vary. Multiplying by deltaTime ensures consistent movement regardless of performance:
- At 60 FPS:
deltaTime ≈ 0.0167, ball moves 1.67 units per frame - At 30 FPS:
deltaTime ≈ 0.0333, ball moves 3.33 units per frame - Result: Same 100 units per second in both cases
// Basic motion equation: position = position + velocity × time
position = position.Add(velocity.Multiply(deltaTime));// Exponential decay for realistic deceleration
double frictionMultiplier = Math.Pow(frictionCoefficient, deltaTime * 60);
velocity = velocity.Multiply(frictionMultiplier);When two balls of equal mass collide, momentum and energy are conserved:
// Calculate collision normal (direction between centres)
Vector2D collisionNormal = (pos1 - pos2).Normalised;
// Calculate relative velocity along normal
double velocityAlongNormal = relativeVelocity.DotProduct(collisionNormal);
// Calculate impulse
double impulse = -(1 + restitution) * velocityAlongNormal / 2;
// Apply impulse to both balls
ball1.Velocity += collisionNormal * impulse;
ball2.Velocity -= collisionNormal * impulse;WPF separates appearance (XAML) from behaviour (C#):
<!-- MainWindow.xaml - Declarative UI definition -->
<Canvas x:Name="gameCanvas"
MouseMove="GameCanvas_MouseMove"
MouseLeftButtonDown="GameCanvas_MouseLeftButtonDown"/>// MainWindow.xaml.cs - Event handling logic
private void GameCanvas_MouseMove(object sender, MouseEventArgs e)
{
Point pos = e.GetPosition(gameCanvas);
mousePosition.Set(pos.X, pos.Y);
}| Container | Purpose | Used For |
|---|---|---|
DockPanel |
Dock elements to edges | Main layout (top panel, bottom panel, centre canvas) |
Grid |
Row/column layout | Score panels with multiple columns |
StackPanel |
Linear stacking | Grouping labels vertically |
Canvas |
Absolute positioning | Game rendering (balls, table) |
All game graphics are drawn programmatically on a WPF Canvas:
// Create a shape
Ellipse ballVisual = new Ellipse
{
Width = radius * 2,
Height = radius * 2,
Fill = new SolidColorBrush(Colors.Red)
};
// Position it (Canvas uses top-left corner)
Canvas.SetLeft(ballVisual, position.X - radius);
Canvas.SetTop(ballVisual, position.Y - radius);
// Add to canvas
canvas.Children.Add(ballVisual);The game uses a state machine to manage game flow:
┌─────────────────┐
│ PlacingCueBall │ ←─────────────────────────────┐
└────────┬────────┘ │
│ Click in D │
▼ │
┌─────────────────┐ │
│ Aiming │ ←──────────────┐ │
└────────┬────────┘ │ │
│ Release shot │ │
▼ │ │
┌─────────────────┐ │ │
│ BallsMoving │ │ │
└────────┬────────┘ │ │
│ All balls stopped │ │
▼ │ │
┌─────────────────┐ │ │
│ ProcessingShot │ │ │
└────────┬────────┘ │ │
│ │ │
┌────┴────┐ │ │
▼ ▼ │ │
Legal pot Foul/Miss │ │
│ │ │ │
│ Switch player │ │
│ │ │ │
└────┬────┘ │ │
│ │ │
Cue ball ──── No ──────────────┘ │
potted? │
│ │
Yes ──────────────────────────────────────┘
| Ball | Points | Quantity |
|---|---|---|
| Red | 1 | 15 |
| Yellow | 2 | 1 |
| Green | 3 | 1 |
| Brown | 4 | 1 |
| Blue | 5 | 1 |
| Pink | 6 | 1 |
| Black | 7 | 1 |
- Player must pot a red ball
- If successful, player chooses and pots any colour
- Colour is respotted, player returns to step 1
- When all reds are potted, colours are potted in order (yellow → black)
The game detects these fouls:
- Potting the cue ball
- Failing to hit any ball
- Hitting wrong ball first (e.g., hitting colour when red is required)
- Potting wrong ball
Foul penalty: Minimum 4 points or the value of the ball involved (whichever is higher)
Custom 2D vector class providing mathematical operations essential for physics simulation. Includes vector addition, subtraction, scalar multiplication, dot product, magnitude calculation, normalisation, and angle calculations.
Key methods:
Add(),Subtract(),Multiply()- Basic vector arithmeticDotProduct()- Used in collision calculationsDistanceTo()- Collision detectionAngleTo()- Aiming calculationsFromAngle()- Converting shot power/angle to velocity
Base class for all balls, containing shared physics properties and behaviours. Marked as abstract to prevent direct instantiation.
Key features:
- Position and velocity as
Vector2D Update()method for position changesApplyFriction()for deceleration- Abstract
Draw()andReset()methods
Represents the white ball that players control. Extends Ball with aiming and shooting mechanics.
Key features:
AimAngleandShotPowerfor shot setupStrike()converts angle/power to velocityIsInHandstate for ball-in-hand situations- Visual aim line rendering
Represents all 21 coloured balls (15 reds + 6 colours). Extends Ball with scoring and respotting.
Key features:
BallTypeenum for identificationPointValuefor scoringRespot()for returning colours to their spotsOriginalPositionstored for respotting
Represents the snooker table including playing surface, cushions, pockets, and markings.
Key features:
- Official spot positions calculated proportionally
GetRedBallPositions()generates triangle formationIsInD()validates cue ball placementDraw()renders table with all markings
Represents one of six pockets with detection logic.
Key features:
ContainsBall()checks if ball is potted- Corner vs Middle pocket types (different sizes)
- Position enumeration for identification
Tracks player statistics and scoring.
Key features:
- Score, current break, highest break tracking
AddPoints()for legal potsAddFoulPoints()for opponent foulsEndBreak()records break and resets
Handles all physics calculations separate from game rules.
Key features:
Update()main physics loopApplyFriction()exponential decay modelDetectCollision()circle-circle detectionResolveCollision()elastic collision responseCheckPottedBalls()pocket detection
Controls game flow and enforces snooker rules.
Key features:
GameStateenum for state machineTargetBallTypetracking (red/colour/specific)CheckForFouls()comprehensive foul detectionProcessShot()evaluates shot resultsRespotColour()handles colour respotting
Declarative UI layout using WPF XAML.
Key features:
DockPanelmain layout- Score display panels for both players
Canvasfor game rendering- Power bar visualisation
- Mouse event bindings
Code-behind handling input, game loop, and rendering.
Key features:
DispatcherTimergame loop at 60 FPS- Mouse input for aiming and shooting
Render()draws all game elementsUpdateUI()syncs display with game state
| Concept | Implementation | Purpose |
|---|---|---|
| Inheritance | Ball → CueBall, ColouredBall |
Code reuse, polymorphism |
| Abstraction | abstract class Ball |
Define interface, prevent invalid instantiation |
| Encapsulation | Private fields, public properties | Protect internal state |
| Polymorphism | List<Ball> processing |
Handle different ball types uniformly |
| Composition | GameManager contains Player[] |
Build complex objects from simpler ones |
| State Machine | GameState enum |
Manage game flow |
| Delta Time | Frame-independent physics | Consistent simulation speed |
| Vector Mathematics | Custom Vector2D class |
Clean physics calculations |
| Event-Driven | WPF mouse events | Responsive user input |
| Separation of Concerns | Physics vs Rules vs UI | Maintainable code structure |
- Visual Studio 2019 or later
- .NET Framework 4.7.2 or later (or .NET 6+)
- Windows OS (WPF is Windows-only)
- Open
SnookerGame.slnin Visual Studio - Build the solution (Ctrl+Shift+B)
- Run the project (F5)
- Mouse movement - Aim the cue
- Left mouse button (hold) - Charge shot power
- Left mouse button (release) - Take shot
- Click in D - Place cue ball when in hand
If you were creating your own snooker/pool game (not copying this one), you might consider:
- AI opponent using pathfinding or decision trees
- Spin/English on the cue ball
- Sound effects and animations
- Network multiplayer
- Tournament mode with brackets
- Replay system recording shots
- Practice mode with ball placement
Remember: These would need to be YOUR implementation, designed and coded by YOU.
This exemplar demonstrates professional game development techniques including:
- Object-oriented design with inheritance hierarchies
- Real-time physics simulation
- State machine game management
- Event-driven user interfaces
However, your NEA project must be:
- Your own idea - addressing a problem you've identified
- Your own design - based on your analysis and planning
- Your own code - implementing concepts in your own way
- Your own work - demonstrating your problem-solving ability
Use this exemplar to learn concepts, not to copy solutions.
For Teachers: This exemplar can be used to demonstrate concepts in lessons, but students should not use it as a starting point for their NEA. Encourage students to identify their own problems and design their own solutions.
For Students: Learn from this exemplar, understand the concepts, practise the techniques - but then put it aside and create your own original work. Your NEA should reflect YOUR problem-solving skills, not your ability to modify existing code.
This project and documentation were generated by Claude AI (Anthropic) for educational purposes only.
