Index: apps/plugins/tictactoe.c =================================================================== --- apps/plugins/tictactoe.c (revision 0) +++ apps/plugins/tictactoe.c (revision 0) @@ -0,0 +1,360 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id: + * + * Copyright (C) 2007 Johnathon Mihalop + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +/* I have commented most of this in an attempt to help people learn how to make + their own plugins. This is the first one I have ever made, and I'd like + to help people with making their own. */ + + +/* Headers needed for plugins and for button presses */ +#include "plugin.h" +#include "pluginlib_actions.h" + +/* Needed for plugins */ +PLUGIN_HEADER +/* Checks to make sure the player is greyscale or higher */ +#if (LCD_DEPTH >= 2) + +/* Set the localised name of buttons against their reference in pluginlib_actions.h */ +#define TIC_QUIT PLA_QUIT +#define TIC_RIGHT PLA_RIGHT +#define TIC_LEFT PLA_LEFT +#define TIC_UP PLA_UP +#define TIC_DOWN PLA_DOWN +#define TIC_SELECT PLA_FIRE + +#else /* If the player is monochrome... */ + #error Unsupported screen /* Give an error in the compiler */ +#endif /* End checking to see if the player is monochrome or not */ + +/* Define all the values that will be needed for drawing the board and player tokens */ +#define MARGINY 15 +#define PLAYERONE MARGINY +#define PLAYERTWO (LCD_WIDTH - 44) +#define MARGINX ((LCD_WIDTH - RECT_SIZE * 3)) / 2 +#define RECT_SIZE_WIDTH (LCD_WIDTH - MARGIN*2)/3 +#define RECT_SIZE_HEIGHT (LCD_HEIGHT - MARGIN*2)/3 +#define TILE_MARGIN (RECT_SIZE / 3) +#define TILE_SIZE (RECT_SIZE / 2) +#define CUR_PLAYER (LCD_WIDTH / 2) - 4 +#define BOTTOM_OF_SCREEN (LCD_HEIGHT - MARGINY) + 3 +#if (RECT_SIZE_WIDTH > RECT_SIZE_HEIGHT) +#define RECT_SIZE (LCD_HEIGHT - MARGINY*2)/3 +#else +#define RECT_SIZE (LCD_WIDTH - MARGINY*2)/3 +#endif +#undef RECT_SIZE_WIDTH +#undef RECT_SIZE_LENGTH + +/* Define the functions */ +char buf[14]; +static struct plugin_api* rb; +void init_board(bool start); +static int main_loop(void); +void update_scores(void); +void reset_game(int x, int y); +void draw_cursor(int x, int y); +void mark_square(int x, int y); +int check_for_win(void); + +/* Make button mapping look for directional and non directional buttons */ +const struct button_mapping *plugin_contexts[] += {generic_directions, generic_actions}; + +/* Needed for the button mapping API */ +#define NB_ACTION_CONTEXTS \ + sizeof(plugin_contexts)/sizeof(struct button_mapping*) + +/* Global variables used in setting up the board and general gameplay */ +struct mainstruct board; + +struct mainstruct{ + int sections[3][3]; + int player; + int playerwins[3]; + int count_pieces; + int marker[3]; + int marker2[3]; +}; + +/* END OF DEFINES. From here on, the plugin actually starts from the bottom and +works up, starting at the line 'enum plugin_status...' */ + +/* Function to check whether or not the player has won, or if it's a draw */ +int check_for_win(void){ + int a, b; + board.count_pieces = 0; + /* This for statement checks to see if there are any horizontal or verticle + lines of 3 matching pieces. If there are it returns a '1' to say that + the player has won. */ + for(a = 0; a < 3; a++){ + if((board.sections[a][0] == board.sections[a][1]) && + (board.sections[a][0] == board.sections[a][2]) && + (board.sections[a][0] != 0)){ + return 1; + } else if((board.sections[0][a] == board.sections[1][a]) && + (board.sections[0][a] == board.sections[2][a]) && + (board.sections[0][a] != 0)){ + return 1; + } + /* This for statement counts up the amount of pieces on the board to + check it isn't a draw */ + for(b = 0; b < 3; b++){ + if(board.sections[a][b] != 0) + board.count_pieces = board.count_pieces + 1; + } + } + /* Yield to Rockbox to allow for other threads to run */ + rb->yield(); + /* These if statements check to see if the player has won by a diagonal + line */ + if((board.sections[0][2] == board.sections[1][1]) && + (board.sections[0][2] == board.sections[2][0]) && + (board.sections[0][2] != 0)){ + return 1; + } else if((board.sections[0][0] == board.sections[1][1]) + && (board.sections[0][0] == board.sections[2][2]) && + (board.sections[0][0] != 0)){ + return 1; + } + /* Checks to see if all the spaces on the board are full. If so, returns + a '2', meaning it's a draw */ + if(board.count_pieces == 9) + return 2; + return 0; +} + +/* Function to update the scores at the bottom of the screen */ +void update_scores(){ + rb->snprintf(buf, 14, "P1: %d", board.playerwins[1]); + rb->lcd_putsxy(PLAYERONE, BOTTOM_OF_SCREEN - 1, buf); + rb->snprintf(buf, 14, "P2: %d", board.playerwins[2]); + rb->lcd_putsxy(PLAYERTWO, BOTTOM_OF_SCREEN - 1, buf); + rb->lcd_update(); +} + +/* Function used after a game has been won or is a draw to wipe the screen and + redraw the board */ +void reset_game(int x, int y){ + rb->button_get(true); + init_board(false); + draw_cursor(x, y); +} + +/* This function is used to put a players token into the correct tile on the + board */ +void mark_square(int x, int y){ + int a, b; + /* This for loop goes through every number in the board.sections array to + check if the cursor is currently in that position, and if so, makes + sure it's empty. If it is empty, it draws the token into the space and + changes who's turn it currently is */ + for(a = 0; a < 3; a++){ + for(b = 0; b < 3; b++){ + /* If the loop is currently looking at the correct x position.. */ + if(x == (MARGINX + (RECT_SIZE * a))){ + /* And looking at the correct y position... */ + if(y == (MARGINY + (RECT_SIZE * b))){ + /* And if this position is currently empty... */ + if(board.sections[a][b] == 0){ + /* Mark that this position is now taken by the player */ + board.sections[a][b] = board.player; + /* And draw the token in for that player */ + rb->lcd_set_foreground(board.marker[board.player]); + rb->lcd_fillrect(x + (TILE_MARGIN), y + (TILE_MARGIN), + (TILE_MARGIN), (TILE_MARGIN)); + /* Draw the token at the bottom for who's turn it is */ + rb->lcd_fillrect(CUR_PLAYER, BOTTOM_OF_SCREEN, + 8, 8); + rb->lcd_update(); + /* Change turns */ + if(board.player == 1) + board.player = 2; + else + board.player = 1; + } + } + } + } + } +} + + +/* Draws the cursor when a button is pressed to move it */ +void draw_cursor(int x, int y) { + rb->lcd_set_drawmode(DRMODE_COMPLEMENT); + rb->lcd_fillrect(x, y, RECT_SIZE, RECT_SIZE); + rb->lcd_set_drawmode(DRMODE_SOLID); + rb->lcd_update(); +} + +/* This is the main loop function that is run for the entire game, until the + quit button is pressed */ +static int main_loop(void){ + bool quit = false; + int x = MARGINX + RECT_SIZE, y = MARGINY + RECT_SIZE, button; + draw_cursor(x, y); + update_scores(); + /* While the boolean 'quit' is equal to false (And only pressing the + quit button will ever make it true, so it'll only stop looping then.. */ + while (!quit){ + /* Makes sure that it is yielding often */ + rb->yield(); + /* Waits for a button press */ + button = pluginlib_getaction(rb, TIMEOUT_NOBLOCK, + plugin_contexts, NB_ACTION_CONTEXTS); + /* Checks which button it is and executes code for it */ + switch(button){ + /* If it's left, or if it's the left button held down... */ + case TIC_LEFT: + case (TIC_LEFT|BUTTON_REPEAT): + /* Get rid of the current cursor shown */ + draw_cursor(x, y); + /* Make sure the cursor doesn't go off the screen */ + if (x != MARGINX) + x = x - RECT_SIZE; + /* And redraw the cursor in it's new position */ + draw_cursor(x, y); + break; + /* Same as left for Right, Up and Down */ + case TIC_RIGHT: + case (TIC_RIGHT|BUTTON_REPEAT): + draw_cursor(x, y); + if (x != MARGINX + (RECT_SIZE * 2)) + x = x + RECT_SIZE; + draw_cursor(x, y); + break; + case TIC_UP: + case (TIC_UP|BUTTON_REPEAT): + draw_cursor(x, y); + if (y != MARGINY) + y = y - RECT_SIZE; + draw_cursor(x, y); + break; + case TIC_DOWN: + case (TIC_DOWN|BUTTON_REPEAT): + draw_cursor(x, y); + if (y != MARGINY + (RECT_SIZE * 2)) + y = y + RECT_SIZE; + draw_cursor(x, y); + break; + /* When the select button is pressed */ + case TIC_SELECT: + /* Run the mark square function using current X and Y values */ + mark_square(x, y); + /* Then check for a win... */ + switch(check_for_win()){ + /* If a 1 gets returned, display that they won */ + case 1: + rb->splash(HZ, "Player %d Wins!", board.player); + /* Add on a win for the winning player */ + board.playerwins[board.player] = + board.playerwins[board.player] + 1; + /* Reset the game and update the scores */ + reset_game(x, y); + update_scores(); + break; + /* If a 2 gets returned, display that it was a draw */ + case 2: + rb->splash(HZ, "It's a draw!"); + reset_game(x, y); + update_scores(); + break; + } + break; + /* If the quit button is pressed, break the loop */ + case TIC_QUIT: + quit = true; + break; + /* Checks to see if the USB cable was put in */ + default: + if (rb->default_event_handler(button) + == SYS_USB_CONNECTED) + return PLUGIN_USB_CONNECTED; + break; + } + } + /* Tells the plugin it can quit once the loop is broken */ + return PLUGIN_OK; +} + +/* Run to clear the screen and draw the board */ +void init_board(bool start){ + int x, y; + /* Clears the variables holding locations of player markers */ + for(x = 0; x < 3; x++){ + for(y = 0; y < 3; y++){ + board.sections[x][y] = 0; + } + } + /* Wipes the display and sets the drawing colour to black */ + rb->lcd_clear_display(); + rb->lcd_set_foreground(LCD_BLACK); + /* Draws 9 squares to make the board */ + for(x = 0; x < 3; x++){ + for(y = 0; y < 3; y++){ + rb->lcd_drawrect(MARGINX + (RECT_SIZE * x), + MARGINY + (RECT_SIZE * y), RECT_SIZE, RECT_SIZE); + } + } + /* Checks to see if it's the first time this function has been run */ + if(!start){ + /* If it's not the first time, it sets the foreground colour to + the colour of the current players turn */ + rb->lcd_set_foreground(board.marker2[board.player]); + } else { + /* Otherwise, it sets the drawing style to solid, sets the font, + background colour and sets the colour to the first player */ + rb->lcd_set_drawmode(DRMODE_SOLID); + rb->lcd_set_foreground(LCD_LIGHTGRAY); + rb->lcd_set_background(LCD_WHITE); + rb->lcd_setfont(FONT_SYSFIXED); + } + /* Draws the current players token colour at the bottom of the screen */ + rb->lcd_fillrect(CUR_PLAYER, BOTTOM_OF_SCREEN, 8, 8); + /* Puts the drawing colour back to the default black */ + rb->lcd_set_foreground(LCD_BLACK); + /* Updates the screen with everything just passed to it */ + rb->lcd_update(); +} + +/* This is where the plugin actually starts and progresses from */ +enum plugin_status plugin_start(struct plugin_api* api, void* parameter) +{ + /* This line is used if the plugin takes no parameters (Wouldn't be used + if you wanted to load savegames into it or similar) */ + (void)parameter; + /* Sets the following variables */ + board.marker[1] = LCD_LIGHTGRAY; + board.marker[2] = LCD_DARKGRAY; + board.marker2[1] = LCD_DARKGRAY; + board.marker2[2] = LCD_LIGHTGRAY; + board.player = 2; + /* Needed line for using the API easily */ + rb = api; + /* Clears the backdrop so you can see the board clearly */ + rb->lcd_set_backdrop(NULL); + /* Initialises the board passing the 'start' boolean as true. This causes + it to set things only needing to be set once */ + init_board(true); + /* Starts the running of the main loop */ + main_loop(); + /* Once the loop is finally broken, it returns a PLUGIN_OK, which returns + you back to Rockbox */ + return PLUGIN_OK; +} Index: apps/plugins/SOURCES =================================================================== --- apps/plugins/SOURCES (revision 14207) +++ apps/plugins/SOURCES (working copy) @@ -57,6 +57,10 @@ invadrox.c #endif +#if (LCD_DEPTH >= 2) +tictactoe.c +#endif + #if LCD_WIDTH != 128 /* These need adjusting for the iRiver if'p screen */ brickmania.c