PONG

Платформа ATWatch благодаря наличию NRF может реализовывать проекты с беспроводной связью. Данный проект представляет из себя игру Пинг-понг, в которую можно играть вдвоём на разных часах по беспроводной связи.

Код приёмника (RX):
#define OLED_DC    26
#define OLED_CS    31
#define OLED_RESET 24
#define BUZZER     15
#define BTN1       2
#define BTN2       12
#define BTN3       13

#include 
#include "nRF24L01.h"
#include "GyverButton.h"
#include "RF24.h"
#include 
#include 

RF24 radio(3, 4);
Adafruit_SSD1306 display(OLED_DC, OLED_RESET, OLED_CS);
GButton btn1(BTN1, LOW_PULL, NORM_OPEN);
GButton btn2(BTN2, LOW_PULL, NORM_OPEN);
GButton btn3(BTN3, LOW_PULL, NORM_OPEN);

byte address[][6] = {"1Node", "2Node", "3Node", "4Node", "5Node", "6Node"}; //возможные номера труб

struct point_t
{
  byte x, y;
};
struct package_t
{
  byte gameStatus;
  byte scoreP1;
  byte scoreP2;
  byte txPlayerPos;
  point_t ballPos;
  point_t ballVel;
};

int x_pixels = 128;
int y_pixels = 64;

//Paddle Parameters
int paddle_height = 10;
int paddle_width = 3;

byte myPos = 20;

package_t package;

void setup() 
{
  btn1.setStepTimeout(50);
  btn1.setTimeout(50);
  btn3.setStepTimeout(50);
  btn3.setTimeout(50);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.display();

  radio.begin(); //активировать модуль
  radio.setAutoAck(1);         //режим подтверждения приёма, 1 вкл 0 выкл
  radio.setRetries(0, 15);    //(время между попыткой достучаться, число попыток)
  radio.enableAckPayload();    //разрешить отсылку данных в ответ на входящий сигнал
  //radio.enableDynamicPayloads(); // Разрешить динамически изменяемый размер блока данных на всех трубах.
  radio.setPayloadSize(10);     //размер пакета, в байтах
  radio.openReadingPipe(1, address[0]);     //хотим слушать трубу 0
  radio.setChannel(0x60);  //выбираем канал (в котором нет шумов!)
  radio.setPALevel (RF24_PA_MAX); //уровень мощности передатчика. На выбор RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX
  radio.setDataRate (RF24_2MBPS); //скорость обмена. На выбор RF24_2MBPS, RF24_1MBPS, RF24_250KBPS
  radio.powerUp(); //начать работу
  radio.startListening();  //начинаем слушать эфир, мы приёмный модуль
}

void loop(void)
{
  Buttons();
  byte pipeNo;
  while (radio.available(&pipeNo)) // слушаем эфир со всех труб
  {
    radio.read(&package, sizeof(package) );         // чиатем входящий сигнал
    radio.writeAckPayload(pipeNo, &myPos, sizeof(myPos) ); // отправляем обратно то что приняли
    pongLoop();
  }
}

void Buttons()
{
  btn1.tick();
  btn2.tick();
  btn3.tick();
  if (btn1.isStep() || btn1.isClick())
  {
    if (myPos < (y_pixels - paddle_height))
      myPos += 2;
  }

  if (btn3.isStep() || btn3.isClick())
  {
    if (myPos > 0) 
      myPos -= 2;
  }
}

void pongLoop()
{
  if (package.ballPos.x > x_pixels - 1) 
  {
    tone(BUZZER, 50, 100);
    delay(50);
    tone(BUZZER, 50, 100);
  }

  else if (package.ballPos.x < 1) 
  {
    tone(BUZZER, 50, 100);
    delay(50);
    tone(BUZZER, 50, 100);
  }


  // Draw pong elements to display:
  display.clearDisplay();
  display.drawPixel(package.ballPos.x, package.ballPos.y, WHITE);
  display.fillRect(0, package.txPlayerPos, paddle_width, paddle_height, WHITE);
  display.fillRect(x_pixels - paddle_width , myPos, paddle_width, paddle_height, WHITE);

  // Display scores
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(display.width() / 4, 0);
  display.println(package.scoreP1);
  display.setCursor(display.width() * 3 / 4, 0);
  display.println(package.scoreP2);

  // Display all elements
  display.display();

  // Check for ball bouncing off paddles:
  if (ball_on_right_paddle())
  {
    tone(BUZZER, 300, 100);
  }
  else if (ball_on_left_paddle())
  {
    tone(BUZZER, 250, 100);
  }
}

bool ball_on_right_paddle() 
{
  // If ball is heading towards paddle and is at the surface of paddle between the top and bottom of the paddle, then it's a hit
  return (package.ballPos.x == x_pixels - paddle_width - 1 && package.ballPos.y >= myPos && package.ballPos.y <= (myPos + paddle_height) && package.ballVel.x == 1);
}

bool ball_on_left_paddle() 
{
  return (package.ballPos.x == paddle_width - 1 && package.ballPos.y >= package.txPlayerPos && package.ballPos.y <= (package.txPlayerPos + paddle_height));
}



Код передатчика (TX):
#define OLED_DC    26
#define OLED_CS    31
#define OLED_RESET 24
#define BUZZER     15
#define BTN1       2
#define BTN2       12
#define BTN3       13

#include 
#include "nRF24L01.h"
#include "GyverButton.h"
#include "RF24.h"
#include 
#include 

RF24 radio(3, 4);
Adafruit_SSD1306 display(OLED_DC, OLED_RESET, OLED_CS);
GButton btn1(BTN1, LOW_PULL, NORM_OPEN);
GButton btn2(BTN2, LOW_PULL, NORM_OPEN);
GButton btn3(BTN3, LOW_PULL, NORM_OPEN);

byte address[][6] = {"1Node", "2Node", "3Node", "4Node", "5Node", "6Node"}; //возможные номера труб

struct point_t
{
  byte x, y;
};
struct package_t
{
  byte gameStatus;
  byte scoreP1;
  byte scoreP2;
  byte txPlayerPos;
  point_t ballPos;
  point_t ballVel;
};

int x_pixels = 128;
int y_pixels = 64;

//Paddle Parameters
int paddle_height = 10;
int paddle_width = 3;

byte rxPlayerPos;

point_t ballPos = { 10, 20};
package_t package;

void setup()
{
  btn1.setStepTimeout(50);
  btn1.setTimeout(50);
  btn3.setStepTimeout(50);
  btn3.setTimeout(50);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.display();

  radio.begin(); //активировать модуль
  radio.setAutoAck(1);         //режим подтверждения приёма, 1 вкл 0 выкл
  radio.setRetries(0, 2);    //(время между попыткой достучаться, число попыток)
  radio.enableAckPayload();    //разрешить отсылку данных в ответ на входящий сигнал
  //radio.enableDynamicPayloads(); // Разрешить динамически изменяемый размер блока данных на всех трубах.
  radio.setPayloadSize(10);     //размер пакета, в байтах
  radio.openWritingPipe(address[0]);   //мы - труба 0, открываем канал для передачи данных
  radio.setChannel(0x60);  //выбираем канал (в котором нет шумов!)
  radio.setPALevel (RF24_PA_MAX); //уровень мощности передатчика. На выбор RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX
  radio.setDataRate (RF24_2MBPS); //скорость обмена. На выбор RF24_2MBPS, RF24_1MBPS, RF24_250KBPS
  radio.powerUp(); //начать работу
  radio.stopListening();  //не слушаем радиоэфир, мы передатчик
  Init();
}
void Init()
{
  package.ballPos = ballPos;
  package.txPlayerPos = package.txPlayerPos;
  package.scoreP1 = 0;
  package.scoreP2 = 0;
  package.gameStatus = 0;
  package.ballVel = {1, 1};
}
void loop(void)
{
  Buttons();
  radio.writeFast(&package, sizeof(package));
  while (radio.available())
  {
    radio.read(&rxPlayerPos, sizeof(rxPlayerPos) );
  }
  pongLoop();
  delay(20);
}

void Buttons()
{
  btn1.tick();
  btn2.tick();
  btn3.tick();
  if (btn1.isStep() || btn1.isClick())
  {
    if (package.txPlayerPos < (y_pixels - paddle_height))
      package.txPlayerPos += 2;
  }

  if (btn3.isStep() || btn3.isClick())
  {
    if ( package.txPlayerPos > 0)
      package.txPlayerPos -= 2;
  }
}

void pongLoop()
{
  // Check for ball hitting a wall:
  if (package.ballPos.x > x_pixels - 1)
  {
    ball_reset(false);
    package.scoreP1 += 1;
    tone(BUZZER, 50, 100);
    delay(50);
    tone(BUZZER, 50, 100);
  }

  else if (package.ballPos.x < 1)
  {
    ball_reset(true);
    package.scoreP2 += 1;
    tone(BUZZER, 50, 100);
    delay(50);
    tone(BUZZER, 50, 100);
    Serial.println(2);
  }

  // Check for ball bouncing off ceiling:
  if (package.ballPos.y > y_pixels - 1 || package.ballPos.y < 0)
  {
    package.ballVel.y = -package.ballVel.y;
  }

  // Check for ball bouncing off paddle:

  // Update ball position:
  package.ballPos.x += package.ballVel.x;
  package.ballPos.y += package.ballVel.y;

  // Draw pong elements to display:
  display.clearDisplay();
  display.drawPixel(package.ballPos.x, package.ballPos.y, WHITE);
  display.fillRect(0, package.txPlayerPos, paddle_width, paddle_height, WHITE);
  display.fillRect(x_pixels - paddle_width , rxPlayerPos, paddle_width, paddle_height, WHITE);

  // Display scores
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(display.width() / 4, 0);
  display.println(package.scoreP1);
  display.setCursor(display.width() * 3 / 4, 0);
  display.println(package.scoreP2);

  // Display all elements
  display.display();

  // Check for ball bouncing off paddles:
  if (ball_on_right_paddle())
  {
    package.ballVel.x = -package.ballVel.x;
    tone(BUZZER, 300, 100);
  }
  else if (ball_on_left_paddle())
  {
    package.ballVel.x = -package.ballVel.x;
    tone(BUZZER, 250, 100);
  }
}

bool ball_on_right_paddle()
{
  // If ball is heading towards paddle and is at the surface of paddle between the top and bottom of the paddle, then it's a hit
  return (package.ballPos.x == x_pixels - paddle_width - 1 && package.ballPos.y >= rxPlayerPos && package.ballPos.y <= (rxPlayerPos + paddle_height) && package.ballVel.x == 1);
}

bool ball_on_left_paddle()
{
  return (package.ballPos.x == paddle_width + 1 && package.ballPos.y >= package.txPlayerPos && package.ballPos.y <= (package.txPlayerPos + paddle_height));
}

void ball_reset(bool left)
{
  //If left is true, then ball should launch from the left, otherwise launch from the right
  //Ball should launch at a random Y location and at a random Y velocity

  package.ballPos.y = random(display.height());
  if (random(2) > 0)
  {
    package.ballVel.y = 1;
  }
  else
  {
    package.ballVel.y = -1;
  }

  if (left)
  {
    package.ballVel.x = 1;
    package.ballPos.x = paddle_width - 1;
  }
  else
  {
    package.ballVel.x = -1;
    package.ballPos.x = display.width() - paddle_width;
  }
}