[178] Pong na oscyloskopie cz. 4
Większość pracy związanej z wyświetlaniem cyfr już odrobiliśmy w poprzednich artykułach. Nauczyliśmy się tam tworzyć oscyloskopowe napisy, w szczególności wyświetlać cyfry. I to nie takie sztampowe, a fantazyjne, bowiem ogranicza nas tu tylko wyobraźnia i zasady optymalizacji – rysowanie figur bez odrywania wirtualnego pisaka od ekranu. Czas więc połączyć dwa programy: obecną wersję Ponga i ten, w którym wyświetlane były cyfry. Ciekawe jak sobie poradzi z tym mój oscyloskop. Spójrzmy jakie zmiany pojawiły się.
#include "pwm.h" // Biblioteka PWM dla architektury Uno R4
const byte pot = A1; // Port potencjometru sterującego prawą paletką (gracz).
PwmOut pwmX(10); // Obiekt sterujący sprzętowym PWM dla pinu 10 (oś X).
PwmOut pwmY(9); // Obiekt sterujący sprzętowym PWM dla pinu 9 (oś Y).
const byte paddleLength = 8; // Połowa szerokości paletki.
const byte paddleTop = 100 - paddleLength; // Górna granica ruchu środka paletki.
byte paddleLCenterY = 50; // Środek lewej paletki (komputera).
byte paddleRCenterY = 50; // Środek prawej paletki (gracza).
int ballX = 50; // Pozycja X piłki.
int ballY = 50; // Pozycja Y piłki.
int ballXSpeed = 2; // Prędkość pozioma piłki i jej kierunek.
int ballYSpeed = 1; // Prędkość pionowa piłki i jej kierunek.
unsigned long prevMillis = 0; // Czas poprzedniego stanu zegara systemowego.
const unsigned long refresh = 20; // Czas odświeżania logiki gry w ms.
int ballCount = 0; // Licznik odbić piłki od paletek.
byte scoreL = 0; // Wynik lewej paletki (komputer)
byte scoreR = 0; // Wynik prawej paletki (gracz)
#define XL 0 // Definicje położenia: Lewy punkt pierwszej cyfry.
#define XR 20 // Prawy punkt pierwszej cyfry.
#define YD 30 // Dolny punkt siatki odcinków.
#define YM 50 // Środkowy punkt siatki odcinków.
#define YU 70 // Górny punkt siatki odcinków.
const byte nodeX[7] = { 0, XL, XR, XL, XR, XL, XR }; // Mapowanie węzłów na współrzędne X...
const byte nodeY[7] = { 0, YU, YU, YM, YM, YD, YD }; // oraz Y
#define LU 1 // Definicje indeksów węzłów według klucza: Left-Up itd
#define RU 2
#define LM 3
#define RM 4
#define LD 5
#define RD 6
const byte vectors[10][8] = {
{ RD, LD, LU, RU, RD, 0, 0, 0 }, // Tablica połączeń węzłów dla kolejnych cyfr, 0
{ LM, RU, RD, 0, 0, 0, 0, 0 }, // 1
{ LU, RU, RM, LD, RD, 0, 0, 0 }, // 2
{ LU, RU, LM, RM, LD, 0, 0, 0 }, // 3
{ RU, LM, RM, RD, 0, 0, 0, 0 }, // 4
{ RU, LU, LM, RM, LD, 0, 0, 0 }, // 5
{ RU, LM, LD, RD, RM, LM, 0, 0 }, // 6
{ LU, RU, LD, 0, 0, 0, 0, 0 }, // 7
{ LM, LU, RU, RD, LD, LM, RM, 0 }, // 8
{ RM, LM, LU, RU, RM, LD, 0, 0 } // 9
};
void setup() {
pwmX.begin(480000.0f, 0.0f); // Częstotliwość pracy X = 480 kHz = 100 kroków.
pwmY.begin(480000.0f, 0.0f); // Częstotliwość pracy Y = 480 kHz = 100 kroków.
randomSeed(analogRead(A0)); // Inicjalizacja generatora losowego szumem z pinu A0.
}
void loop() {
unsigned long currentMillis = millis(); // Sczytaj czas systemowy.
if (currentMillis - prevMillis >= refresh) { // Jeśli minął czas trwania jednej klatki gry...
prevMillis = currentMillis; // Przenieś sczytany czas systemowy do zmiennej do porównań.
paddleRCenterY = map(analogRead(pot), 0, 1023, paddleLength, paddleTop); // Oblicz położenie środka prawej paletki (gracza).
ballX += ballXSpeed; // Wyznacz nowy adres X piłki.
ballY += ballYSpeed; // Wyznacz nowy adres Y piłki.
if (ballY > paddleLCenterY) { // Jeśli piłka znajduje się powyżej środka lewej paletki...
paddleLCenterY += random(1, 3); // Przesuń lewą paletkę (komputera) w górę.
if (paddleLCenterY > paddleTop) paddleLCenterY = paddleTop; // Zabezpiecz się na wypadek przekroczenia górnego zakresu ekranu.
} else if (ballY < paddleLCenterY) { // Jeśli piłka znajduje się poniżej środka lewej paletki...
paddleLCenterY -= random(1, 3); // Przesuń lewą paletkę (komputera) w dół.
if (paddleLCenterY < paddleLength) paddleLCenterY = paddleLength; // Zabezpiecz się na wypadek przekroczenia dolnego zakresu ekranu (chroni przed znikaniem).
}
if (ballY >= 100) { // Jeśli piłka osiągnęła górną krawędź...
ballYSpeed = -ballYSpeed; // Zmień kierunek poruszania się piłki w dół.
ballY = 100; // W razie przekroczenia zakresu ustaw piłkę na górze ekranu
} else if (ballY <= 0) { // Jeśli piłka osiągnęła dolną krawędź...
ballYSpeed = -ballYSpeed; // Zmień kierunek poruszania się piłki w górę.
ballY = 0; // W razie przekroczenia zakresu ustaw piłkę na dole ekranu.
}
if (ballXSpeed < 0 && ballX <= 2 && abs(ballY - paddleLCenterY) <= paddleLength) { // Odbij piłkę jeśli leci w lewo i trafia w komputer...
ballYSpeed = (ballY - paddleLCenterY + random(-4, 5)) / 2; // Wyznacz nowy kąt z przesunięcia od środka paletki komputera.
hitBall(); // Dokończ działania w podprogramie.
} else if (ballXSpeed > 0 && ballX >= 98 && abs(ballY - paddleRCenterY) <= paddleLength) { // Odbij piłkę jeśli leci w prawo i trafia w gracza...
ballYSpeed = (ballY - paddleRCenterY) / 2; // Wyznacz nowy kąt z przesunięcia od środka paletki gracza.
hitBall(); // Dokończ działania w podprogramie.
}
if (ballX < 0 || ballX > 100) { // Jeśli piłka nie została przechwycona paletką (znalazła się poza ekranem w osi X)...
if (ballX < 0) scoreR++; // Piłka wypadła z lewej strony - punkt zdobywa gracz.
else scoreL++; // Piłka wypadła z prawej strony - punkt zdobywa komputer.
if (scoreL > 9 || scoreR > 9) { // Jeśli jeden z liczników przekroczył maksimum...
scoreL = 0; // Zeruj oba.
scoreR = 0;
}
ballYSpeed = random(-3, 4); // Losuj kąt rzutu.
ballCount = 0; // Zresetuj licznik odbić piłki dla nowej rundy.
if (ballX < 0) { // Jeśli wygrał gracz po prawej stronie...
ballX = 98; // Ustaw piłkę przed paletką gracza.
ballY = paddleRCenterY; // Piłka startuje z wysokości środka paletki gracza.
ballXSpeed = -2; // Skieruj piłkę w lewo.
} else { // Jeśli wygrał komputer po lewej stronie...
ballX = 2; // Ustaw piłkę przed paletką komputera.
ballY = paddleLCenterY; // Piłka startuje z wysokości środka paletki komputera.
ballXSpeed = 2; // Skieruj piłkę w prawo.
}
}
}
pwmX.pulse_perc(0); // Rysuj lewą paletkę (komputera, od góry do dołu).
int n = paddleLCenterY + paddleLength; // Oblicz najwyższy punkt lewej paletki.
do { // Rysuj serię kropek, które stanowią obraz paletki.
pwmY.pulse_perc(n); // Wyślij adres Y kolejnej kropki paletki.
delayMicroseconds(300); // Wprowadź opóźnienie dające wyraźny rysunek paletki.
} while (--n >= (paddleLCenterY - paddleLength)); // Powtarzaj dopóki nie narysujesz całej paletki.
pwmX.pulse_perc(ballX); // Wyślij adres X pozycji piłki.
pwmY.pulse_perc(ballY); // Wyślij adres Y pozycji piłki.
delayMicroseconds(1000); // Wprowadź opóźnienie dające wyraźny rysunek piłki.
pwmX.pulse_perc(100); // Rysuj prawą paletkę (gracza, od dołu do góry).
n = paddleRCenterY - paddleLength; // Oblicz najniższy punkt prawej paletki.
do { // Rysuj serię kropek, które stanowią obraz paletki.
pwmY.pulse_perc(n); // Wyślij adres Y kolejnej kropki paletki.
delayMicroseconds(300); // Wprowadź opóźnienie dające wyraźny rysunek paletki.
} while (++n <= (paddleRCenterY + paddleLength)); // Powtarzaj dopóki nie narysujesz całej paletki.
drawDigit(scoreL, 15); // Rysuj wynik komputera, pozycja X
drawDigit(scoreR, 65); // Rysuj wynik gracza, pozycja X
drawCourt(); // Rysuj obrys boiska.
}
void hitBall() {
ballXSpeed = -ballXSpeed; // Zmień kierunek poruszania piłki.
ballCount++; // Zwiększ licznik odbić.
if (ballCount == 10) ballXSpeed *= 2; // Jeśli to dziesiąte odbicie, podwój szybkość poruszania się piłki.
}
void drawCourt() { // Rysuj obrys boiska.
byte x = 50; // Punkty startowe.
byte y = 0;
pwmX.pulse_perc(x); // Środkowa pionowa w górę.
do {
pwmY.pulse_perc(y);
delayMicroseconds(5);
} while (++y <= 99);
pwmY.pulse_perc(y); // Lewa górna w lewo.
do {
pwmX.pulse_perc(x);
delayMicroseconds(5);
} while (x-- > 1);
pwmX.pulse_perc(x); // Lewa pionowa w dół.
do {
pwmY.pulse_perc(y);
delayMicroseconds(5);
} while (y-- > 1);
pwmY.pulse_perc(y); // Dolna w prawo.
do {
pwmX.pulse_perc(x);
delayMicroseconds(5);
} while (++x <= 99);
pwmX.pulse_perc(x); // Prawa pionowa w górę.
do {
pwmY.pulse_perc(y);
delayMicroseconds(5);
} while (++y <= 99);
pwmY.pulse_perc(y); // Prawa górna w lewo.
do {
pwmX.pulse_perc(x);
delayMicroseconds(5);
} while (x-- > 51);
}
void drawDigit(byte digit, byte xOffset) { // Odzyskaj cyfrę oraz pozycję rysowania.
byte step = 0; // Numer kolejnego odcinka.
while (step < 7) { // Analizuj po kolei wszystkie odcinki.
byte beginNode = vectors[digit][step]; // Węzeł, od którego należy rysować odcinek.
byte endNode = vectors[digit][step + 1]; // Węzeł, do którego należy rysować odcinek.
if (endNode == 0) { // Jeśli węzeł docelowy równy jest zero...
break; // Zakończ rysowanie.
}
byte beginX = xOffset + nodeX[beginNode]; // Początek odcinka w osi X.
byte beginY = nodeY[beginNode]; // Początek odcinka w osi Y.
byte endX = xOffset + nodeX[endNode]; // Koniec odcinka w osi X.
byte endY = nodeY[endNode]; // Koniec odcinka w osi Y.
for (byte nextStep = 0; nextStep <= 10; nextStep++) { // Rysuj wybrany odcinek krok po kroku, dobierz współczynnik dla płynności.
byte x = beginX + ((endX - beginX) * nextStep) / 10; // Oblicz napięcie dla kolejnego punktu osi X
byte y = beginY + ((endY - beginY) * nextStep) / 10; // Oblicz napięcie dla kolejnego punktu osi Y
pwmX.pulse_perc(x); // Wystaw napięcie dla osi X
pwmY.pulse_perc(y); // Wystaw napięcie dla osi Y
delayMicroseconds(25); // Wprowadź opóźnienie dające wyraźny rysunek cyfr.
}
step++; // Analizuj kolejny odcinek.
}
}
Wśród zmiennych ujrzymy dwa liczniki punków – dla komputera i gracza. Oraz wszystko, co jest związane z wyświetlaniem cyfr – dokładnie skopiowane z projektu, który opisywałem, więc zainteresowanych zapraszam do wspomnianego artykułu.
W głównej pętli pojawią się nowe wątki. Zaczniemy od naliczania punktów z chwilą, gdy piłka nie zostanie odbita. Zaraz potem mamy pułapkę na przepełnienie licznika, tam zresztą wrócę za chwilę. Samo rysowanie będzie obsługiwane w podprogramie, natomiast odwołanie nastąpi po rysowaniu paletek, a przed rysowaniem boiska. Z wejściem w podprogram można poeksperymentować, na przykład wstawiając między cyfry procedurę rysowania piłki dostaniemy smugę, która łączy paletki, liczniki i piłkę. Mnie takie rozwiązanie wydaje się najciekawsze wizualnie. Wspomniany podprogram rysujący cyfry nie różni się od omawianego kiedyś, więc także pominę analizę. Jedyną nowością jest tutaj dodanie opóźnienia dla zwiększenia jasności i stabilności rysunku cyfr.
No właśnie, zanim skompilujemy kod, przeanalizujmy opóźnienia. Przyznaję, że rozpędziłem się z nimi trochę i suma jednego przebiegu pętli zbliża się do 40 ms. Współczynnik odświeżania klatek jest dużo niższy i stał się on obecnie tworem sztucznym, bo czas rysowania klatki jest dwa razy dłuższy. Całość pracuje wolniej niż zakładałem, co nie znaczy że jest źle, ale niezgodnie ze sztuką. Trzeba poskracać niektóre opóźnienia.
#include "pwm.h" // Biblioteka PWM dla architektury Uno R4
const byte pot = A1; // Port potencjometru sterującego prawą paletką (gracz).
PwmOut pwmX(10); // Obiekt sterujący sprzętowym PWM dla pinu 10 (oś X).
PwmOut pwmY(9); // Obiekt sterujący sprzętowym PWM dla pinu 9 (oś Y).
const byte paddleLength = 8; // Połowa szerokości paletki.
const byte paddleTop = 100 - paddleLength; // Górna granica ruchu środka paletki.
byte paddleLCenterY = 50; // Środek lewej paletki (komputera).
byte paddleRCenterY = 50; // Środek prawej paletki (gracza).
int ballX = 50; // Pozycja X piłki.
int ballY = 50; // Pozycja Y piłki.
int ballXSpeed = 2; // Prędkość pozioma piłki i jej kierunek.
int ballYSpeed = 1; // Prędkość pionowa piłki i jej kierunek.
unsigned long prevMillis = 0; // Czas poprzedniego stanu zegara systemowego.
const unsigned long refresh = 20; // Czas odświeżania logiki gry w ms.
int ballCount = 0; // Licznik odbić piłki od paletek.
byte scoreL = 0; // Wynik lewej paletki (komputer)
byte scoreR = 0; // Wynik prawej paletki (gracz)
unsigned long pause = 0; // Pauza po zdobyciu punktu.
#define XL 0 // Definicje położenia: Lewy punkt pierwszej cyfry.
#define XR 20 // Prawy punkt pierwszej cyfry.
#define YD 30 // Dolny punkt siatki odcinków.
#define YM 50 // Środkowy punkt siatki odcinków.
#define YU 70 // Górny punkt siatki odcinków.
const byte nodeX[7] = { 0, XL, XR, XL, XR, XL, XR }; // Mapowanie węzłów na współrzędne X...
const byte nodeY[7] = { 0, YU, YU, YM, YM, YD, YD }; // oraz Y
#define LU 1 // Definicje indeksów węzłów według klucza: Left-Up itd
#define RU 2
#define LM 3
#define RM 4
#define LD 5
#define RD 6
const byte vectors[10][8] = {
{ RD, LD, LU, RU, RD, 0, 0, 0 }, // Tablica połączeń węzłów dla kolejnych cyfr, 0
{ LM, RU, RD, 0, 0, 0, 0, 0 }, // 1
{ LU, RU, RM, LD, RD, 0, 0, 0 }, // 2
{ LU, RU, LM, RM, LD, 0, 0, 0 }, // 3
{ RU, LM, RM, RD, 0, 0, 0, 0 }, // 4
{ RU, LU, LM, RM, LD, 0, 0, 0 }, // 5
{ RU, LM, LD, RD, RM, LM, 0, 0 }, // 6
{ LU, RU, LD, 0, 0, 0, 0, 0 }, // 7
{ LM, LU, RU, RD, LD, LM, RM, 0 }, // 8
{ RM, LM, LU, RU, RM, LD, 0, 0 } // 9
};
void setup() {
pwmX.begin(480000.0f, 0.0f); // Częstotliwość pracy X = 480 kHz = 100 kroków.
pwmY.begin(480000.0f, 0.0f); // Częstotliwość pracy Y = 480 kHz = 100 kroków.
randomSeed(analogRead(A0)); // Inicjalizacja generatora losowego szumem z pinu A0.
}
void loop() {
unsigned long currentMillis = millis(); // Sczytaj czas systemowy.
if (currentMillis - prevMillis >= refresh) { // Jeśli minął czas trwania jednej klatki gry...
prevMillis = currentMillis; // Przenieś sczytany czas systemowy do zmiennej do porównań.
paddleRCenterY = map(analogRead(pot), 0, 1023, paddleLength, paddleTop); // Oblicz położenie środka prawej paletki (gracza).
if (currentMillis >= pause) { // Jeśli nie trwa pauza po zdobyciu punktu...
ballX += ballXSpeed; // Wyznacz nowy adres X piłki.
ballY += ballYSpeed; // Wyznacz nowy adres Y piłki.
if (ballY > paddleLCenterY) { // Jeśli piłka znajduje się powyżej środka lewej paletki...
paddleLCenterY += random(1, 3); // Przesuń lewą paletkę (komputera) w górę.
if (paddleLCenterY > paddleTop) paddleLCenterY = paddleTop; // Zabezpiecz się na wypadek przekroczenia górnego zakresu ekranu.
} else if (ballY < paddleLCenterY) { // Jeśli piłka znajduje się poniżej środka lewej paletki...
paddleLCenterY -= random(1, 3); // Przesuń lewą paletkę (komputera) w dół.
if (paddleLCenterY < paddleLength) paddleLCenterY = paddleLength; // Zabezpiecz się na wypadek przekroczenia dolnego zakresu ekranu (chroni przed znikaniem).
}
if (ballY >= 100) { // Jeśli piłka osiągnęła górną krawędź...
ballYSpeed = -ballYSpeed; // Zmień kierunek poruszania się piłki w dół.
ballY = 100; // W razie przekroczenia zakresu ustaw piłkę na górze ekranu
} else if (ballY <= 0) { // Jeśli piłka osiągnęła dolną krawędź...
ballYSpeed = -ballYSpeed; // Zmień kierunek poruszania się piłki w górę.
ballY = 0; // W razie przekroczenia zakresu ustaw piłkę na dole ekranu.
}
if (ballXSpeed < 0 && ballX <= 2 && abs(ballY - paddleLCenterY) <= paddleLength) { // Odbij piłkę jeśli leci w lewo i trafia w komputer...
ballYSpeed = (ballY - paddleLCenterY + random(-4, 5)) / 2; // Wyznacz nowy kąt z przesunięcia od środka paletki komputera.
hitBall(); // Dokończ działania w podprogramie.
} else if (ballXSpeed > 0 && ballX >= 98 && abs(ballY - paddleRCenterY) <= paddleLength) { // Odbij piłkę jeśli leci w prawo i trafia w gracza...
ballYSpeed = (ballY - paddleRCenterY) / 2; // Wyznacz nowy kąt z przesunięcia od środka paletki gracza.
hitBall(); // Dokończ działania w podprogramie.
}
if (ballX < 0 || ballX > 100) { // Jeśli piłka nie została przechwycona paletką (znalazła się poza ekranem w osi X)...
if (ballX < 0) scoreR++; // Piłka wypadła z lewej strony - punkt zdobywa gracz.
else scoreL++; // Piłka wypadła z prawej strony - punkt zdobywa komputer.
if (scoreL > 9 || scoreR > 9) { // Jeśli jeden z liczników przekroczył maksimum...
scoreL = 0; // Zeruj oba.
scoreR = 0;
}
ballYSpeed = random(-3, 4); // Losuj kąt rzutu.
ballCount = 0; // Zresetuj licznik odbić piłki dla nowej rundy.
if (ballX < 0) { // Jeśli wygrał gracz po prawej stronie...
ballX = 98; // Ustaw piłkę przed paletką gracza.
ballY = paddleRCenterY; // Piłka startuje z wysokości środka paletki gracza.
ballXSpeed = -2; // Skieruj piłkę w lewo.
} else { // Jeśli wygrał komputer po lewej stronie...
ballX = 2; // Ustaw piłkę przed paletką komputera.
ballY = paddleLCenterY; // Piłka startuje z wysokości środka paletki komputera.
ballXSpeed = 2; // Skieruj piłkę w prawo.
}
pause = millis() + 1000; // Aktywuj pauzę.
}
}
}
pwmX.pulse_perc(0); // Rysuj lewą paletkę (komputera, od góry do dołu).
int n = paddleLCenterY + paddleLength; // Oblicz najwyższy punkt lewej paletki.
do { // Rysuj serię kropek, które stanowią obraz paletki.
pwmY.pulse_perc(n); // Wyślij adres Y kolejnej kropki paletki.
delayMicroseconds(300); // Wprowadź opóźnienie dające wyraźny rysunek paletki.
} while (--n >= (paddleLCenterY - paddleLength)); // Powtarzaj dopóki nie narysujesz całej paletki.
pwmX.pulse_perc(ballX); // Wyślij adres X pozycji piłki.
pwmY.pulse_perc(ballY); // Wyślij adres Y pozycji piłki.
delayMicroseconds(1000); // Wprowadź opóźnienie dające wyraźny rysunek piłki.
pwmX.pulse_perc(100); // Rysuj prawą paletkę (gracza, od dołu do góry).
n = paddleRCenterY - paddleLength; // Oblicz najniższy punkt prawej paletki.
do { // Rysuj serię kropek, które stanowią obraz paletki.
pwmY.pulse_perc(n); // Wyślij adres Y kolejnej kropki paletki.
delayMicroseconds(300); // Wprowadź opóźnienie dające wyraźny rysunek paletki.
} while (++n <= (paddleRCenterY + paddleLength)); // Powtarzaj dopóki nie narysujesz całej paletki.
drawDigit(scoreL, 15); // Rysuj wynik komputera, pozycja X
drawDigit(scoreR, 65); // Rysuj wynik gracza, pozycja X
drawCourt(); // Rysuj obrys boiska.
}
void hitBall() {
ballXSpeed = -ballXSpeed; // Zmień kierunek poruszania piłki.
ballCount++; // Zwiększ licznik odbić.
if (ballCount == 10) ballXSpeed *= 2; // Jeśli to dziesiąte odbicie, podwój szybkość poruszania się piłki.
}
void drawCourt() { // Rysuj obrys boiska.
byte x = 50; // Punkty startowe.
byte y = 0;
pwmX.pulse_perc(x); // Środkowa pionowa w górę.
do {
pwmY.pulse_perc(y);
delayMicroseconds(5);
} while (++y <= 99);
pwmY.pulse_perc(y); // Lewa górna w lewo.
do {
pwmX.pulse_perc(x);
delayMicroseconds(5);
} while (x-- > 1);
pwmX.pulse_perc(x); // Lewa pionowa w dół.
do {
pwmY.pulse_perc(y);
delayMicroseconds(5);
} while (y-- > 1);
pwmY.pulse_perc(y); // Dolna w prawo.
do {
pwmX.pulse_perc(x);
delayMicroseconds(5);
} while (++x <= 99);
pwmX.pulse_perc(x); // Prawa pionowa w górę.
do {
pwmY.pulse_perc(y);
delayMicroseconds(5);
} while (++y <= 99);
pwmY.pulse_perc(y); // Prawa górna w lewo.
do {
pwmX.pulse_perc(x);
delayMicroseconds(5);
} while (x-- > 51);
}
void drawDigit(byte digit, byte xOffset) { // Odzyskaj cyfrę oraz pozycję rysowania.
byte step = 0; // Numer kolejnego odcinka.
while (step < 7) { // Analizuj po kolei wszystkie odcinki.
byte beginNode = vectors[digit][step]; // Węzeł, od którego należy rysować odcinek.
byte endNode = vectors[digit][step + 1]; // Węzeł, do którego należy rysować odcinek.
if (endNode == 0) { // Jeśli węzeł docelowy równy jest zero...
break; // Zakończ rysowanie.
}
byte beginX = xOffset + nodeX[beginNode]; // Początek odcinka w osi X.
byte beginY = nodeY[beginNode]; // Początek odcinka w osi Y.
byte endX = xOffset + nodeX[endNode]; // Koniec odcinka w osi X.
byte endY = nodeY[endNode]; // Koniec odcinka w osi Y.
for (byte nextStep = 0; nextStep <= 10; nextStep++) { // Rysuj wybrany odcinek krok po kroku, dobierz współczynnik dla płynności.
byte x = beginX + ((endX - beginX) * nextStep) / 10; // Oblicz napięcie dla kolejnego punktu osi X
byte y = beginY + ((endY - beginY) * nextStep) / 10; // Oblicz napięcie dla kolejnego punktu osi Y
pwmX.pulse_perc(x); // Wystaw napięcie dla osi X
pwmY.pulse_perc(y); // Wystaw napięcie dla osi Y
delayMicroseconds(25); // Wprowadź opóźnienie dające wyraźny rysunek cyfr.
}
step++; // Analizuj kolejny odcinek.
}
}Krytyczne są te w pętlach rysowania paletek: 1000 mikrosekund zjada nam 17 ms na paletkę. Skróćmy je do 300 ms. Teraz całość w najgorszym razie będzie potrzebować 16 i pół milisekundy, a to pozostawia margines rezerw dla odświeżania równego 20 ms. Same obliczenia wykonują się bardzo szybko, wszak używamy kontrolera trzydziestodwubitowego.
Do ostatniego szlifu brakuje nam pauzy pomiędzy stratami punktów. Wprowadźmy jednosekundową przerwę, ale nie można tego zrobić, zawieszając program na sekundę, bo wszystko nam zniknie. W tym celu trzeba powołać jeszcze jedną zmienną związaną z zegarem systemowym. Wszystkie dynamiczne obliczenia związane z procesem gry będziemy wykonywać pod warunkiem, że pauza nie jest aktywowana. W razie straty punków aktywuje się ją, dodając do licznika 1000 ms. W tym czasie cały ten blok będzie przeskakiwany i program od razu przejdzie do procedur rysowania grafik, bez ich odświeżania.
No to mamy gotowego Ponga. Obiecuję nic już nie zmieniać na ekranie, ale nasz Pong nie wydaje żadnych dźwięków, a fajnie byłoby, gdyby uderzenia paletek wydawały odgłosy. W kolejnym, ostatnim już artykule serii, zajmiemy się tym.

Inne artykuły z tej kategorii
Fotograficzny kalkulator raz jeszcze









































































