From ba371fb595affd68c823926b85718d1d613dc7d3 Mon Sep 17 00:00:00 2001 From: Björn Stenberg Date: Sun, 29 Jun 2003 16:33:04 +0000 Subject: Added plugin loader. Moved games, demos and the text viewer to loadable plugins. Copy your *.rock files to /.rockbox/rocks/ git-svn-id: svn://svn.rockbox.org/rockbox/trunk@3769 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/wormlet.c | 2009 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2009 insertions(+) create mode 100644 apps/plugins/wormlet.c (limited to 'apps/plugins/wormlet.c') diff --git a/apps/plugins/wormlet.c b/apps/plugins/wormlet.c new file mode 100644 index 0000000000..be089cdb7c --- /dev/null +++ b/apps/plugins/wormlet.c @@ -0,0 +1,2009 @@ +/*************************************************************************** + * __________ __ ___. + * 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 "plugin.h" + +#ifdef HAVE_LCD_BITMAP + +/* 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; + +/* the rockbox plugin api */ +static struct plugin_api* rb; + +#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 = rb->rand() % (FIELD_RECT_WIDTH - FOOD_SIZE); + y = rb->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 */ + rb->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 */ + rb->lcd_fillrect(foodx[index] + FIELD_RECT_X, + foody[index] + FIELD_RECT_Y, + FOOD_SIZE, FOOD_SIZE); + rb->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 = rb->rand() % (FIELD_RECT_WIDTH - ARGH_SIZE); + y = rb->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 */ + rb->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 */ + rb->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 */ + rb->lcd_invertrect(0, 0, FIELD_RECT_WIDTH + 2, FIELD_RECT_HEIGHT + 2); + rb->lcd_invertrect(1, 1, FIELD_RECT_WIDTH, FIELD_RECT_HEIGHT); + + /* make everything visible */ + rb->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) { + rb->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) { + rb->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 = NULL; + int i; + int y = 0; + rb->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 */ + rb->snprintf(buf, sizeof (buf),"Len:%d", score); + + /* worm state */ + switch (check_collision(&worms[i])) { + case COLLISION_NONE: + if (worms[i].growing > 0) + buf2 = "Growing"; + else { + if (worms[i].alive) + buf2 = "Hungry"; + else + buf2 = "Wormed"; + } + break; + + case COLLISION_WORM: + buf2 = "Wormed"; + break; + + case COLLISION_FOOD: + buf2 = "Growing"; + break; + + case COLLISION_ARGH: + buf2 = "Argh"; + break; + + case COLLISION_FIELD: + buf2 = "Crashed"; + break; + } + rb->lcd_putsxy(FIELD_RECT_WIDTH + 3, y , buf); + rb->lcd_putsxy(FIELD_RECT_WIDTH + 3, y+8, buf2); + + if (!worms[i].alive){ + rb->lcd_invertrect(FIELD_RECT_WIDTH + 2, y, + LCD_WIDTH - FIELD_RECT_WIDTH - 2, 17); + } + y += 19; + } + rb->snprintf(buf , sizeof(buf), "Hs: %d", highscore); +#ifndef DEBUG_WORMLET + rb->lcd_putsxy(FIELD_RECT_WIDTH + 3, LCD_HEIGHT - 8, buf); +#else + rb->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 = *rb->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_RC_VOL_UP: + player3_dir = (player3_dir + 1) % 4; + break; + + case BUTTON_RC_VOL_DOWN: + player3_dir = (player3_dir + 3) % 4; + break; + + case BUTTON_PLAY: + do { + button = rb->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(); + rb->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 = *rb->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; + } + rb->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 = rb->button_get_w_tmo(SPEED - cycle_duration); + cycle_start = *rb->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; + rb->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++; + } + rb->snprintf(buf, sizeof buf, "collisions: %d", collision_count); + rb->lcd_putsxy(0, LCD_HEIGHT -8, buf); + rb->lcd_update(); + } + if (collision_count != FOOD_SIZE) { + rb->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 ++; + } + rb->snprintf(buf, sizeof buf, "collisions: %d", collision_count); + rb->lcd_putsxy(0, LCD_HEIGHT -8, buf); + rb->lcd_update(); + } + if (collision_count != FOOD_SIZE * 2) { + rb->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; + rb->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 ++; + } + rb->snprintf(buf, sizeof buf, "collisions: %d", collision_count); + rb->lcd_putsxy(0, LCD_HEIGHT -8, buf); + rb->lcd_update(); + } + if (collision_count != ARGH_SIZE * 2) { + rb->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 ++; + } + rb->snprintf(buf, sizeof buf, "collisions: %d", collision_count); + rb->lcd_putsxy(0, LCD_HEIGHT -8, buf); + rb->lcd_update(); + } + if (collision_count != ARGH_SIZE * 4) { + rb->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)) { + rb->lcd_drawrect(rx, ry, rw, rh); + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_update(); + rb->lcd_putsxy(0, 0, "failed 1"); + rb->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)) { + rb->lcd_drawrect(rx, ry, rw, rh); + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_putsxy(0, 0, "failed 2"); + rb->lcd_update(); + rb->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)) { + rb->lcd_drawrect(rx, ry, rw, rh); + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_putsxy(0, 0, "failed 3"); + rb->lcd_update(); + rb->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)) { + rb->lcd_drawrect(rx, ry, rw, rh); + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_putsxy(0, 0, "failed 4"); + rb->lcd_update(); + rb->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)) { + rb->lcd_drawrect(rx, ry, rw, rh); + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_putsxy(0, 0, "failed 5"); + rb->lcd_update(); + rb->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)) { + rb->lcd_drawrect(rx, ry, rw, rh); + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_putsxy(0, 0, "failed 6"); + rb->lcd_update(); + rb->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)) { + rb->lcd_drawrect(rx, ry, rw, rh); + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_putsxy(0, 0, "failed 7"); + rb->lcd_update(); + rb->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)) { + rb->lcd_drawrect(rx, ry, rw, rh); + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_putsxy(0, 0, "failed 8"); + rb->lcd_update(); + rb->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)) { + rb->lcd_drawrect(rx, ry, rw, rh); + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_putsxy(0, 0, "failed 9"); + rb->lcd_update(); + rb->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)) { + rb->lcd_drawrect(rx, ry, rw, rh); + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_putsxy(0, 0, "failed 10"); + rb->lcd_update(); + rb->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)) { + rb->lcd_drawrect(rx, ry, rw, rh); + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_putsxy(0, 0, "failed 11"); + rb->lcd_update(); + rb->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)) { + rb->lcd_drawrect(rx, ry, rw, rh); + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_putsxy(0, 0, "failed 12"); + rb->lcd_update(); + rb->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))) { + rb->lcd_drawrect(rx, ry, rw, rh); + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_putsxy(0, 0, "failed 13"); + rb->lcd_update(); + rb->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))) { + rb->lcd_drawline(x1, y1, x2, y2); + rb->lcd_invertrect(rx, ry, rw, rh); + rb->lcd_putsxy(0, 0, "failed 14"); + rb->lcd_update(); + rb->button_get(true); + testfailed = 14; + } + + rb->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]; + rb->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 ++; + } + rb->lcd_invertpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y); + rb->snprintf(buf, sizeof buf, "collisions %d", collisions); + rb->lcd_putsxy(0, LCD_HEIGHT - 8, buf); + rb->lcd_update(); + } + } + if (collisions != 21) { + rb->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; + rb->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]); + } + } + } + } + + rb->lcd_update(); + + for (seed = 0; hit < 20; seed += 2) { + char buf[20]; + int x, y; + rb->srand(seed); + x = rb->rand() % (FIELD_RECT_WIDTH - ARGH_SIZE); + y = rb->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; + rb->srand(seed); + + tries = make_argh(0); + if ((x == arghx[0] && y == arghy[0]) || tries < 2) { + failures ++; + } + + rb->snprintf(buf, sizeof buf, "(%d;%d) fail%d try%d", x, y, failures, tries); + rb->lcd_putsxy(0, LCD_HEIGHT - 8, buf); + rb->lcd_update(); + rb->lcd_invertrect(x + FIELD_RECT_X, y+ FIELD_RECT_Y, ARGH_SIZE, ARGH_SIZE); + rb->lcd_update(); + draw_argh(0); + rb->lcd_update(); + rb->lcd_invertrect(x + FIELD_RECT_X, y + FIELD_RECT_Y, ARGH_SIZE, ARGH_SIZE); + rb->lcd_clearrect(arghx[0] + FIELD_RECT_X, arghy[0] + FIELD_RECT_Y, ARGH_SIZE, ARGH_SIZE); + + if (failures > last_failures) { + rb->button_get(true); + } + last_failures = failures; + hit ++; + } + } + } +} + +static void test_worm_argh_collision_in_moves(void) { + int hit_count = 0; + int i; + rb->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 ++; + } + rb->snprintf(buf, sizeof buf, "in 5 moves hits: %d", hit_count); + rb->lcd_putsxy(0, LCD_HEIGHT - 8, buf); + rb->lcd_update(); + } + if (hit_count != ARGH_SIZE + 5) { + rb->button_get(true); + } +} +#endif /* DEBUG_WORMLET */ + +extern bool use_old_rect; + +/** + * Main entry point + */ +enum plugin_status plugin_start(struct plugin_api* api, void* parameter) +{ + bool worm_dead = false; + int button; + + TEST_PLUGIN_API(api); + (void)(parameter); + + rb = api; + rb->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 + + /* Setup screen */ + do { + char buf[20]; + char* ptr; + rb->lcd_clear_display(); + + /* first line players */ + rb->snprintf(buf, sizeof buf, "%d Players UP/DN", players); + rb->lcd_puts(0, 0, buf); + + /* second line worms */ + rb->snprintf(buf, sizeof buf, "%d Worms L/R", worm_count); + rb->lcd_puts(0, 1, buf); + + /* third line control */ + if (players > 1) { + if (use_remote) { + ptr = "Remote Control F1"; + } else { + ptr = "No Rem. Control F1"; + } + } else { + if (players > 0) { + if (use_remote) { + ptr = "2 Key Control F1"; + } else { + ptr = "4 Key Control F1"; + } + } else { + ptr = "Out Of Control"; + } + } + rb->lcd_puts(0, 2, ptr); + rb->lcd_update(); + + /* user selection */ + button = rb->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: + rb->usb_screen(); + return PLUGIN_USB_CONNECTED; + } + } while (button != BUTTON_PLAY && + button != BUTTON_OFF && button != BUTTON_ON); + + rb->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 = rb->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); + + return PLUGIN_OK; +} + +#endif -- cgit v1.2.3