Full Code of dupontgu/pov_pong_mouse for AI

main 9de8de0adfa2 cached
5 files
20.5 KB
6.2k tokens
13 symbols
1 requests
Download .txt
Repository: dupontgu/pov_pong_mouse
Branch: main
Commit: 9de8de0adfa2
Files: 5
Total size: 20.5 KB

Directory structure:
gitextract_tp3htaza/

├── LICENSE
├── README.md
├── game_mouse.ino
├── pong.h
└── usbh_helper.h

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2024 Guy Dupont

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
## WHAT?

A mouse with a game of Pong running inside it's firmware, rendered on screen by moving the cursor really fast!

## Demo

https://github.com/user-attachments/assets/f5d2ad66-0942-43ed-bbec-080b894ee467

Notes:
- The game stops/starts when I _click_ the scroll wheel. Otherwise the mouse acts normally.
- The score is rendered after a point is scored by showing the cursor somewhere between the left and right "paddles". The closer to a given paddle, the more that player is winning by (it's a ratio). If the cursor is in the middle, the score is tied.

## Hardware Details
My code runs on an [Seeed Studio XIAO RP2040](https://wiki.seeedstudio.com/XIAO-RP2040/) dev board, which - using the magic of [TinyUSB](https://docs.tinyusb.org/en/latest/) - can act as both a custom USB device _and_ host.  I detached the USB connection from the inside of a cheap HP mouse and fed it into the XIAO's USB port (this goes to the computer). I then wire up the aux USB connection using the XIAO's power/GPIO pins, which goes back to the original mouse's PCB. This creates a USB interceptor that can be programmed to do whatever! Also see my [Mouse/Keyboard effects pedal](https://github.com/dupontgu/hidden_agenda_pedal) :P.

<img src=".docs/mouse_apart.jpg" style="width: 700px;"/>

## How It Works
Most modern computer mice using relative positioning - they report _changes_ in their movement. If you move the mouse slowly to the left, it might spit out a bunch of packets where the x component is just -1 (meaning the mouse moved 1 "pixel" to the left). However! It is possible to implement a USB mouse that uses _absolute_ positioning. It can send an exact position on your monitor (percentage X, percentage Y) that the cursor should move to instantly. This is commonly used for touchscreen drivers - you want the cursor to appear where the finger touches the screen.  This firmware emulates an absolute positioning mouse and quickly moves the mouse cursor between points of interest. It moves fast enough that the cursor (kinda) appears in all positions at once and gives the impression that it is in multiple places at once. I have implemented a simple game of Pong to run in the firmware, and set the cursor's points of interest to be the 2 paddles and the ball while the game is active. So the game runs completely inside the mouse!

## Setup
1. You'll need hardware! Any RP2040 based board wired up with a second USB port will do, but I recommend Adafruit's [Feather RP2040 with USB Host](https://www.adafruit.com/product/5723).
2. Follow [Adafruit's guide](https://learn.adafruit.com/adafruit-feather-rp2040-with-usb-type-a-host/arduino-ide-setup) for getting set up with the Arduino IDE.
3. Install the Adafruit TinyUSB Library and the Pico PIO USB Library (again, [guide](https://learn.adafruit.com/adafruit-feather-rp2040-with-usb-type-a-host/usb-host-device-info)).
4. Ensure that your USB D+/D- pins are [set correctly](./usbh_helper.h).
5. Build and run!


================================================
FILE: game_mouse.ino
================================================
/*********************************************************************
 Adafruit invests time and resources providing this open source code,
 please support Adafruit and open-source hardware by purchasing
 products from Adafruit!

 MIT license, check LICENSE for more information
 Copyright (c) 2019 Ha Thach for Adafruit Industries
 All text above, and the splash screen below must be included in
 any redistribution
*********************************************************************/

/**
Adapted from Adafruit's TinyUSB sample remapper demo:
https://github.com/adafruit/Adafruit_TinyUSB_Arduino/tree/master/examples/DualRole/HID/hid_remapper

by Guy Dupont, August 2024
**/

// USBHost is defined in usbh_helper.h
#include "usbh_helper.h"
#include "pong.h"

// adapted from https://github.com/jonathanedgecombe/absmouse/blob/master/src/AbsMouse.cpp
uint8_t const abs_mouse_desc[] = {
  0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
  0x09, 0x02,        // Usage (Mouse)
  0xA1, 0x01,        // Collection (Application)
  0x09, 0x01,        //   Usage (Pointer)
  0xA1, 0x00,        //   Collection (Physical)
  0x85, 0x04,        //     Report ID (4)
  0x05, 0x09,        //     Usage Page (Button)
  0x19, 0x01,        //     Usage Minimum (0x01)
  0x29, 0x03,        //     Usage Maximum (0x03)
  0x15, 0x00,        //     Logical Minimum (0)
  0x25, 0x01,        //     Logical Maximum (1)
  0x95, 0x03,        //     Report Count (3)
  0x75, 0x01,        //     Report Size (1)
  0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
  0x95, 0x01,        //     Report Count (1)
  0x75, 0x05,        //     Report Size (5)
  0x81, 0x03,        //     Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
  0x05, 0x01,        //     Usage Page (Generic Desktop Ctrls)
  0x09, 0x30,        //     Usage (X)
  0x09, 0x31,        //     Usage (Y)
  0x16, 0x00, 0x00,  //     Logical Minimum (0)
  0x26, 0xFF, 0x7F,  //     Logical Maximum (32767)
  0x36, 0x00, 0x00,  //     Physical Minimum (0)
  0x46, 0xFF, 0x7F,  //     Physical Maximum (32767)
  0x75, 0x10,        //     Report Size (16)
  0x95, 0x02,        //     Report Count (2)
  0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
  0xC0,              //   End Collection
  0xC0               // End Collection
};

uint8_t const desc_hid_report[] = {
  TUD_HID_REPORT_DESC_MOUSE()
};

// two HID devices, one for absolute mouse (the game) and one for normal mouse (passthrough)
Adafruit_USBD_HID usb_hid_abs(abs_mouse_desc, sizeof(abs_mouse_desc), HID_ITF_PROTOCOL_NONE, 2, true);
Adafruit_USBD_HID usb_hid(desc_hid_report, sizeof(desc_hid_report), HID_ITF_PROTOCOL_MOUSE, 2, true);
Game game;

long lastFrameTime = 0;

// these numbers are virtual/relative and don't correspond to monitor size
const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;
const int PADDLE_WIDTH = 10;
const int PADDLE_HEIGHT = 100;
const int HALF_PADDLE_HEIGHT = PADDLE_HEIGHT / 2;
const int BALL_SIZE = 10;
const float MAX_Y_VEL = 8.0;

int8_t mouseVelocity;
bool gameRunning = false;

void setup() {
  Serial.begin(115200);
  usb_hid_abs.begin();
  usb_hid.begin();
  initGame(&game, 10.0, SCREEN_HEIGHT, SCREEN_WIDTH, BALL_SIZE, PADDLE_HEIGHT, PADDLE_WIDTH);
}

// renders the cursor on screen in a specific spot.
// x and y should be floats between 0.0 and 1.0
// (0, 0) -> top left of screen, (1, 1) -> bottom right
void renderCursor(float x, float y) {
  static uint8_t reportOut[5] = { 0, 0, 0, 0, 0 };
  uint16_t adjX = (uint16_t)((x * 20000) + 6383);  // padding is (32767 - [max]) / 2
  uint16_t adjY = (uint16_t)((y * 26000) + 3383);
  reportOut[1] = adjX & 0xFF;
  reportOut[2] = (adjX >> 8) & 0xFF;
  reportOut[3] = adjY & 0xFF;
  reportOut[4] = (adjY >> 8) & 0xFF;
  if (usb_hid_abs.ready()) {
    // report id 4 was hardcoded in descriptor above
    usb_hid_abs.sendReport(4, &reportOut, 5);
  }
}

void loop() {
  if (gameRunning) {
    long timeNow = millis();
    if ((timeNow - lastFrameTime) >= 16) {
      lastFrameTime = timeNow;
      float leftPaddleVel = paddleAutoPilot(&game, &(game.lPaddle), 5.0, 2);
      float rightPaddleVel = mouseVelocity;
      mouseVelocity = 0;
      tick(&game, leftPaddleVel, rightPaddleVel);
    }

    Frame frame = nextSubframe(&game);
    renderCursor(frame.x, frame.y);
  }

  // ok to delay in this loop, different values produce different POV artifacts
  delay(7);
}

//------------- Core1 -------------//
void setup1() {
  // configure pio-usb: defined in usbh_helper.h
  rp2040_configure_pio_usb();

  // run host stack on controller (rhport) 1
  // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the
  // host bit-banging processing works done in core1 to free up core0 for other works
  USBHost.begin(1);
}

void loop1() {
  USBHost.task();
}

//--------------------------------------------------------------------+
// TinyUSB Host callbacks
//--------------------------------------------------------------------+
extern "C" {

  // Invoked when device with hid interface is mounted
  // Report descriptor is also available for use.
  // tuh_hid_parse_report_descriptor() can be used to parse common/simple enough
  // descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE,
  // it will be skipped therefore report_desc = NULL, desc_len = 0
  void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) {
    (void)desc_report;
    (void)desc_len;
    uint16_t vid, pid;
    tuh_vid_pid_get(dev_addr, &vid, &pid);

    Serial.printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance);
    Serial.printf("VID = %04x, PID = %04x\r\n", vid, pid);

    uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
    if (itf_protocol == HID_ITF_PROTOCOL_MOUSE) {
      Serial.printf("HID Mouse\r\n");
      if (!tuh_hid_receive_report(dev_addr, instance)) {
        Serial.printf("Error: cannot request to receive report\r\n");
      }
    }
  }

  // Invoked when device with hid interface is un-mounted
  void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) {
    Serial.printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance);
  }

  // Invoked when received report from device via interrupt endpoint
  void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) {
    static uint8_t reportOut[5] = { 0, 0, 0, 0, 0 };
    static bool scrollButtonDown;
    // different mice might have different lengths here! My shitty HP mouse only reports [buttons, x, y]
    if (len == 3) {
      // scroll wheel click is 3rd bit, use that to start/stop the game
      if (report[0] & 0b100) {
        if (!scrollButtonDown) {
          scrollButtonDown = true;
          gameRunning = !gameRunning;
        }
      } else {
        scrollButtonDown = false;
      }

      if (!gameRunning) {
        // if game's not running, copy mouse report to passthrough
        reportOut[0] = report[0];
        reportOut[1] = report[1];
        reportOut[2] = report[2];
        usb_hid.sendReport(0, reportOut, 5);
      } else {
        // if game is happening, copy latest Y velocity to be used by game logic
        mouseVelocity = (int8_t)report[2];
      }
    }
    // continue to request to receive report
    if (!tuh_hid_receive_report(dev_addr, instance)) {
      Serial.printf("Error: cannot request to receive report\r\n");
    }
  }
}


================================================
FILE: pong.h
================================================
#define RENDER_SCORE_FRAMES_MAX 80

typedef struct
{
  float x, y;
  float velX, velY;
} Ball;

typedef struct
{
  float x, y;
  float velY;
  int score;
} Paddle;

typedef struct
{
  Ball ball;
  Paddle lPaddle;
  Paddle rPaddle;
  float maxYVel;
  int screenHeight;
  int screenWidth;
  int ballSize;
  int paddleHeight;
  int paddleWidth;
  int halfPaddleHeight;
  int renderScoreCounter;
  uint8_t subFrame;
} Game;

typedef struct
{
  float x, y;
} Frame;

void initGame(Game *gameHolder, float maxBallVelocity, float screenHeight, float screenWidth, int ballSize, int paddleHeight, int paddleWidth) {
  Paddle left = { 10, (screenHeight - paddleHeight) / 2, 0, 0 };
  gameHolder->lPaddle = left;
  Paddle right = { screenWidth - 20, (screenHeight - paddleHeight) / 2, 0, 0 };
  gameHolder->rPaddle = right;
  // TODO - pass in ball x velocity as argument
  Ball ball = { .x = screenWidth / 2, .y = screenHeight / 2, .velX = -5, .velY = 1.6 };
  gameHolder->ball = ball;
  gameHolder->maxYVel = maxBallVelocity;
  gameHolder->screenHeight = screenHeight;
  gameHolder->screenWidth = screenWidth;
  gameHolder->ballSize = ballSize;
  gameHolder->paddleHeight = paddleHeight;
  gameHolder->paddleWidth = paddleWidth;
  gameHolder->halfPaddleHeight = paddleHeight / 2;
  gameHolder->renderScoreCounter = 0;
}

int distFromPaddleCenter(Game *game, Ball *ball, Paddle *paddle) {
  return ball->y - (paddle->y + game->halfPaddleHeight);
}

float yVelocityFromPaddleIntersect(Game *game, int distFromPaddleCenter) {
  return ((float)distFromPaddleCenter / (float)game->halfPaddleHeight) * game->maxYVel;
}

void movePaddle(Game *game, Paddle *paddle) {
  paddle->y += paddle->velY;

  // Keep paddle within screen bounds
  if (paddle->y < 0) {
    paddle->y = 0;
  } else if (paddle->y > game->screenHeight - game->paddleHeight) {
    paddle->y = game->screenHeight - game->paddleHeight;
  }
}

void moveBall(Game *game) {
  Ball *ball = &(game->ball);
  Paddle *leftPaddle = &(game->lPaddle);
  Paddle *rightPaddle = &(game->rPaddle);
  ball->x += ball->velX;
  ball->y += ball->velY;

  // Bounce off top and bottom walls
  if (ball->y <= 0 || ball->y >= game->screenHeight - game->ballSize) {
    ball->velY = -ball->velY;
  }

  int leftPaddleYDist = distFromPaddleCenter(game, ball, leftPaddle);
  int rightPaddleYDist = distFromPaddleCenter(game, ball, rightPaddle);

  // Bounce off paddles
  if (ball->x <= leftPaddle->x + game->paddleWidth && ball->y + game->ballSize >= leftPaddle->y && abs(leftPaddleYDist) < game->halfPaddleHeight) {
    ball->velX = -ball->velX;
    ball->x = leftPaddle->x + game->paddleWidth;
    ball->velY = yVelocityFromPaddleIntersect(game, leftPaddleYDist);
  } else if (ball->x + game->ballSize >= rightPaddle->x && ball->y + game->ballSize >= rightPaddle->y && abs(rightPaddleYDist) < game->halfPaddleHeight) {
    ball->velX = -ball->velX;
    ball->x = rightPaddle->x - game->ballSize;
    ball->velY = yVelocityFromPaddleIntersect(game, rightPaddleYDist);
  }

  bool pointScored = false;
  // Reset ball if it goes past the paddles
  if (ball->x < 0) {
    pointScored = true;
    game->rPaddle.score++;
    Serial.println("r score");
  } else if (ball->x > game->screenWidth) {
    pointScored = true;
    game->lPaddle.score++;
    Serial.println("l score");
  }

  if (pointScored) {
    game->renderScoreCounter = RENDER_SCORE_FRAMES_MAX;
    ball->x = game->screenWidth / 2;
    ball->y = game->screenHeight / 2;
    ball->velX = -ball->velX;
    ball->velY = 1;
  }
}

// quick an dirty AI, can be used with either/both paddles
// call with the desired paddle, then pass the returned velocity value back in with the next "tick"
float paddleAutoPilot(Game *game, Paddle *paddle, float maxVelocity, int randomSeed) {
  float guessVelocity = 0.0;
  int scoreSeed = game->lPaddle.score + game->rPaddle.score + randomSeed;
  bool targetOffsetNegative = (scoreSeed % 2) == 1;
  int targetOffset = ((scoreSeed + 1) * 7) % game->halfPaddleHeight;
  if (targetOffsetNegative) {
    targetOffset = -targetOffset;
  }
  float yDistFromBall = game->ball.y - (paddle->y + game->halfPaddleHeight + targetOffset);
  if (yDistFromBall > maxVelocity) {
    guessVelocity = maxVelocity;
  } else if (yDistFromBall < -maxVelocity) {
    guessVelocity = -maxVelocity;
  }
  return guessVelocity;
}

void tick(Game *game, float leftPaddleVel, float rightPaddleVel) {
  if (game->renderScoreCounter > 0) {
    game->renderScoreCounter -= 1;
  } else {
    game->lPaddle.velY = leftPaddleVel;
    game->rPaddle.velY = rightPaddleVel;
    movePaddle(game, &(game->lPaddle));
    movePaddle(game, &(game->rPaddle));
    moveBall(game);
  }
}

Frame nextSubframe(Game *game) {
  Frame frame;
  if (game->renderScoreCounter > 0) {
    // score is rendered by showing the mouse cursor in three spots
    // outer spots show the bounds, inner spot indicates which player is winning
    // the closer the inner spot is to either edge indicates how much that player is up by
    // when the game is tied, the inner spot should sit right in the middle
    float pointRatio = (float)(game->rPaddle.score + 1) / (float)(game->lPaddle.score + 1);
    if (pointRatio < 1.0) {
      pointRatio /= 2.0;
    } else {
      pointRatio = 1.0 - ((1.0 / pointRatio) / 2.0);
    }
    switch (game->subFrame++) {
      case 0:
        frame.x = 0;
        frame.y = 0.5;
        break;
      case 1:
        frame.x = pointRatio;
        frame.y = 0.5;
        break;
      case 2:
        frame.x = 1;
        frame.y = 0.5;
      default:
        game->subFrame = 0;
    }
  } else {
    switch (game->subFrame++) {
      case 0:
        frame.x = game->lPaddle.x / (float)game->screenWidth;
        frame.y = (game->lPaddle.y + game->halfPaddleHeight) / (float)game->screenHeight;
        break;
      case 1:
        frame.x = game->ball.x / (float)game->screenWidth;
        frame.y = game->ball.y / (float)game->screenHeight;
        break;
      case 2:
        frame.x = game->rPaddle.x / (float)game->screenWidth;
        frame.y = (game->rPaddle.y + game->halfPaddleHeight) / (float)game->screenHeight;
      default:
        game->subFrame = 0;
    }
  }
  return frame;
}


================================================
FILE: usbh_helper.h
================================================
/*********************************************************************
 Adafruit invests time and resources providing this open source code,
 please support Adafruit and open-source hardware by purchasing
 products from Adafruit!

 MIT license, check LICENSE for more information
 Copyright (c) 2019 Ha Thach for Adafruit Industries
 All text above, and the splash screen below must be included in
 any redistribution
*********************************************************************/

/**
Adapted from Adafruit's TinyUSB sample remapper demo:
https://github.com/adafruit/Adafruit_TinyUSB_Arduino/tree/master/examples/DualRole/HID/hid_remapper

by Guy Dupont, August 2024
**/

#ifndef USBH_HELPER_H
#define USBH_HELPER_H

#ifdef ARDUINO_ARCH_RP2040
  // pio-usb is required for rp2040 host
  #include "pio_usb.h"

  // Pin D+ for host, D- = D+ + 1
  #ifndef PIN_USB_HOST_DP
  // THIS IS SPECIFIC TO MY BOARD, use 16 for D+ with usb host feather
  #define PIN_USB_HOST_DP  3
  #endif

  // Pin for enabling Host VBUS. comment out if not used
  #ifndef PIN_5V_EN
  #define PIN_5V_EN        18
  #endif

  #ifndef PIN_5V_EN_STATE
  #define PIN_5V_EN_STATE  1
  #endif
#endif // ARDUINO_ARCH_RP2040

#include "Adafruit_TinyUSB.h"

#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421
  // USB Host using MAX3421E: SPI, CS, INT
  #include "SPI.h"

  #if defined(ARDUINO_METRO_ESP32S2)
    Adafruit_USBH_Host USBHost(&SPI, 15, 14);
  #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2)
    Adafruit_USBH_Host USBHost(&SPI, 33, 15);
  #else
    // Default CS and INT are pin 10, 9
    Adafruit_USBH_Host USBHost(&SPI, 10, 9);
  #endif
#else
  // Native USB Host such as rp2040
  Adafruit_USBH_Host USBHost;
#endif

//--------------------------------------------------------------------+
// Helper Functions
//--------------------------------------------------------------------+

#ifdef ARDUINO_ARCH_RP2040
static void rp2040_configure_pio_usb(void) {
  //while ( !Serial ) delay(10);   // wait for native usb
  Serial.println("Core1 setup to run TinyUSB host with pio-usb");

  // Check for CPU frequency, must be multiple of 120Mhz for bit-banging USB
  uint32_t cpu_hz = clock_get_hz(clk_sys);
  if (cpu_hz != 120000000UL && cpu_hz != 240000000UL) {
    while (!Serial) {
      delay(10);   // wait for native usb
    }
    Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 120 Mhz\r\n", cpu_hz);
    Serial.printf("Change your CPU Clock to either 120 or 240 Mhz in Menu->CPU Speed \r\n");
    while (1) {
      delay(1);
    }
  }

#ifdef PIN_5V_EN
  pinMode(PIN_5V_EN, OUTPUT);
  digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE);
#endif

  pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
  pio_cfg.pin_dp = PIN_USB_HOST_DP;

#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
  // For pico-w, PIO is also used to communicate with cyw43
  // Therefore we need to alternate the pio-usb configuration
  // details https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46
  pio_cfg.sm_tx      = 3;
  pio_cfg.sm_rx      = 2;
  pio_cfg.sm_eop     = 3;
  pio_cfg.pio_rx_num = 0;
  pio_cfg.pio_tx_num = 1;
  pio_cfg.tx_ch      = 9;
#endif

  USBHost.configure_pio_usb(1, &pio_cfg);
}
#endif

#endif
Download .txt
gitextract_tp3htaza/

├── LICENSE
├── README.md
├── game_mouse.ino
├── pong.h
└── usbh_helper.h
Download .txt
SYMBOL INDEX (13 symbols across 2 files)

FILE: pong.h
  type Ball (line 3) | typedef struct
  type Paddle (line 9) | typedef struct
  type Game (line 16) | typedef struct
  type Frame (line 32) | typedef struct
  function initGame (line 37) | void initGame(Game *gameHolder, float maxBallVelocity, float screenHeigh...
  function distFromPaddleCenter (line 55) | int distFromPaddleCenter(Game *game, Ball *ball, Paddle *paddle) {
  function yVelocityFromPaddleIntersect (line 59) | float yVelocityFromPaddleIntersect(Game *game, int distFromPaddleCenter) {
  function movePaddle (line 63) | void movePaddle(Game *game, Paddle *paddle) {
  function moveBall (line 74) | void moveBall(Game *game) {
  function paddleAutoPilot (line 123) | float paddleAutoPilot(Game *game, Paddle *paddle, float maxVelocity, int...
  function tick (line 140) | void tick(Game *game, float leftPaddleVel, float rightPaddleVel) {
  function Frame (line 152) | Frame nextSubframe(Game *game) {

FILE: usbh_helper.h
  function rp2040_configure_pio_usb (line 66) | static void rp2040_configure_pio_usb(void) {
Condensed preview — 5 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (22K chars).
[
  {
    "path": "LICENSE",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2024 Guy Dupont\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "README.md",
    "chars": 2967,
    "preview": "## WHAT?\n\nA mouse with a game of Pong running inside it's firmware, rendered on screen by moving the cursor really fast!"
  },
  {
    "path": "game_mouse.ino",
    "chars": 7582,
    "preview": "/*********************************************************************\n Adafruit invests time and resources providing th"
  },
  {
    "path": "pong.h",
    "chars": 6187,
    "preview": "#define RENDER_SCORE_FRAMES_MAX 80\n\ntypedef struct\n{\n  float x, y;\n  float velX, velY;\n} Ball;\n\ntypedef struct\n{\n  float"
  },
  {
    "path": "usbh_helper.h",
    "chars": 3212,
    "preview": "/*********************************************************************\n Adafruit invests time and resources providing th"
  }
]

About this extraction

This page contains the full source code of the dupontgu/pov_pong_mouse GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 5 files (20.5 KB), approximately 6.2k tokens, and a symbol index with 13 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!