// MusicPal Pong v1.1 // // Changes in 1.1 (2009-11-07): // - fix: paddles were not shown due to breaking changes in musicpal firmware // - update: header files to reflect on version change // // // An implementation of the popular game Pong by Agilo (Alessandro Lo-Presti), // created for the Freecom MusicPal on January 31st, 2009. // // E-mail: agilo3@gmail.com // Website: http://www.agilo.nl/ #include #include #include #include // c99 boolean data typeand defines. #include #include #include #include #include #include "splash.h" // Nice splash screen for framebuffer. #include "paused.h" // Nice paused screen for framebuffer. // TODO: split defines/structs/prototypes to a separate header file. #define MAX_STR_LENGTH 1024 struct disp_string_lcd { char str[MAX_STR_LENGTH]; int x; int y; int xend; unsigned int flags; unsigned int scrolljump; unsigned int scrolltime; unsigned int scrolldelay; unsigned int offs; }; // System font defines. #define STR_FLAG_FONT_8X8 0x00000000 #define STR_FLAG_INVERSE 0x00000001 #define STR_FLAG_FONT_6X8 0x00000002 #define STR_FLAG_FONT_24X24 0x00000004 // My own defines. #define LCD_REFRESH 0x4619 #define LCD_CLEAR 0x4624 #define LCD_DISP_STR 0x4622 #define LCD_DISP_LOGO 0x4623 #define LCD_BRIGHTNESS 0x4625 // Paddle directions. #define PAD_DIR_U 1 #define PAD_DIR_R 2 #define PAD_DIR_D 4 #define PAD_DIR_L 8 // TODO: Game setup implementation. struct game { unsigned int speed; } game = { 1 }; // Puck properties. struct puck { char ch[1]; int x; int y; int step; // Steps to take per frame int xdir; // Move direction on X axis. int ydir; // Move direction on Y axis. unsigned int hits; // Amount of hits endured by players. } puck = { "\x09", 64, 32, 1, PAD_DIR_R, PAD_DIR_D, 0 }; // Player properties. struct player { char name[16]; unsigned int score; int y; unsigned int type; // Future usage: 0 = human, 1 = CPU. } player[2] = { { "Player 1", 0, 12, 0 }, { "Player 2", 0, 33, 0 } }; // Function prototypes. void fbcmd(int cmd); void printfb(char *msg, int x, int y, int size, bool hl); void drawpaddles(); void drawpuck(); void movepaddles(char dir); void gamespeed(int hits); void movepuck(int count); void drawscores(); void drawsplash(); void drawpaused(); void drawline(); void screenblink(int count); // Framebuffer file descriptor. int fbfd; // Framebuffer command template (used for refresh/clear). void fbcmd(int cmd) { if (ioctl(fbfd, cmd, 0)) { printf("[!] Error issuing command: %d\n", cmd); } } // Write a text string to the framebuffer at a given position. void printfb(char *msg, int x, int y, int size, bool highlight) { struct disp_string_lcd disp_lcd; strcpy(disp_lcd.str, msg); disp_lcd.x = x; disp_lcd.y = y; // Various sizes to choose from. switch (size) { case 0: disp_lcd.flags = STR_FLAG_FONT_6X8; break; case 1: disp_lcd.flags = STR_FLAG_FONT_8X8; break; case 2: disp_lcd.flags = STR_FLAG_FONT_24X24; break; default: disp_lcd.flags = STR_FLAG_FONT_6X8; } // Determine whether or not the string needs to be highlighted. //if (highlight) disp_lcd.flags |= STR_FLAG_INVERSE; // Temporarily disabled due to breaking changes in latest firmwares. // Send the command to the framebuffer. if (ioctl(fbfd, LCD_DISP_STR, &disp_lcd)) { errx(-1, "[!] Error: couldn't send text to framebuffer."); } } // Draw a nice framebuffered splash/intro screen. void drawsplash() { if (ioctl(fbfd, LCD_DISP_LOGO, &splash_data)) { // If anything went wrong, fall back to plain text. printfb(" MusicPal Pong v1.1 ", 4, 11, 0, true); printfb("[MENU] = Start Game", 6, 30, 0, false); printfb("Made by: Agilo", 22, 48, 0, false); } } // Draw a nice framebuffered paused screen. void drawpaused() { if (ioctl(fbfd, LCD_DISP_LOGO, &paused_data)) { printfb(" MusicPal Pong v1.1 ", 4, 11, 0, true); printfb("Paused", 48, 30, 0, false); printfb("By: Agilo (agilo.nl)", 5, 52, 0, false); } } // Draw the paddles. void drawpaddles() { int i, j; for (i=1; i>=0; i--) { // Guards in case the paddles leave the screen. if (player[i].y >= 40) player[i].y = 40; if (player[i].y <= 0) player[i].y = 0; // Draw paddles. for (j=0; j<3; j++) printfb("\xDB", ((i==0)?0:120), player[i].y+(j<<3), 1, false); } } // Draw the puck. void drawpuck() { printfb(puck.ch, puck.x, puck.y, 1, false); } // Move the paddles up/down (limit screen edges). void movepaddles(char dir) { int i; for (i=1; i>=0; i--) { // Detect which direction to move and set y coordinate accordingly. if (dir == ((i==0)?'V':'n')) player[i].y += 3; if (dir == ((i==0)?'v':'N')) player[i].y -= 3; } } // Set the game speed based on the amount of puck hits. void gamespeed(int hits) { // TODO: implement game speed changes. // Temporary rudimentary speed changes based on puck steps. if (hits < 2) { puck.step = 1; } else if (hits == 2 || hits == 6) { puck.step <<= 1; } } // Move the puck around the screen. // TODO: English effect. void movepuck(int count) { // Do it as many times as needed. while (count > 0) { // Calculate incremental position based on step. puck.x += ((puck.xdir == PAD_DIR_R) ? puck.step : -puck.step); puck.y += ((puck.ydir == PAD_DIR_D) ? puck.step : -puck.step); // Figure out the direction if in contact with ceiling/floor. if (puck.ydir == PAD_DIR_U && puck.y < 1) puck.ydir = PAD_DIR_D; if (puck.ydir == PAD_DIR_D && puck.y > 57) puck.ydir = PAD_DIR_U; // figure out direction/score. if ((puck.xdir == PAD_DIR_L) && (puck.x < 7)) { // Check if there is contact with the paddle. if ((puck.y+5 > player[0].y) && (puck.y < player[0].y+24)) { // If so, then set direction. puck.xdir = PAD_DIR_R; // Figure out game speed. gamespeed(++puck.hits); } else { // Otherwise, there is a score! player[1].score++; puck.x = 64; gamespeed(puck.hits = 0); printfb("SCORE!", 76, 18, 1, false); drawscores(); fbcmd(LCD_REFRESH); screenblink(2); sleep(1); } } // figure out direction/score. if ((puck.xdir == PAD_DIR_R) && (puck.x > 113)) { // Check if there is contact with the paddle. if ((puck.y+5 > player[1].y) && (puck.y < player[1].y+24)) { // If so, then set direction. puck.xdir = PAD_DIR_L; // Figure out game speed. gamespeed(++puck.hits); } else { // Otherwise, there is a score! player[0].score++; puck.x = 64; gamespeed(puck.hits = 0); printfb("SCORE!", 6, 18, 1, false); drawscores(); fbcmd(LCD_REFRESH); screenblink(2); sleep(1); } } // Reset counter. count--; } } // Draw scores to the top of the screen. void drawscores() { char score1[6], score2[6]; unsigned int size1 = sizeof(score1), size2 = sizeof(score2); // Copy integers into string buffers. if (snprintf(score1, size1, " %d ", player[0].score) >= size1) errx(-1, "[!] Error: buf size %d is too small!", size1); if (snprintf(score2, size2, " %d ", player[1].score) >= size2) errx(-1, "[!] Error: buf size %d is too small!", size2); // Write string buffers to screen. printfb(score1, 23, 0, 1, false); printfb(score2, 82, 0, 1, false); } // Draw a vertical line in the middle of the screen. // This is unused as of yet due to problems pertaining to LCD refresh rate. void drawline(){ int i; for (i=0; i<4; i++) printfb("|", 63, 2+(i<<4), 1, false); } // Blink the screen (by way of lightening/darkening the LCD backlight). void screenblink(int count) { unsigned int num = 4, brght = 0; // Do it as many times as needed. while (count > 0) { // Dim the LCD backlight. do { brght = 1< 1); // Illuminate the LCD backlight. do { brght = 1< * * #define KNOBCON_IOC_MAGIC 'K' * #define KNOBCON_GET_VOLUME _IOR(KNOBCON_IOC_MAGIC, 1, unsigned long) * #define KNOBCON_GET_NAVI _IOR(KNOBCON_IOC_MAGIC, 2, unsigned long) * * int key; * if (ioctl(fbfd, KNOBCON_GET_VOLUME, &key)) { * printf("Error refreshing.\n"); * return -1; * } else { * printf("Key: %c\n", key); * } * * * I've spent a lot of time trying to get this to work, yet alas, to no avail. * * For more info look at knobcon_ioctl() as defined in (the firmware package): * alinux/linux-2.6.16.16/arch/arm/mach-mv88w8xx8/knobcon/knobcon.c * */ // Open framebuffer device. if ((fbfd = open("/dev/fb0", O_RDWR)) < 0) { errx(-1, "[!] Error: couldn't open /dev/fb0!\n"); } else { // Open knob controller device. if ((kbfd = open("/dev/knobcon", O_RDONLY|O_NDELAY|O_NONBLOCK)) < 0) { errx(-1, "[!] Error: couldn't open /dev/knobcon!\n"); } else { // Initialize brightness. unsigned int brght = 8; ioctl(fbfd, LCD_BRIGHTNESS, &brght); // Main loop. while (true) { // Read a character from knob controller device. if ((read(kbfd, &ch, 1) < 0) && (errno != EAGAIN)) { errx(-1, "[!] Error: couldn't read from kbfd!\n"); } else if (!started) { // Draw Intro/Splash screen. // If Volume knob is held down for a few seconds: exit and start Nashville // // if (ch == 'O') { // printf("Starting Nashville and exiting normally...\n"); // system("/etc/init.d/watching start && /etc/init.d/mainapp start"); // exit(0); // } // If menu button is pressed, start the game. if (ch == '2') started = true; // Draw splash screen. fbcmd(LCD_CLEAR); drawsplash(); fbcmd(LCD_REFRESH); // Initialize scores when started. if (started) { drawscores(); sleep(1); } // This is a hack to ensure the scores are shown. // Without this, scores won't be shown first time when called... // The delay is awkward/annoying, though.. I should look more into this. } else if (paused) { // Show paused screen. // Continue the game when pressing Menu or either knob buttons. if (ch == '2' || ch == '4' || ch == '8') paused = false; // Draw paused screen. fbcmd(LCD_CLEAR); drawpaused(); fbcmd(LCD_REFRESH); // Blink the screen every 45 ticks. if ((ch == 'T') && ((heartbeat++ % 45) == 0)) screenblink(1); // Reset heartbeat when exitig paused screen. if (!paused) heartbeat = 1; } else { // Main game routines. fbcmd(LCD_CLEAR); // Move the puck a couple of positions on ticker. if (ch == 'T') { movepuck( ((puck.step == 1) ? 4 : (puck.step == 2) ? 3 : 2) ); // Start playing automatically if inactive for over 120 ticks. if (idlecnt++ > 120) { if (puck.x < 64-14) player[0].y = puck.y-7; if (puck.x > 64+14) player[1].y = puck.y-7; } } else if (ch == '2' || ch == '4' || ch == '8') { // Show paused screen. paused = true; } else { // Knob wheel was turned (or key was pressed). movepaddles(ch); idlecnt = 0; } // Draw puck and paddles. drawpuck(); drawpaddles(); fbcmd(LCD_REFRESH); } } } close(kbfd); } close(fbfd); return 0; }