| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * $Id$ |
| * |
| * Copyright (C) 2002 Philipp Pertermann |
| * |
| * 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. |
| * |
| ****************************************************************************/ |
| |
| #include "config.h" |
| #include "options.h" |
| |
| #ifdef USE_GAMES |
| |
| /* #define DEBUG_WORMLET */ |
| |
| #include <sprintf.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include "system.h" |
| #include "lcd.h" |
| #include "button.h" |
| #include "kernel.h" |
| #include "menu.h" |
| #include "rtc.h" |
| #include "lang.h" |
| #include "screens.h" |
| #include "font.h" |
| |
| /* size of the field the worm lives in */ |
| #define FIELD_RECT_X 1 |
| #define FIELD_RECT_Y 1 |
| #define FIELD_RECT_WIDTH (LCD_WIDTH - 45) |
| #define FIELD_RECT_HEIGHT (LCD_HEIGHT - 2) |
| |
| /* size of the ring of the worm |
| choos a value that is a power of 2 to help |
| the compiler optimize modul operations*/ |
| #define MAX_WORM_SEGMENTS 64 |
| |
| /* when the game starts */ |
| #define INITIAL_WORM_LENGTH 10 |
| |
| /* num of pixel the worm grows per eaten food */ |
| #define WORM_PER_FOOD 7 |
| |
| /* num of worms creeping in the FIELD */ |
| #define MAX_WORMS 3 |
| |
| /* minimal distance between a worm and an argh |
| when a new argh is made */ |
| #define MIN_ARGH_DIST 5 |
| |
| /** |
| * All the properties that a worm has. |
| */ |
| static struct worm { |
| /* The worm is stored in a ring of xy coordinates */ |
| char x[MAX_WORM_SEGMENTS]; |
| char y[MAX_WORM_SEGMENTS]; |
| |
| int head; /* index of the head within the buffer */ |
| int tail; /* index of the tail within the buffer */ |
| int growing; /* number of cyles the worm still keeps growing */ |
| bool alive; /* the worms living state */ |
| |
| /* direction vector in which the worm moves */ |
| int dirx; /* only values -1 0 1 allowed */ |
| int diry; /* only values -1 0 1 allowed */ |
| |
| /* this method is used to fetch the direction the user |
| has selected. It can be one of the values |
| human_player1, human_player2, remote_player, virtual_player. |
| All these values are fuctions, that can change the direction |
| of the worm */ |
| void (*fetch_worm_direction)(struct worm *w); |
| } worms[MAX_WORMS]; |
| |
| /* stores the highscore - besides it was scored by a virtual player */ |
| static int highscore; |
| |
| #define MAX_FOOD 5 /* maximal number of food items */ |
| #define FOOD_SIZE 3 /* the width and height of a food */ |
| |
| /* The arrays store the food coordinates */ |
| static char foodx[MAX_FOOD]; |
| static char foody[MAX_FOOD]; |
| |
| #define MAX_ARGH 100 /* maximal number of argh items */ |
| #define ARGH_SIZE 4 /* the width and height of a argh */ |
| #define ARGHS_PER_FOOD 2 /* number of arghs produced per eaten food */ |
| |
| /* The arrays store the argh coordinates */ |
| static char arghx[MAX_ARGH]; |
| static char arghy[MAX_ARGH]; |
| |
| /* the number of arghs that are currently in use */ |
| static int argh_count; |
| |
| #ifdef DEBUG_WORMLET |
| /* just a buffer used for debug output */ |
| static char debugout[15]; |
| #endif |
| |
| /* the number of ticks each game cycle should take */ |
| #define SPEED 14 |
| |
| /* the number of active worms (dead or alive) */ |
| static int worm_count = MAX_WORMS; |
| |
| /* in multiplayer mode: en- / disables the remote worm control |
| in singleplayer mode: toggles 4 / 2 button worm control */ |
| static bool use_remote = false; |
| |
| /* return values of check_collision */ |
| #define COLLISION_NONE 0 |
| #define COLLISION_WORM 1 |
| #define COLLISION_FOOD 2 |
| #define COLLISION_ARGH 3 |
| #define COLLISION_FIELD 4 |
| |
| /* constants for use as directions. |
| Note that the values are ordered clockwise. |
| Thus increasing / decreasing the values |
| is equivalent to right / left turns. */ |
| #define WEST 0 |
| #define NORTH 1 |
| #define EAST 2 |
| #define SOUTH 3 |
| |
| /* direction of human player 1 */ |
| static int player1_dir = EAST; |
| /* direction of human player 2 */ |
| static int player2_dir = EAST; |
| /* direction of human player 3 */ |
| static int player3_dir = EAST; |
| |
| /* the number of (human) players that currently |
| control a worm */ |
| static int players = 1; |
| |
| #ifdef DEBUG_WORMLET |
| static void set_debug_out(char *str){ |
| strcpy(debugout, str); |
| } |
| #endif |
| |
| /** |
| * Returns the direction id in which the worm |
| * currently is creeping. |
| * @param struct worm *w The worm that is to be investigated. |
| * w Must not be null. |
| * @return int A value 0 <= value < 4 |
| * Note the predefined constants NORTH, SOUTH, EAST, WEST |
| */ |
| static int get_worm_dir(struct worm *w) { |
| int retVal ; |
| if (w->dirx == 0) { |
| if (w->diry == 1) { |
| retVal = SOUTH; |
| } else { |
| retVal = NORTH; |
| } |
| } else { |
| if (w->dirx == 1) { |
| retVal = EAST; |
| } else { |
| retVal = WEST; |
| } |
| } |
| return retVal; |
| } |
| |
| /** |
| * Set the direction of the specified worm with a direction id. |
| * Increasing the value by 1 means to turn the worm direction |
| * to right by 90 degree. |
| * @param struct worm *w The worm that is to be altered. w Must not be null. |
| * @param int dir The new direction in which the worm is to creep. |
| * dir must be 0 <= dir < 4. Use predefined constants |
| * NORTH, SOUTH, EAST, WEST |
| */ |
| static void set_worm_dir(struct worm *w, int dir) { |
| switch (dir) { |
| case WEST: |
| w->dirx = -1; |
| w->diry = 0; |
| break; |
| case NORTH: |
| w->dirx = 0; |
| w->diry = - 1; |
| break; |
| case EAST: |
| w->dirx = 1; |
| w->diry = 0; |
| break; |
| case SOUTH: |
| w->dirx = 0; |
| w->diry = 1; |
| break; |
| } |
| } |
| |
| /** |
| * Returns the current length of the worm array. This |
| * is also a value for the number of bends that are in the worm. |
| * @return int a positive value with 0 <= value < MAX_WORM_SEGMENTS |
| */ |
| static int get_worm_array_length(struct worm *w) { |
| /* initial simple calculation will be overwritten if wrong. */ |
| int retVal = w->head - w->tail; |
| |
| /* if the worm 'crosses' the boundaries of the ringbuffer */ |
| if (retVal < 0) { |
| retVal = w->head + MAX_WORM_SEGMENTS - w->tail; |
| } |
| |
| return retVal; |
| } |
| |
| /** |
| * Returns the score the specified worm. The score is the length |
| * of the worm. |
| * @param struct worm *w The worm that is to be investigated. |
| * w must not be null. |
| * @return int The length of the worm (>= 0). |
| */ |
| static int get_score(struct worm *w) { |
| int retval = 0; |
| int length = get_worm_array_length(w); |
| int i; |
| for (i = 0; i < length; i++) { |
| |
| /* The iteration iterates the length of the worm. |
| Here's the conversion to the true indices within the worm arrays. */ |
| int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS; |
| int lineend = (linestart + 1) % MAX_WORM_SEGMENTS; |
| int startx = w->x[linestart]; |
| int starty = w->y[linestart]; |
| int endx = w->x[lineend]; |
| int endy = w->y[lineend]; |
| |
| int minimum, maximum; |
| |
| if (startx == endx) { |
| minimum = MIN(starty, endy); |
| maximum = MAX(starty, endy); |
| } else { |
| minimum = MIN(startx, endx); |
| maximum = MAX(startx, endx); |
| } |
| retval += abs(maximum - minimum); |
| } |
| return retval; |
| } |
| |
| /** |
| * Determines wether the line specified by startx, starty, endx, endy intersects |
| * the rectangle specified by x, y, width, height. Note that the line must be exactly |
| * horizontal or vertical (startx == endx or starty == endy). |
| * @param int startx The x coordinate of the start point of the line. |
| * @param int starty The y coordinate of the start point of the line. |
| * @param int endx The x coordinate of the end point of the line. |
| * @param int endy The y coordinate of the end point of the line. |
| * @param int x The x coordinate of the top left corner of the rectangle. |
| * @param int y The y coordinate of the top left corner of the rectangle. |
| * @param int width The width of the rectangle. |
| * @param int height The height of the rectangle. |
| * @return bool Returns true if the specified line intersects with the recangle. |
| */ |
| static bool line_in_rect(int startx, int starty, int endx, int endy, int x, int y, int width, int height) { |
| bool retval = false; |
| int simple, simplemin, simplemax; |
| int compa, compb, compmin, compmax; |
| int temp; |
| if (startx == endx) { |
| simple = startx; |
| simplemin = x; |
| simplemax = x + width; |
| |
| compa = starty; |
| compb = endy; |
| compmin = y; |
| compmax = y + height; |
| } else { |
| simple = starty; |
| simplemin = y; |
| simplemax = y + height; |
| |
| compa = startx; |
| compb = endx; |
| compmin = x; |
| compmax = x + width; |
| }; |
| |
| temp = compa; |
| compa = MIN(compa, compb); |
| compb = MAX(temp, compb); |
| |
| if (simplemin <= simple && simple <= simplemax) { |
| if ((compmin <= compa && compa <= compmax) || |
| (compmin <= compb && compb <= compmax) || |
| (compa <= compmin && compb >= compmax)) { |
| retval = true; |
| } |
| } |
| return retval; |
| } |
| |
| /** |
| * Tests wether the specified worm intersects with the rect. |
| * @param struct worm *w The worm to be investigated |
| * @param int x The x coordinate of the top left corner of the rect |
| * @param int y The y coordinate of the top left corner of the rect |
| * @param int widht The width of the rect |
| * @param int height The height of the rect |
| * @return bool Returns true if the worm intersects with the rect |
| */ |
| static bool worm_in_rect(struct worm *w, int x, int y, int width, int height) { |
| bool retval = false; |
| |
| |
| /* get_worm_array_length is expensive -> buffer the value */ |
| int wormLength = get_worm_array_length(w); |
| int i; |
| |
| /* test each entry that is part of the worm */ |
| for (i = 0; i < wormLength && retval == false; i++) { |
| |
| /* The iteration iterates the length of the worm. |
| Here's the conversion to the true indices within the worm arrays. */ |
| int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS; |
| int lineend = (linestart + 1) % MAX_WORM_SEGMENTS; |
| int startx = w->x[linestart]; |
| int starty = w->y[linestart]; |
| int endx = w->x[lineend]; |
| int endy = w->y[lineend]; |
| |
| retval = line_in_rect(startx, starty, endx, endy, x, y, width, height); |
| } |
| |
| return retval; |
| } |
| |
| /** |
| * Checks wether a specific food in the food arrays is at the |
| * specified coordinates. |
| * @param int foodIndex The index of the food in the food arrays |
| * @param int x the x coordinate. |
| * @param int y the y coordinate. |
| * @return Returns true if the coordinate hits the food specified by |
| * foodIndex. |
| */ |
| static bool specific_food_collision(int foodIndex, int x, int y) { |
| bool retVal = false; |
| if (x >= foodx[foodIndex] && |
| x < foodx[foodIndex] + FOOD_SIZE && |
| y >= foody[foodIndex] && |
| y < foody[foodIndex] + FOOD_SIZE) { |
| |
| retVal = true; |
| } |
| return retVal; |
| } |
| |
| /** |
| * Returns the index of the food that is at the |
| * given coordinates. If no food is at the coordinates |
| * -1 is returned. |
| * @return int -1 <= value < MAX_FOOD |
| */ |
| static int food_collision(int x, int y) { |
| int i = 0; |
| int retVal = -1; |
| for (i = 0; i < MAX_FOOD; i++) { |
| if (specific_food_collision(i, x, y)) { |
| retVal = i; |
| break; |
| } |
| } |
| return retVal; |
| } |
| |
| /** |
| * Checks wether a specific argh in the argh arrays is at the |
| * specified coordinates. |
| * @param int arghIndex The index of the argh in the argh arrays |
| * @param int x the x coordinate. |
| * @param int y the y coordinate. |
| * @return Returns true if the coordinate hits the argh specified by |
| * arghIndex. |
| */ |
| static bool specific_argh_collision(int arghIndex, int x, int y) { |
| |
| if ( x >= arghx[arghIndex] && |
| y >= arghy[arghIndex] && |
| x < arghx[arghIndex] + ARGH_SIZE && |
| y < arghy[arghIndex] + ARGH_SIZE ) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns the index of the argh that is at the |
| * given coordinates. If no argh is at the coordinates |
| * -1 is returned. |
| * @param int x The x coordinate. |
| * @param int y The y coordinate. |
| * @return int -1 <= value < argh_count <= MAX_ARGH |
| */ |
| static int argh_collision(int x, int y) { |
| int i = 0; |
| int retVal = -1; |
| |
| /* search for the argh that has the specified coords */ |
| for (i = 0; i < argh_count; i++) { |
| if (specific_argh_collision(i, x, y)) { |
| retVal = i; |
| break; |
| } |
| } |
| return retVal; |
| } |
| |
| /** |
| * Checks wether the worm collides with the food at the specfied food-arrays. |
| * @param int foodIndex The index of the food in the arrays. Ensure the value is |
| * 0 <= foodIndex <= MAX_FOOD |
| * @return Returns true if the worm collides with the specified food. |
| */ |
| static bool worm_food_collision(struct worm *w, int foodIndex) |
| { |
| bool retVal = false; |
| |
| retVal = worm_in_rect(w, foodx[foodIndex], foody[foodIndex], |
| FOOD_SIZE - 1, FOOD_SIZE - 1); |
| |
| return retVal; |
| } |
| |
| /** |
| * Returns true if the worm hits the argh within the next moves (unless |
| * the worm changes it's direction). |
| * @param struct worm *w - The worm to investigate |
| * @param int argh_idx - The index of the argh |
| * @param int moves - The number of moves that are considered. |
| * @return Returns false if the specified argh is not hit within the next |
| * moves. |
| */ |
| static bool worm_argh_collision_in_moves(struct worm *w, int argh_idx, int moves){ |
| bool retVal = false; |
| int x1, y1, x2, y2; |
| x1 = w->x[w->head]; |
| y1 = w->y[w->head]; |
| |
| x2 = w->x[w->head] + moves * w->dirx; |
| y2 = w->y[w->head] + moves * w->diry; |
| |
| retVal = line_in_rect(x1, y1, x2, y2, arghx[argh_idx], arghy[argh_idx], |
| ARGH_SIZE, ARGH_SIZE); |
| return retVal; |
| } |
| |
| /** |
| * Checks wether the worm collides with the argh at the specfied argh-arrays. |
| * @param int arghIndex The index of the argh in the arrays. |
| * Ensure the value is 0 <= arghIndex < argh_count <= MAX_ARGH |
| * @return Returns true if the worm collides with the specified argh. |
| */ |
| static bool worm_argh_collision(struct worm *w, int arghIndex) |
| { |
| bool retVal = false; |
| |
| retVal = worm_in_rect(w, arghx[arghIndex], arghy[arghIndex], |
| ARGH_SIZE - 1, ARGH_SIZE - 1); |
| |
| return retVal; |
| } |
| |
| /** |
| * Find new coordinates for the food stored in foodx[index], foody[index] |
| * that don't collide with any other food or argh |
| * @param int index |
| * Ensure that 0 <= index < MAX_FOOD. |
| */ |
| static int make_food(int index) { |
| |
| int x = 0; |
| int y = 0; |
| bool collisionDetected = false; |
| int tries = 0; |
| int i; |
| |
| do { |
| /* make coordinates for a new food so that |
| the entire food lies within the FIELD */ |
| x = rand() % (FIELD_RECT_WIDTH - FOOD_SIZE); |
| y = rand() % (FIELD_RECT_HEIGHT - FOOD_SIZE); |
| tries ++; |
| |
| /* Ensure that the new food doesn't collide with any |
| existing foods or arghs. |
| If one or more corners of the new food hit any existing |
| argh or food a collision is detected. |
| */ |
| collisionDetected = |
| food_collision(x , y ) >= 0 || |
| food_collision(x , y + FOOD_SIZE - 1) >= 0 || |
| food_collision(x + FOOD_SIZE - 1, y ) >= 0 || |
| food_collision(x + FOOD_SIZE - 1, y + FOOD_SIZE - 1) >= 0 || |
| argh_collision(x , y ) >= 0 || |
| argh_collision(x , y + FOOD_SIZE - 1) >= 0 || |
| argh_collision(x + FOOD_SIZE - 1, y ) >= 0 || |
| argh_collision(x + FOOD_SIZE - 1, y + FOOD_SIZE - 1) >= 0; |
| |
| /* use coordinates for further testing */ |
| foodx[index] = x; |
| foody[index] = y; |
| |
| /* now test wether we accidently hit the worm with food ;) */ |
| i = 0; |
| for (i = 0; i < worm_count && !collisionDetected; i++) { |
| collisionDetected |= worm_food_collision(&worms[i], index); |
| } |
| } |
| while (collisionDetected); |
| return tries; |
| } |
| |
| /** |
| * Clears a food from the lcd buffer. |
| * @param int index The index of the food arrays under which |
| * the coordinates of the desired food can be found. Ensure |
| * that the value is 0 <= index <= MAX_FOOD. |
| */ |
| static void clear_food(int index) |
| { |
| /* remove the old food from the screen */ |
| lcd_clearrect(foodx[index] + FIELD_RECT_X, |
| foody[index] + FIELD_RECT_Y, |
| FOOD_SIZE, FOOD_SIZE); |
| } |
| |
| /** |
| * Draws a food in the lcd buffer. |
| * @param int index The index of the food arrays under which |
| * the coordinates of the desired food can be found. Ensure |
| * that the value is 0 <= index <= MAX_FOOD. |
| */ |
| static void draw_food(int index) |
| { |
| /* draw the food object */ |
| lcd_fillrect(foodx[index] + FIELD_RECT_X, |
| foody[index] + FIELD_RECT_Y, |
| FOOD_SIZE, FOOD_SIZE); |
| lcd_clearrect(foodx[index] + FIELD_RECT_X + 1, |
| foody[index] + FIELD_RECT_Y + 1, |
| FOOD_SIZE - 2, FOOD_SIZE - 2); |
| } |
| |
| /** |
| * Find new coordinates for the argh stored in arghx[index], arghy[index] |
| * that don't collide with any other food or argh. |
| * @param int index |
| * Ensure that 0 <= index < argh_count < MAX_ARGH. |
| */ |
| static int make_argh(int index) |
| { |
| int x = -1; |
| int y = -1; |
| bool collisionDetected = false; |
| int tries = 0; |
| int i; |
| |
| do { |
| /* make coordinates for a new argh so that |
| the entire food lies within the FIELD */ |
| x = rand() % (FIELD_RECT_WIDTH - ARGH_SIZE); |
| y = rand() % (FIELD_RECT_HEIGHT - ARGH_SIZE); |
| tries ++; |
| |
| /* Ensure that the new argh doesn't intersect with any |
| existing foods or arghs. |
| If one or more corners of the new argh hit any existing |
| argh or food an intersection is detected. |
| */ |
| collisionDetected = |
| food_collision(x , y ) >= 0 || |
| food_collision(x , y + ARGH_SIZE - 1) >= 0 || |
| food_collision(x + ARGH_SIZE - 1, y ) >= 0 || |
| food_collision(x + ARGH_SIZE - 1, y + ARGH_SIZE - 1) >= 0 || |
| argh_collision(x , y ) >= 0 || |
| argh_collision(x , y + ARGH_SIZE - 1) >= 0 || |
| argh_collision(x + ARGH_SIZE - 1, y ) >= 0 || |
| argh_collision(x + ARGH_SIZE - 1, y + ARGH_SIZE - 1) >= 0; |
| |
| /* use the candidate coordinates to make a real argh */ |
| arghx[index] = x; |
| arghy[index] = y; |
| |
| /* now test wether we accidently hit the worm with argh ;) */ |
| for (i = 0; i < worm_count && !collisionDetected; i++) { |
| collisionDetected |= worm_argh_collision(&worms[i], index); |
| collisionDetected |= worm_argh_collision_in_moves(&worms[i], index, |
| MIN_ARGH_DIST); |
| } |
| } |
| while (collisionDetected); |
| return tries; |
| } |
| |
| /** |
| * Draws an argh in the lcd buffer. |
| * @param int index The index of the argh arrays under which |
| * the coordinates of the desired argh can be found. Ensure |
| * that the value is 0 <= index < argh_count <= MAX_ARGH. |
| */ |
| static void draw_argh(int index) |
| { |
| /* draw the new argh */ |
| lcd_fillrect(arghx[index] + FIELD_RECT_X, |
| arghy[index] + FIELD_RECT_Y, |
| ARGH_SIZE, ARGH_SIZE); |
| } |
| |
| static void virtual_player(struct worm *w); |
| /** |
| * Initialzes the specified worm with INITIAL_WORM_LENGTH |
| * and the tail at the specified position. The worm will |
| * be initialized alive and creeping EAST. |
| * @param struct worm *w The worm that is to be initialized |
| * @param int x The x coordinate at which the tail of the worm starts. |
| * x must be 0 <= x < FIELD_RECT_WIDTH. |
| * @param int y The y coordinate at which the tail of the worm starts |
| * y must be 0 <= y < FIELD_RECT_WIDTH. |
| */ |
| static void init_worm(struct worm *w, int x, int y){ |
| /* initialize the worm size */ |
| w->head = 1; |
| w->tail = 0; |
| |
| w->x[w->head] = x + 1; |
| w->y[w->head] = y; |
| |
| w->x[w->tail] = x; |
| w->y[w->tail] = y; |
| |
| /* set the initial direction the worm creeps to */ |
| w->dirx = 1; |
| w->diry = 0; |
| |
| w->growing = INITIAL_WORM_LENGTH - 1; |
| w->alive = true; |
| w->fetch_worm_direction = virtual_player; |
| } |
| |
| /** |
| * Writes the direction that was stored for |
| * human player 1 into the specified worm. This function |
| * may be used to be stored in worm.fetch_worm_direction. |
| * The value of |
| * the direction is read from player1_dir. |
| * @param struct worm *w - The worm of which the direction |
| * is altered. |
| */ |
| static void human_player1(struct worm *w) { |
| set_worm_dir(w, player1_dir); |
| } |
| |
| /** |
| * Writes the direction that was stored for |
| * human player 2 into the specified worm. This function |
| * may be used to be stored in worm.fetch_worm_direction. |
| * The value of |
| * the direction is read from player2_dir. |
| * @param struct worm *w - The worm of which the direction |
| * is altered. |
| */ |
| static void human_player2(struct worm *w) { |
| set_worm_dir(w, player2_dir); |
| } |
| |
| /** |
| * Writes the direction that was stored for |
| * human player using a remote control |
| * into the specified worm. This function |
| * may be used to be stored in worm.fetch_worm_direction. |
| * The value of |
| * the direction is read from player3_dir. |
| * @param struct worm *w - The worm of which the direction |
| * is altered. |
| */ |
| static void remote_player(struct worm *w) { |
| set_worm_dir(w, player3_dir); |
| } |
| |
| /** |
| * Initializes the worm-, food- and argh-arrays, draws a frame, |
| * makes some food and argh and display all that stuff. |
| */ |
| static void init_wormlet(void) |
| { |
| int i; |
| |
| for (i = 0; i< worm_count; i++) { |
| /* Initialize all the worm coordinates to center. */ |
| int x = (int)(FIELD_RECT_WIDTH / 2); |
| int y = (int)((FIELD_RECT_HEIGHT - 20)/ 2) + i * 10; |
| |
| init_worm(&worms[i], x, y); |
| } |
| |
| player1_dir = EAST; |
| player2_dir = EAST; |
| player3_dir = EAST; |
| |
| if (players > 0) { |
| worms[0].fetch_worm_direction = human_player1; |
| } |
| |
| if (players > 1) { |
| if (use_remote) { |
| worms[1].fetch_worm_direction = remote_player; |
| } else { |
| worms[1].fetch_worm_direction = human_player2; |
| } |
| } |
| |
| if (players > 2) { |
| worms[2].fetch_worm_direction = human_player2; |
| } |
| |
| /* Needed when the game is restarted using BUTTON_ON */ |
| lcd_clear_display(); |
| |
| /* make and display some food and argh */ |
| argh_count = MAX_FOOD; |
| for (i = 0; i < MAX_FOOD; i++) { |
| make_food(i); |
| draw_food(i); |
| make_argh(i); |
| draw_argh(i); |
| } |
| |
| /* draw the game field */ |
| lcd_invertrect(0, 0, FIELD_RECT_WIDTH + 2, FIELD_RECT_HEIGHT + 2); |
| lcd_invertrect(1, 1, FIELD_RECT_WIDTH, FIELD_RECT_HEIGHT); |
| |
| /* make everything visible */ |
| lcd_update(); |
| } |
| |
| |
| /** |
| * Move the worm one step further if it is alive. |
| * The direction in which the worm moves is taken from dirx and diry. |
| * move_worm decreases growing if > 0. While the worm is growing the tail |
| * is left untouched. |
| * @param struct worm *w The worm to move. w must not be NULL. |
| */ |
| static void move_worm(struct worm *w) |
| { |
| if (w->alive) { |
| /* determine the head point and its precessor */ |
| int headx = w->x[w->head]; |
| int heady = w->y[w->head]; |
| int prehead = (w->head + MAX_WORM_SEGMENTS - 1) % MAX_WORM_SEGMENTS; |
| int preheadx = w->x[prehead]; |
| int preheady = w->y[prehead]; |
| |
| /* determine the old direction */ |
| int olddirx; |
| int olddiry; |
| if (headx == preheadx) { |
| olddirx = 0; |
| olddiry = (heady > preheady) ? 1 : -1; |
| } else { |
| olddiry = 0; |
| olddirx = (headx > preheadx) ? 1 : -1; |
| } |
| |
| /* olddir == dir? |
| a change of direction means a new segment |
| has been opened */ |
| if (olddirx != w->dirx || |
| olddiry != w->diry) { |
| w->head = (w->head + 1) % MAX_WORM_SEGMENTS; |
| } |
| |
| /* new head position */ |
| w->x[w->head] = headx + w->dirx; |
| w->y[w->head] = heady + w->diry; |
| |
| |
| /* while the worm is growing no tail procession is necessary */ |
| if (w->growing > 0) { |
| /* update the worms grow state */ |
| w->growing--; |
| } |
| |
| /* if the worm isn't growing the tail has to be dragged */ |
| else { |
| /* index of the end of the tail segment */ |
| int tail_segment_end = (w->tail + 1) % MAX_WORM_SEGMENTS; |
| |
| /* drag the end of the tail */ |
| /* only one coordinate has to be altered. Here it is |
| determined which one */ |
| int dir = 0; /* specifies wether the coord has to be in- or decreased */ |
| if (w->x[w->tail] == w->x[tail_segment_end]) { |
| dir = (w->y[w->tail] - w->y[tail_segment_end] < 0) ? 1 : -1; |
| w->y[w->tail] += dir; |
| } else { |
| dir = (w->x[w->tail] - w->x[tail_segment_end] < 0) ? 1 : -1; |
| w->x[w->tail] += dir; |
| } |
| |
| /* when the tail has been dragged so far that it meets |
| the next segment start the tail segment is obsolete and |
| must be freed */ |
| if (w->x[w->tail] == w->x[tail_segment_end] && |
| w->y[w->tail] == w->y[tail_segment_end]){ |
| |
| /* drop the last tail point */ |
| w->tail = tail_segment_end; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Draws the head and clears the tail of the worm in |
| * the display buffer. lcd_update() is NOT called thus |
| * the caller has to take care that the buffer is displayed. |
| */ |
| static void draw_worm(struct worm *w) |
| { |
| /* draw the new head */ |
| int x = w->x[w->head]; |
| int y = w->y[w->head]; |
| if (x >= 0 && x < FIELD_RECT_WIDTH && y >= 0 && y < FIELD_RECT_HEIGHT) { |
| lcd_drawpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y); |
| } |
| |
| /* clear the space behind the worm */ |
| x = w->x[w->tail] ; |
| y = w->y[w->tail] ; |
| if (x >= 0 && x < FIELD_RECT_WIDTH && y >= 0 && y < FIELD_RECT_HEIGHT) { |
| lcd_clearpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y); |
| } |
| } |
| |
| /** |
| * Checks wether the coordinate is part of the worm. Returns |
| * true if any part of the worm was hit - including the head. |
| * @param x int The x coordinate |
| * @param y int The y coordinate |
| * @return int The index of the worm arrays that contain x, y. |
| * Returns -1 if the coordinates are not part of the worm. |
| */ |
| static int specific_worm_collision(struct worm *w, int x, int y) |
| { |
| int retVal = -1; |
| |
| /* get_worm_array_length is expensive -> buffer the value */ |
| int wormLength = get_worm_array_length(w); |
| int i; |
| |
| /* test each entry that is part of the worm */ |
| for (i = 0; i < wormLength && retVal == -1; i++) { |
| |
| /* The iteration iterates the length of the worm. |
| Here's the conversion to the true indices within the worm arrays. */ |
| int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS; |
| int lineend = (linestart + 1) % MAX_WORM_SEGMENTS; |
| bool samex = (w->x[linestart] == x) && (w->x[lineend] == x); |
| bool samey = (w->y[linestart] == y) && (w->y[lineend] == y); |
| if (samex || samey){ |
| int test, min, max, tmp; |
| |
| if (samey) { |
| min = w->x[linestart]; |
| max = w->x[lineend]; |
| test = x; |
| } else { |
| min = w->y[linestart]; |
| max = w->y[lineend]; |
| test = y; |
| } |
| |
| tmp = min; |
| min = MIN(min, max); |
| max = MAX(tmp, max); |
| |
| if (min <= test && test <= max) { |
| retVal = lineend; |
| } |
| } |
| } |
| return retVal; |
| } |
| |
| /** |
| * Increases the length of the specified worm by marking |
| * that it may grow by len pixels. Note that the worm has |
| * to move to make the growing happen. |
| * @param worm *w The worm that is to be altered. |
| * @param int len A positive value specifying the amount of |
| * pixels the worm may grow. |
| */ |
| static void add_growing(struct worm *w, int len) { |
| w->growing += len; |
| } |
| |
| /** |
| * Determins the worm that is at the coordinates x, y. The parameter |
| * w is a switch parameter that changes the functionality of worm_collision. |
| * If w is specified and x,y hits the head of w NULL is returned. |
| * This is a useful way to determine wether the head of w hits |
| * any worm but including itself but excluding its own head. |
| * (It hits always its own head ;)) |
| * If w is set to NULL worm_collision returns any worm including all heads |
| * that is at position of x,y. |
| * @param struct worm *w The worm of which the head should be excluded in |
| * the test. w may be set to NULL. |
| * @param int x The x coordinate that is checked |
| * @param int y The y coordinate that is checkec |
| * @return struct worm* The worm that has been hit by x,y. If no worm |
| * was at the position NULL is returned. |
| */ |
| static struct worm* worm_collision(struct worm *w, int x, int y){ |
| struct worm *retVal = NULL; |
| int i; |
| for (i = 0; (i < worm_count) && (retVal == NULL); i++) { |
| int collision_at = specific_worm_collision(&worms[i], x, y); |
| if (collision_at != -1) { |
| if (!(w == &worms[i] && collision_at == w->head)){ |
| retVal = &worms[i]; |
| } |
| } |
| } |
| return retVal; |
| } |
| |
| /** |
| * Returns true if the head of the worm just has |
| * crossed the field boundaries. |
| * @return bool true if the worm just has wrapped. |
| */ |
| static bool field_collision(struct worm *w) |
| { |
| bool retVal = false; |
| if ((w->x[w->head] >= FIELD_RECT_WIDTH) || |
| (w->y[w->head] >= FIELD_RECT_HEIGHT) || |
| (w->x[w->head] < 0) || |
| (w->y[w->head] < 0)) |
| { |
| retVal = true; |
| } |
| return retVal; |
| } |
| |
| |
| /** |
| * Returns true if the specified coordinates are within the |
| * field specified by the FIELD_RECT_XXX constants. |
| * @param int x The x coordinate of the point that is investigated |
| * @param int y The y coordinate of the point that is investigated |
| * @return bool Returns false if x,y specifies a point outside the |
| * field of worms. |
| */ |
| static bool is_in_field_rect(int x, int y) { |
| bool retVal = false; |
| retVal = (x >= 0 && x < FIELD_RECT_WIDTH && |
| y >= 0 && y < FIELD_RECT_HEIGHT); |
| return retVal; |
| } |
| |
| /** |
| * Checks and returns wether the head of the w |
| * is colliding with something currently. |
| * @return int One of the values: |
| * COLLISION_NONE |
| * COLLISION_w |
| * COLLISION_FOOD |
| * COLLISION_ARGH |
| * COLLISION_FIELD |
| */ |
| static int check_collision(struct worm *w) |
| { |
| int retVal = COLLISION_NONE; |
| |
| if (worm_collision(w, w->x[w->head], w->y[w->head]) != NULL) |
| retVal = COLLISION_WORM; |
| |
| if (food_collision(w->x[w->head], w->y[w->head]) >= 0) |
| retVal = COLLISION_FOOD; |
| |
| if (argh_collision(w->x[w->head], w->y[w->head]) >= 0) |
| retVal = COLLISION_ARGH; |
| |
| if (field_collision(w)) |
| retVal = COLLISION_FIELD; |
| |
| return retVal; |
| } |
| |
| /** |
| * Returns the index of the food that is closest to the point |
| * specified by x, y. This index may be used in the foodx and |
| * foody arrays. |
| * @param int x The x coordinate of the point |
| * @param int y The y coordinate of the point |
| * @return int A value usable as index in foodx and foody. |
| */ |
| static int get_nearest_food(int x, int y){ |
| int nearestfood = 0; |
| int olddistance = FIELD_RECT_WIDTH + FIELD_RECT_HEIGHT; |
| int deltax = 0; |
| int deltay = 0; |
| int foodindex; |
| for (foodindex = 0; foodindex < MAX_FOOD; foodindex++) { |
| int distance; |
| deltax = foodx[foodindex] - x; |
| deltay = foody[foodindex] - y; |
| deltax = deltax > 0 ? deltax : deltax * (-1); |
| deltay = deltay > 0 ? deltay : deltay * (-1); |
| distance = deltax + deltay; |
| |
| if (distance < olddistance) { |
| olddistance = distance; |
| nearestfood = foodindex; |
| } |
| } |
| return nearestfood; |
| } |
| |
| /** |
| * Returns wether the specified position is next to the worm |
| * and in the direction the worm looks. Use this method to |
| * test wether this position would be hit with the next move of |
| * the worm unless the worm changes its direction. |
| * @param struct worm *w - The worm to be investigated |
| * @param int x - The x coordinate of the position to test. |
| * @param int y - The y coordinate of the position to test. |
| * @return Returns true if the worm will hit the position unless |
| * it change its direction before the next move. |
| */ |
| static bool is_in_front_of_worm(struct worm *w, int x, int y) { |
| bool infront = false; |
| int deltax = x - w->x[w->head]; |
| int deltay = y - w->y[w->head]; |
| |
| if (w->dirx == 0) { |
| infront = (w->diry * deltay) > 0; |
| } else { |
| infront = (w->dirx * deltax) > 0; |
| } |
| return infront; |
| } |
| |
| /** |
| * Returns true if the worm will collide with the next move unless |
| * it changes its direction. |
| * @param struct worm *w - The worm to be investigated. |
| * @return Returns true if the worm will collide with the next move |
| * unless it changes its direction. |
| */ |
| static bool will_worm_collide(struct worm *w) { |
| int x = w->x[w->head] + w->dirx; |
| int y = w->y[w->head] + w->diry; |
| bool retVal = !is_in_field_rect(x, y); |
| if (!retVal) { |
| retVal = (argh_collision(x, y) != -1); |
| } |
| |
| if (!retVal) { |
| retVal = (worm_collision(w, x, y) != NULL); |
| } |
| return retVal; |
| } |
| |
| /** |
| * This function |
| * may be used to be stored in worm.fetch_worm_direction for |
| * worms that are not controlled by humans but by artificial stupidity. |
| * A direction is searched that doesn't lead to collision but to the nearest |
| * food - but not very intelligent. The direction is written to the specified |
| * worm. |
| * @param struct worm *w - The worm of which the direction |
| * is altered. |
| */ |
| static void virtual_player(struct worm *w) { |
| bool isright; |
| int plana, planb, planc; |
| /* find the next lunch */ |
| int nearestfood = get_nearest_food(w->x[w->head], w->y[w->head]); |
| |
| /* determine in which direction it is */ |
| |
| /* in front of me? */ |
| bool infront = is_in_front_of_worm(w, foodx[nearestfood], foody[nearestfood]); |
| |
| /* left right of me? */ |
| int olddir = get_worm_dir(w); |
| set_worm_dir(w, (olddir + 1) % 4); |
| isright = is_in_front_of_worm(w, foodx[nearestfood], foody[nearestfood]); |
| set_worm_dir(w, olddir); |
| |
| /* detect situation, set strategy */ |
| if (infront) { |
| if (isright) { |
| plana = olddir; |
| planb = (olddir + 1) % 4; |
| planc = (olddir + 3) % 4; |
| } else { |
| plana = olddir; |
| planb = (olddir + 3) % 4; |
| planc = (olddir + 1) % 4; |
| } |
| } else { |
| if (isright) { |
| plana = (olddir + 1) % 4; |
| planb = olddir; |
| planc = (olddir + 3) % 4; |
| } else { |
| plana = (olddir + 3) % 4; |
| planb = olddir; |
| planc = (olddir + 1) % 4; |
| } |
| } |
| |
| /* test for collision */ |
| set_worm_dir(w, plana); |
| if (will_worm_collide(w)){ |
| |
| /* plan b */ |
| set_worm_dir(w, planb); |
| |
| /* test for collision */ |
| if (will_worm_collide(w)) { |
| |
| /* plan c */ |
| set_worm_dir(w, planc); |
| } |
| } |
| } |
| |
| /** |
| * prints out the score board with all the status information |
| * about the game. |
| */ |
| static void score_board(void) |
| { |
| char buf[15]; |
| char buf2[15]; |
| int i; |
| int y = 0; |
| lcd_clearrect(FIELD_RECT_WIDTH + 2, 0, LCD_WIDTH - FIELD_RECT_WIDTH - 2, LCD_HEIGHT); |
| for (i = 0; i < worm_count; i++) { |
| int score = get_score(&worms[i]); |
| |
| /* high score */ |
| if (worms[i].fetch_worm_direction != virtual_player){ |
| if (highscore < score) { |
| highscore = score; |
| } |
| } |
| |
| /* length */ |
| snprintf(buf, sizeof (buf),str(LANG_WORMLET_LENGTH), score); |
| |
| /* worm state */ |
| switch (check_collision(&worms[i])) { |
| case COLLISION_NONE: |
| if (worms[i].growing > 0){ |
| snprintf(buf2, sizeof(buf2), str(LANG_WORMLET_GROWING)); |
| } |
| else { |
| if (worms[i].alive) { |
| snprintf(buf2, sizeof(buf2), str(LANG_WORMLET_HUNGRY)); |
| } else { |
| snprintf(buf2, sizeof(buf2), str(LANG_WORMLET_WORMED)); |
| } |
| } |
| break; |
| |
| case COLLISION_WORM: |
| snprintf(buf2, sizeof(buf2), str(LANG_WORMLET_WORMED)); |
| break; |
| |
| case COLLISION_FOOD: |
| snprintf(buf2, sizeof(buf2), str(LANG_WORMLET_GROWING)); |
| break; |
| |
| case COLLISION_ARGH: |
| snprintf(buf2, sizeof(buf2), str(LANG_WORMLET_ARGH)); |
| break; |
| |
| case COLLISION_FIELD: |
| snprintf(buf2, sizeof(buf2), str(LANG_WORMLET_CRASHED)); |
| break; |
| } |
| lcd_putsxy(FIELD_RECT_WIDTH + 3, y , buf); |
| lcd_putsxy(FIELD_RECT_WIDTH + 3, y+8, buf2); |
| |
| if (!worms[i].alive){ |
| lcd_invertrect(FIELD_RECT_WIDTH + 2, y, |
| LCD_WIDTH - FIELD_RECT_WIDTH - 2, 17); |
| } |
| y += 19; |
| } |
| snprintf(buf , sizeof(buf), str(LANG_WORMLET_HIGHSCORE), highscore); |
| #ifndef DEBUG_WORMLET |
| lcd_putsxy(FIELD_RECT_WIDTH + 3, LCD_HEIGHT - 8, buf); |
| #else |
| lcd_putsxy(FIELD_RECT_WIDTH + 3, LCD_HEIGHT - 8, debugout); |
| #endif |
| } |
| |
| /** |
| * Checks for collisions of the worm and its environment and |
| * takes appropriate actions like growing the worm or killing it. |
| * @return bool Returns true if the worm is dead. Returns |
| * false if the worm is healthy, up and creeping. |
| */ |
| static bool process_collisions(struct worm *w) |
| { |
| int index = -1; |
| |
| w->alive &= !field_collision(w); |
| |
| if (w->alive) { |
| |
| /* check if food was eaten */ |
| index = food_collision(w->x[w->head], w->y[w->head]); |
| if (index != -1){ |
| int i; |
| |
| clear_food(index); |
| make_food(index); |
| draw_food(index); |
| |
| for (i = 0; i < ARGHS_PER_FOOD; i++) { |
| argh_count++; |
| if (argh_count > MAX_ARGH) |
| argh_count = MAX_ARGH; |
| make_argh(argh_count - 1); |
| draw_argh(argh_count - 1); |
| } |
| |
| add_growing(w, WORM_PER_FOOD); |
| |
| draw_worm(w); |
| } |
| |
| /* check if argh was eaten */ |
| else { |
| index = argh_collision(w->x[w->head], w->y[w->head]); |
| if (index != -1) { |
| w->alive = false; |
| } |
| else { |
| if (worm_collision(w, w->x[w->head], w->y[w->head]) != NULL) { |
| w->alive = false; |
| } |
| } |
| } |
| } |
| return !w->alive; |
| } |
| |
| /** |
| * The main loop of the game. |
| * @return bool Returns true if the game ended |
| * with a dead worm. Returns false if the user |
| * aborted the game manually. |
| */ |
| static bool run(void) |
| { |
| int button = 0; |
| int wormDead = false; |
| |
| /* ticks are counted to compensate speed variations */ |
| long cycle_start = 0, cycle_end = 0; |
| #ifdef DEBUG_WORMLET |
| int ticks_to_max_cycle_reset = 20; |
| long max_cycle = 0; |
| char buf[20]; |
| #endif |
| |
| /* initialize the board and so on */ |
| init_wormlet(); |
| |
| cycle_start = current_tick; |
| /* change the direction of the worm */ |
| while (button != BUTTON_OFF && ! wormDead) |
| { |
| int i; |
| long cycle_duration ; |
| switch (button) { |
| case BUTTON_UP: |
| if (players == 1 && !use_remote) { |
| player1_dir = NORTH; |
| } |
| break; |
| |
| case BUTTON_DOWN: |
| if (players == 1 && !use_remote) { |
| player1_dir = SOUTH; |
| } |
| break; |
| |
| case BUTTON_LEFT: |
| if (players != 1 || use_remote) { |
| player1_dir = (player1_dir + 3) % 4; |
| } else { |
| player1_dir = WEST; |
| } |
| break; |
| |
| case BUTTON_RIGHT: |
| if (players != 1 || use_remote) { |
| player1_dir = (player1_dir + 1) % 4; |
| } else { |
| player1_dir = EAST; |
| } |
| break; |
| |
| case BUTTON_F2: |
| player2_dir = (player2_dir + 3) % 4; |
| break; |
| |
| case BUTTON_F3: |
| player2_dir = (player2_dir + 1) % 4; |
| break; |
| |
| case BUTTON_VOL_UP: |
| player3_dir = (player3_dir + 1) % 4; |
| break; |
| |
| case BUTTON_VOL_DOWN: |
| player3_dir = (player3_dir + 3) % 4; |
| break; |
| |
| case BUTTON_PLAY: |
| do { |
| button = button_get(true); |
| } while (button != BUTTON_PLAY && |
| button != BUTTON_OFF && |
| button != BUTTON_ON); |
| break; |
| } |
| |
| for (i = 0; i < worm_count; i++) { |
| worms[i].fetch_worm_direction(&worms[i]); |
| } |
| |
| wormDead = true; |
| for (i = 0; i < worm_count; i++){ |
| struct worm *w = &worms[i]; |
| move_worm(w); |
| wormDead &= process_collisions(w); |
| draw_worm(w); |
| } |
| score_board(); |
| lcd_update(); |
| if (button == BUTTON_ON) { |
| wormDead = true; |
| } |
| |
| /* here the wormlet game cycle ends |
| thus the current tick is stored |
| as end time */ |
| cycle_end = current_tick; |
| |
| /* The duration of the game cycle */ |
| cycle_duration = cycle_end - cycle_start; |
| cycle_duration = MAX(0, cycle_duration); |
| cycle_duration = MIN(SPEED -1, cycle_duration); |
| |
| |
| #ifdef DEBUG_WORMLET |
| ticks_to_max_cycle_reset--; |
| if (ticks_to_max_cycle_reset <= 0) { |
| max_cycle = 0; |
| } |
| |
| if (max_cycle < cycle_duration) { |
| max_cycle = cycle_duration; |
| ticks_to_max_cycle_reset = 20; |
| } |
| snprintf(buf, sizeof buf, "ticks %d", max_cycle); |
| set_debug_out(buf); |
| #endif |
| /* adjust the number of ticks to wait for a button. |
| This ensures that a complete game cycle including |
| user input runs in constant time */ |
| button = button_get_w_tmo(SPEED - cycle_duration); |
| cycle_start = current_tick; |
| |
| } |
| return wormDead; |
| } |
| |
| #ifdef DEBUG_WORMLET |
| |
| /** |
| * Just a test routine that checks that worm_food_collision works |
| * in some typical situations. |
| */ |
| static void test_worm_food_collision(void) { |
| int collision_count = 0; |
| int i; |
| lcd_clear_display(); |
| init_worm(&worms[0], 10, 10); |
| add_growing(&worms[0], 10); |
| set_worm_dir(&worms[0], EAST); |
| for (i = 0; i < 10; i++) { |
| move_worm(&worms[0]); |
| draw_worm(&worms[0]); |
| } |
| |
| set_worm_dir(&worms[0], SOUTH); |
| for (i = 0; i < 10; i++) { |
| move_worm(&worms[0]); |
| draw_worm(&worms[0]); |
| } |
| |
| foodx[0] = 15; |
| foody[0] = 12; |
| for (foody[0] = 20; foody[0] > 0; foody[0] --) { |
| char buf[20]; |
| bool collision; |
| draw_worm(&worms[0]); |
| draw_food(0); |
| collision = worm_food_collision(&worms[0], 0); |
| if (collision) { |
| collision_count++; |
| } |
| snprintf(buf, sizeof buf, "collisions: %d", collision_count); |
| lcd_putsxy(0, LCD_HEIGHT -8, buf); |
| lcd_update(); |
| } |
| if (collision_count != FOOD_SIZE) { |
| button_get(true); |
| } |
| |
| |
| foody[0] = 15; |
| for (foodx[0] = 30; foodx[0] > 0; foodx[0] --) { |
| char buf[20]; |
| bool collision; |
| draw_worm(&worms[0]); |
| draw_food(0); |
| collision = worm_food_collision(&worms[0], 0); |
| if (collision) { |
| collision_count ++; |
| } |
| snprintf(buf, sizeof buf, "collisions: %d", collision_count); |
| lcd_putsxy(0, LCD_HEIGHT -8, buf); |
| lcd_update(); |
| } |
| if (collision_count != FOOD_SIZE * 2) { |
| button_get(true); |
| } |
| |
| } |
| |
| |
| static bool expensive_worm_in_rect(struct worm *w, int rx, int ry, int rw, int rh){ |
| int x, y; |
| bool retVal = false; |
| for (x = rx; x < rx + rw; x++){ |
| for (y = ry; y < ry + rh; y++) { |
| if (specific_worm_collision(w, x, y) != -1) { |
| retVal = true; |
| } |
| } |
| } |
| return retVal; |
| } |
| |
| static void test_worm_argh_collision(void) { |
| int i; |
| int dir; |
| int collision_count = 0; |
| lcd_clear_display(); |
| init_worm(&worms[0], 10, 10); |
| add_growing(&worms[0], 40); |
| for (dir = 0; dir < 4; dir++) { |
| set_worm_dir(&worms[0], (EAST + dir) % 4); |
| for (i = 0; i < 10; i++) { |
| move_worm(&worms[0]); |
| draw_worm(&worms[0]); |
| } |
| } |
| |
| arghx[0] = 12; |
| for (arghy[0] = 0; arghy[0] < FIELD_RECT_HEIGHT - ARGH_SIZE; arghy[0]++){ |
| char buf[20]; |
| bool collision; |
| draw_argh(0); |
| collision = worm_argh_collision(&worms[0], 0); |
| if (collision) { |
| collision_count ++; |
| } |
| snprintf(buf, sizeof buf, "collisions: %d", collision_count); |
| lcd_putsxy(0, LCD_HEIGHT -8, buf); |
| lcd_update(); |
| } |
| if (collision_count != ARGH_SIZE * 2) { |
| button_get(true); |
| } |
| |
| arghy[0] = 12; |
| for (arghx[0] = 0; arghx[0] < FIELD_RECT_HEIGHT - ARGH_SIZE; arghx[0]++){ |
| char buf[20]; |
| bool collision; |
| draw_argh(0); |
| collision = worm_argh_collision(&worms[0], 0); |
| if (collision) { |
| collision_count ++; |
| } |
| snprintf(buf, sizeof buf, "collisions: %d", collision_count); |
| lcd_putsxy(0, LCD_HEIGHT -8, buf); |
| lcd_update(); |
| } |
| if (collision_count != ARGH_SIZE * 4) { |
| button_get(true); |
| } |
| } |
| |
| static int testline_in_rect(void) { |
| int testfailed = -1; |
| |
| int rx = 10; |
| int ry = 15; |
| int rw = 20; |
| int rh = 25; |
| |
| /* Test 1 */ |
| int x1 = 12; |
| int y1 = 8; |
| int x2 = 12; |
| int y2 = 42; |
| |
| if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) && |
| !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) { |
| lcd_drawrect(rx, ry, rw, rh); |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_update(); |
| lcd_putsxy(0, 0, "failed 1"); |
| button_get(true); |
| testfailed = 1; |
| } |
| |
| /* test 2 */ |
| y2 = 20; |
| if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) && |
| !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) { |
| lcd_drawrect(rx, ry, rw, rh); |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_putsxy(0, 0, "failed 2"); |
| lcd_update(); |
| button_get(true); |
| testfailed = 2; |
| } |
| |
| /* test 3 */ |
| y1 = 30; |
| if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) && |
| !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) { |
| lcd_drawrect(rx, ry, rw, rh); |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_putsxy(0, 0, "failed 3"); |
| lcd_update(); |
| button_get(true); |
| testfailed = 3; |
| } |
| |
| /* test 4 */ |
| y2 = 45; |
| if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) && |
| !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) { |
| lcd_drawrect(rx, ry, rw, rh); |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_putsxy(0, 0, "failed 4"); |
| lcd_update(); |
| button_get(true); |
| testfailed = 4; |
| } |
| |
| /* test 5 */ |
| y1 = 50; |
| if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) || |
| line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) { |
| lcd_drawrect(rx, ry, rw, rh); |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_putsxy(0, 0, "failed 5"); |
| lcd_update(); |
| button_get(true); |
| testfailed = 5; |
| } |
| |
| /* test 6 */ |
| y1 = 5; |
| y2 = 7; |
| if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) || |
| line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) { |
| lcd_drawrect(rx, ry, rw, rh); |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_putsxy(0, 0, "failed 6"); |
| lcd_update(); |
| button_get(true); |
| testfailed = 6; |
| } |
| |
| /* test 7 */ |
| x1 = 8; |
| y1 = 20; |
| x2 = 35; |
| y2 = 20; |
| if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) && |
| !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) { |
| lcd_drawrect(rx, ry, rw, rh); |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_putsxy(0, 0, "failed 7"); |
| lcd_update(); |
| button_get(true); |
| testfailed = 7; |
| } |
| |
| /* test 8 */ |
| x2 = 12; |
| if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) && |
| !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) { |
| lcd_drawrect(rx, ry, rw, rh); |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_putsxy(0, 0, "failed 8"); |
| lcd_update(); |
| button_get(true); |
| testfailed = 8; |
| } |
| |
| /* test 9 */ |
| x1 = 25; |
| if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) && |
| !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) { |
| lcd_drawrect(rx, ry, rw, rh); |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_putsxy(0, 0, "failed 9"); |
| lcd_update(); |
| button_get(true); |
| testfailed = 9; |
| } |
| |
| /* test 10 */ |
| x2 = 37; |
| if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) && |
| !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) { |
| lcd_drawrect(rx, ry, rw, rh); |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_putsxy(0, 0, "failed 10"); |
| lcd_update(); |
| button_get(true); |
| testfailed = 10; |
| } |
| |
| /* test 11 */ |
| x1 = 42; |
| if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) || |
| line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) { |
| lcd_drawrect(rx, ry, rw, rh); |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_putsxy(0, 0, "failed 11"); |
| lcd_update(); |
| button_get(true); |
| testfailed = 11; |
| } |
| |
| /* test 12 */ |
| x1 = 5; |
| x2 = 7; |
| if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) || |
| line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) { |
| lcd_drawrect(rx, ry, rw, rh); |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_putsxy(0, 0, "failed 12"); |
| lcd_update(); |
| button_get(true); |
| testfailed = 12; |
| } |
| |
| /* test 13 */ |
| rx = 9; |
| ry = 15; |
| rw = FOOD_SIZE; |
| rh = FOOD_SIZE; |
| |
| x1 = 10; |
| y1 = 10; |
| x2 = 10; |
| y2 = 20; |
| if (!(line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) && |
| line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh))) { |
| lcd_drawrect(rx, ry, rw, rh); |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_putsxy(0, 0, "failed 13"); |
| lcd_update(); |
| button_get(true); |
| testfailed = 13; |
| } |
| |
| /* test 14 */ |
| rx = 9; |
| ry = 15; |
| rw = 4; |
| rh = 4; |
| |
| x1 = 10; |
| y1 = 10; |
| x2 = 10; |
| y2 = 19; |
| if (!(line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) && |
| line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh))) { |
| lcd_drawline(x1, y1, x2, y2); |
| lcd_invertrect(rx, ry, rw, rh); |
| lcd_putsxy(0, 0, "failed 14"); |
| lcd_update(); |
| button_get(true); |
| testfailed = 14; |
| } |
| |
| lcd_clear_display(); |
| |
| return testfailed; |
| } |
| |
| /** |
| * Just a test routine to test wether specific_worm_collision might work properly |
| */ |
| static int test_specific_worm_collision(void) { |
| int collisions = 0; |
| int dir; |
| int x = 0; |
| int y = 0; |
| char buf[20]; |
| lcd_clear_display(); |
| init_worm(&worms[0], 10, 20); |
| add_growing(&worms[0], 20 - INITIAL_WORM_LENGTH); |
| |
| for (dir = EAST; dir < EAST + 4; dir++) { |
| int i; |
| set_worm_dir(&worms[0], dir % 4); |
| for (i = 0; i < 5; i++) { |
| if (!(dir % 4 == NORTH && i == 9)) { |
| move_worm(&worms[0]); |
| draw_worm(&worms[0]); |
| } |
| } |
| } |
| |
| for (y = 15; y < 30; y ++){ |
| for (x = 5; x < 20; x++) { |
| if (specific_worm_collision(&worms[0], x, y) != -1) { |
| collisions ++; |
| } |
| lcd_invertpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y); |
| snprintf(buf, sizeof buf, "collisions %d", collisions); |
| lcd_putsxy(0, LCD_HEIGHT - 8, buf); |
| lcd_update(); |
| } |
| } |
| if (collisions != 21) { |
| button_get(true); |
| } |
| return collisions; |
| } |
| |
| static void test_make_argh(void){ |
| int dir; |
| int seed = 0; |
| int hit = 0; |
| int failures = 0; |
| int last_failures = 0; |
| int i, worm_idx; |
| lcd_clear_display(); |
| worm_count = 3; |
| |
| for (worm_idx = 0; worm_idx < worm_count; worm_idx++) { |
| init_worm(&worms[worm_idx], 10 + worm_idx * 20, 20); |
| add_growing(&worms[worm_idx], 40 - INITIAL_WORM_LENGTH); |
| } |
| |
| for (dir = EAST; dir < EAST + 4; dir++) { |
| for (worm_idx = 0; worm_idx < worm_count; worm_idx++) { |
| set_worm_dir(&worms[worm_idx], dir % 4); |
| for (i = 0; i < 10; i++) { |
| if (!(dir % 4 == NORTH && i == 9)) { |
| move_worm(&worms[worm_idx]); |
| draw_worm(&worms[worm_idx]); |
| } |
| } |
| } |
| } |
| |
| lcd_update(); |
| |
| for (seed = 0; hit < 20; seed += 2) { |
| char buf[20]; |
| int x, y; |
| srand(seed); |
| x = rand() % (FIELD_RECT_WIDTH - ARGH_SIZE); |
| y = rand() % (FIELD_RECT_HEIGHT - ARGH_SIZE); |
| |
| for (worm_idx = 0; worm_idx < worm_count; worm_idx++){ |
| if (expensive_worm_in_rect(&worms[worm_idx], x, y, ARGH_SIZE, ARGH_SIZE)) { |
| int tries = 0; |
| srand(seed); |
| |
| tries = make_argh(0); |
| if ((x == arghx[0] && y == arghy[0]) || tries < 2) { |
| failures ++; |
| } |
| |
| snprintf(buf, sizeof buf, "(%d;%d) fail%d try%d", x, y, failures, tries); |
| lcd_putsxy(0, LCD_HEIGHT - 8, buf); |
| lcd_update(); |
| lcd_invertrect(x + FIELD_RECT_X, y+ FIELD_RECT_Y, ARGH_SIZE, ARGH_SIZE); |
| lcd_update(); |
| draw_argh(0); |
| lcd_update(); |
| lcd_invertrect(x + FIELD_RECT_X, y + FIELD_RECT_Y, ARGH_SIZE, ARGH_SIZE); |
| lcd_clearrect(arghx[0] + FIELD_RECT_X, arghy[0] + FIELD_RECT_Y, ARGH_SIZE, ARGH_SIZE); |
| |
| if (failures > last_failures) { |
| button_get(true); |
| } |
| last_failures = failures; |
| hit ++; |
| } |
| } |
| } |
| } |
| |
| static void test_worm_argh_collision_in_moves(void) { |
| int hit_count = 0; |
| int i; |
| lcd_clear_display(); |
| init_worm(&worms[0], 10, 20); |
| |
| arghx[0] = 20; |
| arghy[0] = 18; |
| draw_argh(0); |
| |
| set_worm_dir(&worms[0], EAST); |
| for (i = 0; i < 20; i++) { |
| char buf[20]; |
| move_worm(&worms[0]); |
| draw_worm(&worms[0]); |
| if (worm_argh_collision_in_moves(&worms[0], 0, 5)){ |
| hit_count ++; |
| } |
| snprintf(buf, sizeof buf, "in 5 moves hits: %d", hit_count); |
| lcd_putsxy(0, LCD_HEIGHT - 8, buf); |
| lcd_update(); |
| } |
| if (hit_count != ARGH_SIZE + 5) { |
| button_get(true); |
| } |
| } |
| #endif /* DEBUG_WORMLET */ |
| |
| extern bool use_old_rect; |
| |
| /** |
| * Main entry point from the menu to start the game control. |
| */ |
| bool wormlet(void) |
| { |
| bool worm_dead = false; |
| int button; |
| |
| lcd_setfont(FONT_SYSFIXED); |
| |
| #ifdef DEBUG_WORMLET |
| testline_in_rect(); |
| test_worm_argh_collision_in_moves(); |
| test_make_argh(); |
| test_worm_food_collision(); |
| test_worm_argh_collision(); |
| test_specific_worm_collision(); |
| #endif |
| lcd_setmargins(0,0); |
| |
| /* Setup screen */ |
| do { |
| char buf[20]; |
| lcd_clear_display(); |
| |
| /* first line players */ |
| snprintf(buf, sizeof buf, str(LANG_WORMLET_PLAYERS), players); |
| lcd_puts(0, 0, buf); |
| |
| /* second line worms */ |
| snprintf(buf, sizeof buf, str(LANG_WORMLET_WORMS), worm_count); |
| lcd_puts(0, 1, buf); |
| |
| /* third line control */ |
| if (players > 1) { |
| if (use_remote) { |
| snprintf(buf, sizeof buf, str(LANG_WORMLET_REMOTE_CTRL)); |
| } else { |
| snprintf(buf, sizeof buf, str(LANG_WORMLET_NO_REM_CTRL)); |
| } |
| } else { |
| if (players > 0) { |
| if (use_remote) { |
| snprintf(buf, sizeof buf, str(LANG_WORMLET_2_KEY_CTRL)); |
| } else { |
| snprintf(buf, sizeof buf, str(LANG_WORMLET_4_KEY_CTRL)); |
| } |
| } else { |
| snprintf(buf, sizeof buf, str(LANG_WORMLET_NO_CONTROL)); |
| } |
| } |
| lcd_puts(0, 2, buf); |
| lcd_update(); |
| |
| /* user selection */ |
| button = button_get(true); |
| switch (button) { |
| case BUTTON_UP: |
| if (players < 3) { |
| players ++; |
| if (players > worm_count) { |
| worm_count = players; |
| } |
| if (players > 2) { |
| use_remote = true; |
| } |
| } |
| break; |
| case BUTTON_DOWN: |
| if (players > 0) { |
| players --; |
| } |
| break; |
| case BUTTON_LEFT: |
| if (worm_count > 1) { |
| worm_count--; |
| if (worm_count < players) { |
| players = worm_count; |
| } |
| } |
| break; |
| case BUTTON_RIGHT: |
| if (worm_count < MAX_WORMS) { |
| worm_count ++; |
| } |
| break; |
| case BUTTON_F1: |
| use_remote = !use_remote; |
| if (players > 2) { |
| use_remote = true; |
| } |
| break; |
| |
| case SYS_USB_CONNECTED: |
| usb_screen(); |
| lcd_setfont(FONT_UI); |
| return true; |
| } |
| } while (button != BUTTON_PLAY && |
| button != BUTTON_OFF && button != BUTTON_ON); |
| |
| lcd_clear_display(); |
| /* end of setup */ |
| |
| do { |
| |
| /* button state will be overridden if |
| the game quits with the death of the worm. |
| Initializing button to BUTTON_OFF ensures |
| that the user can hit BUTTON_OFF during the |
| game to return to the menu. |
| */ |
| button = BUTTON_OFF; |
| |
| /* start the game */ |
| worm_dead = run(); |
| |
| /* if worm isn't dead the game was quit |
| via BUTTON_OFF -> no need to wait for buttons. */ |
| if (worm_dead) { |
| do { |
| button = button_get(true); |
| } |
| /* BUTTON_ON -> start new game */ |
| /* BUTTON_OFF -> back to game menu */ |
| while (button != BUTTON_OFF && button != BUTTON_ON); |
| } |
| } |
| while (button != BUTTON_OFF); |
| |
| lcd_setfont(FONT_UI); |
| |
| return false; |
| } |
| |
| |
| #endif /* USE_GAMES */ |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |