Final Project Documentation

SonarGame Documentation

Overview

SonarGame is an interactive browser-based game that uses a sonar sensor connected to an Arduino to control gameplay. The game features two modes – Submarine and Exploration – where players navigate through underwater environments by physically moving their hand or an object in front of the sonar sensor.

System Requirements

  • Computer with a modern web browser (Chrome recommended for WebSerial API)
  • Arduino board (Uno or similar)
  • HC-SR04 ultrasonic sensor
  • USB cable to connect Arduino to computer
  • Basic electronic components (jumper wires, optional breadboard)

Installation

Web Application Setup

  1. Download the game files to your local machine:
    • index.html
    • style.css
    • sketch.js (main game file)
    • webSerial.js (for Arduino communication)
  2. Set up the file structure:
sonar-game/
├── index.html
├── style.css
├── js/
│   ├── sketch.js
│   └── webSerial.js

Arduino Setup

  1. Connect the HC-SR04 ultrasonic sensor to your Arduino:
    • VCC to 5V on Arduino
    • GND to GND on Arduino
    • TRIG to pin 9 on Arduino
    • ECHO to pin 10 on Arduino
  2. Upload the Arduino code (see Arduino Code section)

Game Setup

HTML Structure

Create an index.html file with the following structure:

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sonar Game</title>
    <link rel="stylesheet" href="style.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
</head>
<body>
    <div id="gameContainer">
        <div id="gameCanvas"></div>
        
        <div id="controls">
            <div id="modeSelector">
                <h2>Select Game Mode</h2>
                <button id="submarineMode">Submarine Mode</button>
                <button id="explorationMode">Exploration Mode</button>
            </div>
            
            <div id="instructions" style="display: none;">
                <p id="gameInstructions"></p>
                <button onclick="document.getElementById('instructions').style.display='none';document.getElementById('modeSelector').style.display='flex';">Back to Mode Selection</button>
            </div>
            
            <div id="gameInfo">
                <div>Score: <span id="scoreDisplay">0</span></div>
                <div>Level: <span id="levelDisplay">1</span></div>
                <div>Lives: <span id="livesDisplay">3</span></div>
            </div>
            
            <div id="sonarControls">
                <div id="sonarSimulator">
                    <label for="sonarRange">Simulate Sonar (0-400cm): <span id="sonarValue">200</span></label>
                    <input type="range" id="sonarRange" min="0" max="400" value="200">
                </div>
                
                <div id="arduinoControls">
                    <button id="toggleSimulator">Toggle Simulator</button>
                    <button id="connectArduino">Connect Arduino</button>
                    <button id="disconnectArduino">Disconnect Arduino</button>
                </div>
            </div>
        </div>
    </div>

    <script src="js/webSerial.js"></script>
    <script src="js/sketch.js"></script>
</body>
</html>

CSS Styling

Create a style.css file:

css
body {
    margin: 0;
    padding: 0;
    font-family: Arial, sans-serif;
    background-color: #111;
    color: #0f0;
}

#gameContainer {
    display: flex;
    flex-direction: column;
    height: 100vh;
}

#gameCanvas {
    flex-grow: 1;
}

#controls {
    height: 60px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 20px;
    background-color: #222;
}

#modeSelector {
    display: flex;
    flex-direction: column;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: rgba(0, 0, 0, 0.8);
    padding: 20px;
    border-radius: 10px;
    text-align: center;
}

#instructions {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: rgba(0, 0, 0, 0.8);
    padding: 20px;
    border-radius: 10px;
    text-align: center;
}

button {
    background-color: #0f0;
    color: #000;
    border: none;
    padding: 8px 16px;
    margin: 5px;
    border-radius: 5px;
    cursor: pointer;
}

button:hover {
    background-color: #0c0;
}

#gameInfo {
    display: flex;
    gap: 20px;
}

#sonarControls {
    display: flex;
    align-items: center;
}

#sonarSimulator {
    margin-right: 20px;
}

input[type="range"] {
    width: 200px;
    margin: 0 10px;
}

Game Mechanics

Game Modes

  1. Submarine Mode:
    • Navigate a submarine through underwater caves
    • Avoid obstacles like rocks, mines, and enemy submarines
    • Collect power-ups for extra lives and points
  2. Exploration Mode:
    • Explore ocean depths with diving gear
    • Avoid sharks, jellyfish, and trash
    • Collect treasures and discover sea creatures

Controls

The game is controlled using a sonar sensor. The distance measured by the sonar determines the vertical position of the player:

  • Closer to the sensor = higher position on screen
  • Further from sensor = lower position on screen

Scoring System

  • Each obstacle successfully avoided: +1 point
  • Power-up collection: +10 points
  • Level increases every 20 points
  • Starting with 3 lives
  • Game over when lives reach 0

Game Elements

  1. Obstacles:
    • Submarine mode: rocks, mines, enemy submarines
    • Exploration mode: sharks, jellyfish, trash
    • Colliding with obstacles reduces lives
  2. Power-ups:
    • Extra life: Restores 1 life
    • Extra points: Adds 10 points
  3. Visual effects:
    • Sonar waves: Visually represent sonar activity
    • Particles: Created on collisions and power-up collection

P5JS

 

 

Arduino Integration

Arduino Code

cpp
// SonarGame Arduino Code
// Connects HC-SR04 ultrasonic sensor to send distance readings to browser

// Pin definitions
const int trigPin = 9;  // Trigger pin of the HC-SR04
const int echoPin = 10; // Echo pin of the HC-SR04

// Variables
long duration;
int distance;

void setup() {
  // Initialize Serial communication
  Serial.begin(9600);
  
  // Configure pins
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
}

void loop() {
  // Clear the trigger pin
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  
  // Send a 10μs pulse to trigger
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  
  // Read the echo pin, duration in microseconds
  duration = pulseIn(echoPin, HIGH);
  
  // Calculate distance in centimeters
  // Speed of sound is 343m/s = 34300cm/s
  // Duration is time for sound to travel to object and back
  // So distance = (duration * 34300) / 2 / 1000000
  distance = duration * 0.034 / 2;
  
  // Limit range to 0-400cm to match game expectations
  if (distance > 400) distance = 400;
  if (distance < 0) distance = 0;
  
  // Send the distance to the Serial port
  Serial.println(distance);
  
  // Small delay before next reading
  delay(50);
}

WebSerial Integration

The game uses the WebSerial API to communicate with the Arduino. The webSerial.js file should handle:

  1. Requesting port access from the browser
  2. Opening serial connection
  3. Reading and parsing serial data
  4. Handling connection errors

Here’s a sample implementation:

javascript
// webSerial.js - Handles communication with Arduino via WebSerial API

let port;
let serialActive = false;
let reader;
let readableStreamClosed;

async function setUpSerial() {
  if ('serial' in navigator) {
    try {
      // Request port access
      port = await navigator.serial.requestPort();
      
      // Open the port with appropriate settings
      await port.open({ baudRate: 9600 });
      
      serialActive = true;
      
      // Set up reading from the port
      const decoder = new TextDecoder();
      const readableStreamClosed = port.readable.pipeTo(new WritableStream({
        write(chunk) {
          // Process each chunk of data
          let string = decoder.decode(chunk);
          
          // Call the readSerial function from sketch.js
          if (typeof readSerial === 'function') {
            readSerial(string);
          }
        }
      }));
      
      console.log("Serial connection established successfully");
    } catch (error) {
      console.error("Error opening serial port:", error);
    }
  } else {
    console.error("WebSerial API not supported in this browser");
    alert("WebSerial is not supported in this browser. Please use Chrome or Edge.");
  }
}

Customization

Adding New Obstacles

To add new obstacles, modify the createObstacle() function in sketch.js:

javascript
function createObstacle() {
  // Increase the range for obstacle types
  let obstacleType;
  
  if (gameMode === "submarine") {
    obstacleType = floor(random(4)); // Increased from 3 to 4
  } else {
    obstacleType = floor(random(4)) + 4; // Adjusted accordingly
  }
  
  let obstacle = {
    x: width + 50,
    y: random(50, height - 50),
    width: random(30, 80),
    height: random(20, 60),
    speed: 2 + level * 0.5,
    type: obstacleType,
  };
  
  obstacles.push(obstacle);
}

// Then update the drawObstacles() function to handle the new type
function drawObstacles() {
  for (let obstacle of obstacles) {
    // ... existing code ...
    
    if (gameMode === "submarine") {
      // ... existing obstacle types ...
      else if (obstacle.type === 3) {
        // New obstacle type
        // Add drawing code here
      }
    } else {
      // ... existing code ...
      else if (obstacle.type === 7) {
        // New obstacle type for exploration mode
        // Add drawing code here
      }
    }
  }
}

Adjusting Difficulty

To adjust game difficulty, modify these variables:

  1. Initial values in the resetGame() function:
    javascript
    function resetGame() {
      score = 0;
      level = 1;
      lives = 5; // Increased from 3 for easier gameplay
      // ...
    }
  2. Obstacle generation rate in the updateGame() function:
    javascript
    if (millis() - lastObstacleTime > 2500 - level * 75) { // Adjusted timing
      createObstacle();
      lastObstacleTime = millis();
    }
  3. Obstacle speed in the createObstacle() function:
    javascript
    let obstacle = {
      // ...
      speed: 1.5 + level * 0.3, // Slower progression
      // ...
    };

Troubleshooting

Game Performance Issues

If the game is running slowly:

  1. Reduce the number of background elements
  2. Decrease particle effects
  3. Lower the frequency of sonar waves
javascript
function createBackgroundElements() {
  const numElements = 30; // Reduced from 50
  // ...
}

function createParticles(x, y, count, particleColor) {
  count = Math.floor(count / 2); // Half the number of particles
  // ...
}

Arduino Connection Problems

  1. Cannot connect to Arduino:
    • Ensure Arduino is connected via USB
    • Verify correct driver installation
    • Try using a different USB port
    • Restart browser and try again
  2. Erratic sonar readings:
    • Check sensor wiring connections
    • Ensure stable power supply
    • Add smoothing to the readings:
javascript
// In Arduino code, add smoothing:
const int numReadings = 5;
int readings[numReadings];
int readIndex = 0;
int total = 0;
int average = 0;

void setup() {
  // Initialize all the readings to 0
  for (int i = 0; i < numReadings; i++) {
    readings[i] = 0;
  }
  // ... existing setup code ...
}

void loop() {
  // ... existing sonar reading code ...
  
  // Subtract the last reading
  total = total - readings[readIndex];
  // Read from sensor
  readings[readIndex] = distance;
  // Add the reading to the total
  total = total + readings[readIndex];
  // Advance to the next position in the array
  readIndex = (readIndex + 1) % numReadings;
  // Calculate the average
  average = total / numReadings;
  
  // Send the smoothed value
  Serial.println(average);
  
  // ... delay code ...
}
  1. Browser compatibility:
    • WebSerial API is only supported in Chromium-based browsers (Chrome, Edge)
    • Ensure your browser is up to date