I recently received a S65 Shield from watterott.com and I wanted a simple project to learn how to control it.
So I wrote a game of Pong. Check out the video here : http://www.codetorment.com/2009/11/11/arduino-pong-using-s65-shield/. I'll post the code once I've cleaned it up.
I taught it would be a nice idea to have some sound to go with the visuals. I had a buzzer lying around, I just forgot how painfully loud and irritating it can be >:(. The sound in the video doesn't do justice to the high pitch and loud volume this buzzer produces. That's why the video only last for 30 something seconds. Check for yourself Arduino Pong on S65 Shield with Sound on Vimeo
I still didn't get around to cleaning up the code, I've just added some comments...
#include <S65Display.h>
#include <RotaryEncoder.h>
#include <string.h>
#define WIDTH 176
#define HEIGHT 132
#define BAT_WIDTH 5
#define BAT_HEIGHT 30
#define BAT_INIT_X 5
#define BAT_INIT_Y 56
#define BALL 5
#define BAT_STEP 5
#define AI_STEP 2
#define MAX_TURNS 5
float ball_x = WIDTH / 2;
float ball_x_prev = ball_x;
float ball_y = HEIGHT / 2;
float ball_y_prev = ball_y;
float ball_dir = 0;
float bat_1_dir = 0;
int bat1_x = BAT_INIT_X;
int bat1_y = BAT_INIT_Y;
int bat1_y_prev = bat1_y;
int bat2_x = WIDTH - BAT_INIT_X - BAT_WIDTH;
int bat2_y = BAT_INIT_Y;
int bat2_y_prev = bat2_y;
int bat2_step = AI_STEP;
int p1_score = 0;
int p2_score = 0;
int ball_angle = 0; // angle between ball path and horizontal axis
int ball_angle_sign = 0; // sign of ball_angle : up = +1, down = -1, horizontal = 0
boolean score = false;
boolean gameover = false;
char buffer[256]; // String buffer
S65Display lcd;
RotaryEncoder encoder;
// Encoder must be serviced regularly.
ISR(TIMER2_OVF_vect)
{
TCNT2 -= 250; //1000 Hz
encoder.service();
}
void setup()
{
lcd.init(2);
encoder.init();
// More encoder stuff
//init Timer2
TCCR2B = (1<<CS22); //clk=F_CPU/64
TCNT2 = 0x00;
TIMSK2 |= (1<<TOIE2); //enable overflow interupt
score = false;
gameover = false;
lcd.clear(0);
initTurn();
startGame();
}
void loop(){
while(!score && !gameover){
checkControls();
updateAI();
updateFields();
drawScreen();
delay(10);
}
if(checkPress() && !gameover){ // if a player scored wait for rotary press
initTurn();
}
delay(10);
}
// initialize fields for the beginning of game/round
void initTurn(void){
lcd.clear(0);
ball_x = WIDTH / 2;
ball_x_prev = ball_x;
ball_y = HEIGHT / 2;
ball_y_prev = ball_y;
ball_dir = 0;
bat1_x = BAT_INIT_X;
bat1_y = BAT_INIT_Y;
bat1_y_prev = bat1_y;
bat2_x = WIDTH - BAT_INIT_X - BAT_WIDTH;
bat2_y = BAT_INIT_Y;
bat2_y_prev = bat2_y;
ball_angle = 0; // angle between ball path and horizontal axis
ball_angle_sign = 0; // up = +1, down = -1, horizontal = 0
score = false;
ball_dir = 1;
ball_angle = 45;
}
// start the game and wait for press on rotary encoder
void startGame(void){
lcd.drawText(35, 60, "Click to start", RGB(255,255,255), RGB(0,0,0));
while(!checkPress()){
delay(10);
};
lcd.clear(0);
}
boolean checkPress(void){ // checks if rotary encoder was pressed
int8_t press;
press = encoder.sw();
if (SW_PRESSED == press || SW_PRESSEDLONG == press) {
return true;
}
else{
return false;
}
}
void checkRotation(void){
moveBat(encoder.step());
}
void checkControls(void){
if(checkPress()){ // pause
lcd.drawText(45, 60, "Game paused", RGB(255,255,255), RGB(0,0,0));
while(!checkPress()){
}
lcd.clear(0);
}
checkRotation();
}
void moveBat(int rot){
if(!score && !gameover){
if(rot == 1){ // clockwise rotation, move bat up
if(bat1_y - BAT_STEP >= 0){ // only move up when there is enough space left
bat1_y -= BAT_STEP;
}
}
else if(rot == -1){ // anti-clockwise rotation, move bat down
if((bat1_y + BAT_HEIGHT + BAT_STEP) < HEIGHT){ // only move down when there is enough space left
bat1_y += BAT_STEP;
}
}
}
}
void updateFields(void){
if(ball_y <= 0 || ball_y + BALL >= HEIGHT){ // top or bottom of screen reached, bounce ball back
if(ball_angle_sign == -1){ // ball was going down
ball_angle_sign = 1; // ball is now going up
}
else{ // ball was going up
ball_angle_sign = -1; // ball is now going down
}
}
if((ball_x <= 12 && ball_x >= 11) && (ball_dir == 1)){ // check if ball has is in reach of bat1 (horizontal), for some reason checking for equality doens't seem to work here ?
if((ball_y + BALL) >= bat1_y && ball_y <= (bat1_y + BAT_HEIGHT)){ // ball is in reach of bat1 (vertical)
ball_dir = 0; // ball hits bat1, change direction
}
}
if((ball_x >= 160 && ball_x <= 162) && (ball_dir == 0)){ // check if ball has is in reach of bat2 (horizontal)
if((ball_y + BALL) >= bat2_y && ball_y <= (bat2_y + BAT_HEIGHT)){ // ball is in reach of bat2 (vertical)
ball_dir = 1; // ball hits bat2, change direction
}
}
if( ball_x > 0 && ball_x + BALL < WIDTH){ // ball is not near left or right edge
if(ball_dir == 0){ // ball is moving to the right
ball_x += cos(ball_angle); // cosine is always positive in 1st and 4th quadrant
if(ball_angle_sign == -1){ // check sign of angle
ball_y -= sin(ball_angle); // negative if angle in 4th quadrant
}
else{
ball_y += sin(ball_angle); // positive if angle in 1th quadrant
}
}
else{ // ball is moving to the left
ball_x -= cos(ball_angle); // cosine is always negative in 2nd and 3rd quadrant
if(ball_angle_sign == -1){
ball_y -= sin(ball_angle); // sine is always negative in 3rd quadrant
}
else{
ball_y += sin(ball_angle); // sine is always positive in 2nd quadrant
}
}
}
else{ // ball hits left or right edge
if(!score && !gameover){
if(ball_dir == 0){ // ball was going to the right
if(p1_score++ < MAX_TURNS){
p1_score++; // player 1 scores
}
else{
gameover = true;
}
}
else{ // ball was going to the left
if(p2_score < MAX_TURNS){
p2_score++; // player 2 scores
}
else{
gameover = true;
}
}
score = true;
}
}
}
void updateAI(void){
if(ball_y > (bat2_y + (BAT_HEIGHT / 2))){ // bat2 follows vertical position of ball, TODO : implement difficulty levels
if((bat2_y + BAT_HEIGHT + bat2_step) < HEIGHT){ // TODO : make function moveBat() that does the bound checking
bat2_y += bat2_step;
}
}
else{
if((bat2_y - bat2_step) >= 0){
bat2_y -= bat2_step;
}
}
}
void drawScreen(void){
// draw ball
lcd.drawRect( ball_x_prev, ball_y_prev, ball_x_prev + BALL, ball_y_prev + BALL, RGB(0,0,0));
lcd.drawRect( ball_x, ball_y, ball_x + BALL, ball_y + BALL, RGB(255,255,255));
ball_x_prev = ball_x;
ball_y_prev = ball_y;
// draw left bat
lcd.drawRect( bat1_x, bat1_y_prev, bat1_x + BAT_WIDTH, bat1_y_prev + BAT_HEIGHT, RGB(0,0,0));
lcd.drawRect( bat1_x, bat1_y, bat1_x + BAT_WIDTH, bat1_y + BAT_HEIGHT, RGB(255,255,255));
bat1_y_prev = bat1_y;
// draw right bat
lcd.drawRect( bat2_x, bat2_y_prev, bat2_x + BAT_WIDTH, bat2_y_prev + BAT_HEIGHT, RGB(0,0,0));
lcd.drawRect( bat2_x, bat2_y, bat2_x + BAT_WIDTH, bat2_y + BAT_HEIGHT, RGB(255,255,255));
bat2_y_prev = bat2_y;
drawScore();
}
void drawScore(void){
sprintf(buffer, "%d - %d", p1_score, p2_score);
lcd.drawText(72, 5, buffer, RGB(255,255,255), RGB(0,0,0));
}