Sound update

Dependencies:   4DGL-uLCD-SE Physac-MBED PinDetect SDFileSystem mbed-rtos mbed

Files at this revision

API Documentation at this revision

Comitter:
jstephens78
Date:
Thu Dec 01 01:43:39 2022 +0000
Parent:
16:6cf744b2623a
Child:
18:cf74968078ea
Commit message:
Cleaned up air hockey gameplay logic and put uLCD accesses behind mutex locks.

Changed in this revision

hockey/hockey.cpp Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
--- a/hockey/hockey.cpp	Wed Nov 30 21:43:44 2022 +0000
+++ b/hockey/hockey.cpp	Thu Dec 01 01:43:39 2022 +0000
@@ -16,6 +16,15 @@
 #define HOCKEY_PADDLE_W 8
 #define HOCKEY_PADDLE_H 24
 
+// Physics sim properties.
+//  HOCKEY_PHYSICS_STEPS    number of physics ticks between frames
+//  HOCKEY_PHYSICS_DELTA    how many ms the physics engine believes pass per
+//                          update. This is arbitrary. Pick what behaves well.
+//  HOCKEY_PUCK_VELOCITY    how fact the puck moves in one physics tick
+#define HOCKEY_PHYSICS_STEPS 4
+#define HOCKEY_PHYSICS_DELTA 1.66
+#define HOCKEY_PUCK_VELOCITY (5.0f / HOCKEY_PHYSICS_STEPS)
+
 // Controls the dimensions of the arena. You can adjust the screen margin above
 // and below the court, and the width of the goal.
 #define HOCKEY_ARENA_TOP 16
@@ -24,43 +33,57 @@
 
 // Properties derived from other macros
 #define _HOCKEY_ARENA_H (HOCKEY_ARENA_BOT - HOCKEY_ARENA_TOP)
+#define _HOCKEY_ARENA_MID ((HOCKEY_ARENA_BOT + HOCKEY_ARENA_TOP)/2)
 #define _HOCKEY_GOAL_TOP ((_HOCKEY_ARENA_H - HOCKEY_GOAL_HEIGHT)/2 + HOCKEY_ARENA_TOP)
 #define _HOCKEY_GOAL_BOT (_HOCKEY_GOAL_TOP + HOCKEY_GOAL_HEIGHT)
 
-int blue_score = 0;
-int red_score = 0;
+
 
 PhysicsBody puck;
 PhysicsBody paddle_a;
 PhysicsBody paddle_b;
 
-float input_sensitivity = 0.01f;
+int blue_score;
+int red_score;
+bool serve_direction;   // 0 means toward blue, 1 means red
+float input_sensitivity = 4.00f;
 
+// Helper functions
+// @{
 int maxOf(int a, int b)
 {
     return (a > b) ? a : b;
 }
-
 int minOf(int a, int b)
 {
     return (a < b) ? a : b;
 }
+int clamp(int a, int low, int upp)
+{
+    return minOf(upp, maxOf(a, low));
+}
+// @}
 
+/**
+ * Sets up physics elements within the Physac library
+ */
 void initPhysicsObjects()
 {
+    SetPhysicsTimeStep(HOCKEY_PHYSICS_DELTA);
+
     puck = CreatePhysicsBodyCircle((Vector2) {
         SCREEN_WIDTH/2, SCREEN_HEIGHT/2
     }, HOCKEY_PUCK_RADIUS, 1);
     puck->enabled = true;
     puck->useGravity = false;
     puck->restitution = 1.0;
-    puck->dynamicFriction = 0.0;
+    puck->dynamicFriction = 0;
     puck->velocity = (Vector2) {
-        5, 0
+        HOCKEY_PUCK_VELOCITY, 0
     };
 
     paddle_a = CreatePhysicsBodyRectangle((Vector2) {
-        32, 64
+        32, _HOCKEY_ARENA_MID
     }, HOCKEY_PADDLE_W, HOCKEY_PADDLE_H, 100);
     paddle_a->enabled = false; // Disable body state to convert it to static (no dynamics, but collisions)
     paddle_a->useGravity = false;
@@ -68,7 +91,7 @@
     SetPhysicsBodyRotation(paddle_a, 3.14159 / 6);
 
     paddle_b = CreatePhysicsBodyRectangle((Vector2) {
-        96, 64
+        96, _HOCKEY_ARENA_MID
     }, HOCKEY_PADDLE_W, HOCKEY_PADDLE_H, 100);
     paddle_b->enabled = false; // Disable body state to convert it to static (no dynamics, but collisions)
     paddle_b->useGravity = false;
@@ -78,49 +101,69 @@
 
 /**
  * Draws the fixed graphics of the game. This is the border of the arena
+ *
+ * note: locks `uLCD_mutex`
  */
 void drawStaticEnvironment()
 {
+    uLCD_mutex.lock();
+
+    // Clear screen
+    uLCD.cls();
+
+    // Draw arena border
     uLCD.line(0, _HOCKEY_GOAL_TOP, 0, HOCKEY_ARENA_TOP, BLUE);
     uLCD.line(0, _HOCKEY_GOAL_BOT, 0, HOCKEY_ARENA_BOT, BLUE);
     uLCD.line(0, HOCKEY_ARENA_TOP, 63, HOCKEY_ARENA_TOP, BLUE);
     uLCD.line(0, HOCKEY_ARENA_BOT, 63, HOCKEY_ARENA_BOT, BLUE);
-
     uLCD.line(127, _HOCKEY_GOAL_TOP, 127, HOCKEY_ARENA_TOP, RED);
     uLCD.line(127, _HOCKEY_GOAL_BOT, 127, HOCKEY_ARENA_BOT, RED);
     uLCD.line(64, HOCKEY_ARENA_TOP, 127, HOCKEY_ARENA_TOP, RED);
     uLCD.line(64, HOCKEY_ARENA_BOT, 127, HOCKEY_ARENA_BOT, RED);
+
+    // Draw score
+    uLCD.color(BLUE);
+    uLCD.locate(0, 0);
+    uLCD.printf("%i", blue_score);
+    uLCD.color(RED);
+    uLCD.locate(17, 0);
+    uLCD.printf("%i", red_score);
+
+    // Draw serve indicator
+    uLCD.filled_circle(
+        serve_direction ? SCREEN_WIDTH - 16 : 16,
+        HOCKEY_ARENA_TOP / 2,
+        HOCKEY_ARENA_TOP / 6,
+        serve_direction ? RED : BLUE);
+
+    uLCD_mutex.unlock();
 }
 
 /**
  * Redraws the puck and paddles
  * Each elements is erased, updated, and redrawn in as short of a window as
  * possible.
+ *
+ * note: locks `uLCD_mutex`
  */
 void drawDynamicObjects()
 {
     static int puck_x = SCREEN_WIDTH / 2,
-               puck_y = SCREEN_WIDTH / 2;
+               puck_y = _HOCKEY_ARENA_MID;
 
-    // Redraw puck if moved
-    if (puck_x != puck->position.x || puck_y != puck->position.y) {
-        // erase
-        uLCD.filled_circle(puck_x, puck_y, HOCKEY_PUCK_RADIUS, BLACK);
+    uLCD_mutex.lock();
 
-        // update
-        puck_x = puck->position.x;
-        puck_y = puck->position.y;
-
-        // redraw
-        uLCD.filled_circle(puck_x, puck_y, HOCKEY_PUCK_RADIUS, GREEN);
-    }
+    // Redraw puck
+    uLCD.filled_circle(puck_x, puck_y, HOCKEY_PUCK_RADIUS, BLACK);
+    puck_x = puck->position.x;
+    puck_y = puck->position.y;
+    uLCD.filled_circle(puck_x, puck_y, HOCKEY_PUCK_RADIUS, GREEN);
 
     // Redraw blue paddle
     static int pa_x1 = 64,
                pa_x2 = 64,
                pa_y1 = 64,
                pa_y2 = 64;
-
     float sinA = sin(-paddle_a->orient) / 2;
     float cosA = cos(-paddle_a->orient) / 2;
     uLCD.line(pa_x1, pa_y1, pa_x2, pa_y2, BLACK);
@@ -143,44 +186,64 @@
     pb_y1 = paddle_b->position.y + HOCKEY_PADDLE_H * cosB;
     pb_y2 = paddle_b->position.y - HOCKEY_PADDLE_H * cosB;
     uLCD.line(pb_x1, pb_y1, pb_x2, pb_y2, RED);
+
+    uLCD_mutex.unlock();
 }
 
-
+/**
+ * Reset the game after a goal.
+ * If the game will continue, re-draw and reset the pieces.
+ * If the game is over, display end scene and then suspend the game loop until
+ *      re-launched.
+ */
 void resetGame()
 {
+    // If game over, draw an end-game screen
     if (red_score >= 5 || blue_score >= 5) {
+        uLCD_mutex.lock();
         uLCD.cls();
+        uLCD.locate(5, 3);
+        uLCD.color(GREEN);
         uLCD.printf("Game Over");
 
         if (red_score > blue_score) {
-            uLCD.printf("Red Wins");
+            uLCD.locate(5, 5);
+            uLCD.color(RED);
+            uLCD.printf("Red Wins!");
         } else {
-            uLCD.printf("Blue Wins");
+            uLCD.locate(5, 5);
+            uLCD.color(BLUE);
+            uLCD.printf("Blue Wins!");
         }
 
         Thread::wait(2000);
 
         game2 = false;
-    } else {
-        uLCD.cls();
+        uLCD_mutex.unlock();
+    }
+
+    // Otherwise re-draw the game board and reset the pieces
+    else {
         drawStaticEnvironment();
 
         puck->position.x = SCREEN_WIDTH/2;
-        puck->position.y = SCREEN_HEIGHT/2;
+        puck->position.y = _HOCKEY_ARENA_MID;
+        puck->velocity.x = HOCKEY_PUCK_VELOCITY * (serve_direction ? 1 : -1);
+        puck->velocity.y = (float)(rand() % 50) / 100;
+
         paddle_a->position.x = 32;
-        paddle_a->position.y = 64;
+        paddle_a->position.y = _HOCKEY_ARENA_MID;
         SetPhysicsBodyRotation(paddle_a, 0);
 
         paddle_b->position.x = 96;
-        paddle_b->position.y = 64;
+        paddle_b->position.y = _HOCKEY_ARENA_MID;
         SetPhysicsBodyRotation(paddle_b, 0);
     }
 }
 
-
-
 /**
- *
+ * Handles puck bouncing off walls, goal scoring, player input, and end-game
+ * conditions
  */
 void runGameLogic()
 {
@@ -205,12 +268,12 @@
 
     // Puck collides with left or right walls
     if (puckInGoalRange == false) {
-        if (puck->position.x < HOCKEY_PUCK_RADIUS + 1) {
+        if (puck->position.x < HOCKEY_PUCK_RADIUS + 1 && puck->position.x > 0) {
             puck->velocity.x *= -1;
             puck->position.x = maxOf(
                                    puck->position.x,
                                    HOCKEY_PUCK_RADIUS + 1);
-        } else if (puck->position.x > SCREEN_WIDTH - HOCKEY_PUCK_RADIUS - 1) {
+        } else if (puck->position.x > SCREEN_WIDTH - HOCKEY_PUCK_RADIUS - 1 && puck->position.x < SCREEN_WIDTH) {
             puck->velocity.x *= -1;
             puck->position.x = minOf(
                                    puck->position.x,
@@ -221,117 +284,131 @@
     // Puck in goals
     else {
         // SCORE RED
-        if (puck->position.x < -HOCKEY_PUCK_RADIUS * 2) {
+        if (puck->position.x <= -HOCKEY_PUCK_RADIUS * 2) {
             red_score += 1;
-            uLCD.printf("Red Scores");
-            uLCD.printf("   %i - %i", blue_score, red_score);
+            serve_direction = 0;
+            uLCD.locate(4, 1);
+            uLCD.color(RED);
+            uLCD.printf("Red Scores!");
             Thread::wait(2000);
             resetGame();
         }
 
         // SCORE BLUE
-        else if (puck->position.x > SCREEN_WIDTH + HOCKEY_PUCK_RADIUS*2) {
+        else if (puck->position.x >= SCREEN_WIDTH + HOCKEY_PUCK_RADIUS * 2) {
             blue_score += 1;
-            uLCD.printf("Blue Scores");
-            uLCD.printf("   %i - %i", blue_score, red_score);
+            serve_direction = 1;
+            uLCD.locate(3, 1);
+            uLCD.color(BLUE);
+            uLCD.printf("Blue Scores!");
             Thread::wait(2000);
             resetGame();
         }
     }
 }
 
+/**
+ * Process user input and update game state
+ */
+void handleInput()
+{
+    // Process input from the game
+    blue.parseMessage();
+    if (blue.button[BUTTON_UP]) {
+        paddle_a->position.y -= input_sensitivity;
+    }
+    if (blue.button[BUTTON_DOWN]) {
+        paddle_a->position.y += input_sensitivity;
+    }
+    if (blue.button[BUTTON_LEFT]) {
+        paddle_a->position.x -= input_sensitivity;
+    }
+    if (blue.button[BUTTON_RIGHT]) {
+        paddle_a->position.x += input_sensitivity;
+    }
+    if (blue.button[BUTTON_A]) {
+        SetPhysicsBodyRotation(paddle_a, paddle_a->orient - input_sensitivity * 0.05);
+    }
+    if (blue.button[BUTTON_B]) {
+        SetPhysicsBodyRotation(paddle_a, paddle_a->orient + input_sensitivity * 0.05);
+    }
+
+    // Player 2 Input
+    if (!(navSwitch & 0b00001)) { // Up
+        paddle_b->position.y -= input_sensitivity;
+    }
+    if (!(navSwitch & 0b01000)) { // down
+        paddle_b->position.y += input_sensitivity;
+    }
+    if (!(navSwitch & 0b00100)) { // left
+        paddle_b->position.x -= input_sensitivity;
+    }
+    if (!(navSwitch & 0b10000)) { // right
+        paddle_b->position.x += input_sensitivity;
+    }
+    if (!(navSwitch & 0b00010)) { // rotate
+        SetPhysicsBodyRotation(paddle_b, paddle_b->orient + 3.14159 / 4);
+    }
+
+    // Bounds checking. Don't let users steer paddles out of the arena
+    paddle_a->position.y = clamp(paddle_a->position.y, HOCKEY_ARENA_TOP, HOCKEY_ARENA_BOT);
+    paddle_a->position.x = clamp(paddle_a->position.x, 1, SCREEN_WIDTH - 1);
+
+    paddle_b->position.y = clamp(paddle_b->position.y, HOCKEY_ARENA_TOP, HOCKEY_ARENA_BOT);
+    paddle_b->position.x = clamp(paddle_b->position.x, 1, SCREEN_WIDTH - 1);
+}
+
+/**
+ * The primary game loop for the air hockey game
+ */
 void hockeyGame(void)
 {
-    printf("Entering thread 2\r\n");
+    // Set up Physac objects & simulation
+    initPhysicsObjects();
+
     while (true) {
-        
+
+        // Wait until the game starts
         while (game2 == false) {
             printf("Waiting thread 2\r\n");
-            Thread::wait(500);
+            Thread::wait(100);
+            game2 = true;
         }
-        
-        printf("Starting Game 2 - Air Hockey\r\n");
-        
-        // Reset game variables
+
+        // Reset game state
         red_score = 0;
         blue_score = 0;
+        serve_direction = rand() % 2;
+        resetGame();
 
         // Reset screen, draw arena
-        uLCD.baudrate(BAUD_3000000);
-        uLCD.cls();
         drawStaticEnvironment();
 
-        // Set up Physac objects & simulation
-        initPhysicsObjects();
-
         Timer timer;
         timer.start();
 
-        deltaTime = 1.66;
-
         while (game2) {
             float dt = timer.read() * 1000;
             timer.reset();
 
             // Update the physics of the game
-            accumulator += dt;
-            if (accumulator >= deltaTime) {
+            for (int i = 0; i < HOCKEY_PHYSICS_STEPS; i++) {
                 PhysicsStep();
-                accumulator -= deltaTime;
+                runGameLogic();
+                if (game2 == false) break;
+            }
 
-                runGameLogic();
-            }
+            // Exit loop early if game over after the logic step
+            if (game2 == false) continue;
+
+            handleInput();
 
             // Render the game
             drawDynamicObjects();
-    
-            // Process input from the game
-            blue.parseMessage();
-            if (blue.button[BUTTON_UP]) {
-                paddle_a->position.y -= dt * input_sensitivity;
-                printf("UP\r\n");
-            }
-            if (blue.button[BUTTON_DOWN]) {
-                paddle_a->position.y += dt * input_sensitivity;
-                printf("DOWN\r\n");
-            }
-            if (blue.button[BUTTON_LEFT]) {
-                paddle_a->position.x -= dt * input_sensitivity;
-                printf("LEFT\r\n");
-            }
-            if (blue.button[BUTTON_RIGHT]) {
-                paddle_a->position.x += dt * input_sensitivity;
-                printf("RIGHT\r\n");
-            }
-            if (blue.button[BUTTON_A]) {
-                SetPhysicsBodyRotation(paddle_a, paddle_a->orient - dt * input_sensitivity * 0.05);
-                printf("CW\r\n");
-            }
-            if (blue.button[BUTTON_B]) {
-                SetPhysicsBodyRotation(paddle_a, paddle_a->orient + dt * input_sensitivity * 0.05);
-                printf("CCW\r\n");
-            }
-            
-            // Player 2 Input
-            if (!(navSwitch & 0b00001)) { // Up
-                paddle_b->position.y -= dt * input_sensitivity;
-            }
-            if (!(navSwitch & 0b01000)) { // down
-                paddle_b->position.y += dt * input_sensitivity;
-            }
-            if (!(navSwitch & 0b00100)) { // left
-                paddle_b->position.x -= dt * input_sensitivity;
-            }
-            if (!(navSwitch & 0b10000)) { // right
-                paddle_b->position.x += dt * input_sensitivity;
-            }
-            if (!(navSwitch & 0b00010)) { // rotate
-                SetPhysicsBodyRotation(paddle_b, paddle_b->orient + 3.14159 / 4);
-            }
-            
+
             Thread::yield();
         }
+    }
 
-        ClosePhysics();       // Unitialize physics
-    }
+    ClosePhysics();
 }
\ No newline at end of file
--- a/main.cpp	Wed Nov 30 21:43:44 2022 +0000
+++ b/main.cpp	Thu Dec 01 01:43:39 2022 +0000
@@ -9,6 +9,8 @@
 #include "uLCD_4DGL.h"
 
 uLCD_4DGL uLCD(p9,p10,p30);
+Mutex uLCD_mutex;
+
 BluefruitController blue(p13,p14);
 BusIn navSwitch(p15, p16, p17, p18, p19);