summaryrefslogtreecommitdiff
path: root/apps/plugins/wormlet.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/wormlet.c')
-rw-r--r--apps/plugins/wormlet.c2009
1 files changed, 2009 insertions, 0 deletions
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 Philipp Pertermann
11 *
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19#include "plugin.h"
20
21#ifdef HAVE_LCD_BITMAP
22
23/* size of the field the worm lives in */
24#define FIELD_RECT_X 1
25#define FIELD_RECT_Y 1
26#define FIELD_RECT_WIDTH (LCD_WIDTH - 45)
27#define FIELD_RECT_HEIGHT (LCD_HEIGHT - 2)
28
29/* size of the ring of the worm
30 choos a value that is a power of 2 to help
31 the compiler optimize modul operations*/
32#define MAX_WORM_SEGMENTS 64
33
34/* when the game starts */
35#define INITIAL_WORM_LENGTH 10
36
37/* num of pixel the worm grows per eaten food */
38#define WORM_PER_FOOD 7
39
40/* num of worms creeping in the FIELD */
41#define MAX_WORMS 3
42
43/* minimal distance between a worm and an argh
44 when a new argh is made */
45#define MIN_ARGH_DIST 5
46
47/**
48 * All the properties that a worm has.
49 */
50static struct worm {
51 /* The worm is stored in a ring of xy coordinates */
52 char x[MAX_WORM_SEGMENTS];
53 char y[MAX_WORM_SEGMENTS];
54
55 int head; /* index of the head within the buffer */
56 int tail; /* index of the tail within the buffer */
57 int growing; /* number of cyles the worm still keeps growing */
58 bool alive; /* the worms living state */
59
60 /* direction vector in which the worm moves */
61 int dirx; /* only values -1 0 1 allowed */
62 int diry; /* only values -1 0 1 allowed */
63
64 /* this method is used to fetch the direction the user
65 has selected. It can be one of the values
66 human_player1, human_player2, remote_player, virtual_player.
67 All these values are fuctions, that can change the direction
68 of the worm */
69 void (*fetch_worm_direction)(struct worm *w);
70} worms[MAX_WORMS];
71
72/* stores the highscore - besides it was scored by a virtual player */
73static int highscore;
74
75#define MAX_FOOD 5 /* maximal number of food items */
76#define FOOD_SIZE 3 /* the width and height of a food */
77
78/* The arrays store the food coordinates */
79static char foodx[MAX_FOOD];
80static char foody[MAX_FOOD];
81
82#define MAX_ARGH 100 /* maximal number of argh items */
83#define ARGH_SIZE 4 /* the width and height of a argh */
84#define ARGHS_PER_FOOD 2 /* number of arghs produced per eaten food */
85
86/* The arrays store the argh coordinates */
87static char arghx[MAX_ARGH];
88static char arghy[MAX_ARGH];
89
90/* the number of arghs that are currently in use */
91static int argh_count;
92
93#ifdef DEBUG_WORMLET
94/* just a buffer used for debug output */
95static char debugout[15];
96#endif
97
98/* the number of ticks each game cycle should take */
99#define SPEED 14
100
101/* the number of active worms (dead or alive) */
102static int worm_count = MAX_WORMS;
103
104/* in multiplayer mode: en- / disables the remote worm control
105 in singleplayer mode: toggles 4 / 2 button worm control */
106static bool use_remote = false;
107
108/* return values of check_collision */
109#define COLLISION_NONE 0
110#define COLLISION_WORM 1
111#define COLLISION_FOOD 2
112#define COLLISION_ARGH 3
113#define COLLISION_FIELD 4
114
115/* constants for use as directions.
116 Note that the values are ordered clockwise.
117 Thus increasing / decreasing the values
118 is equivalent to right / left turns. */
119#define WEST 0
120#define NORTH 1
121#define EAST 2
122#define SOUTH 3
123
124/* direction of human player 1 */
125static int player1_dir = EAST;
126/* direction of human player 2 */
127static int player2_dir = EAST;
128/* direction of human player 3 */
129static int player3_dir = EAST;
130
131/* the number of (human) players that currently
132 control a worm */
133static int players = 1;
134
135/* the rockbox plugin api */
136static struct plugin_api* rb;
137
138#ifdef DEBUG_WORMLET
139static void set_debug_out(char *str){
140 strcpy(debugout, str);
141}
142#endif
143
144/**
145 * Returns the direction id in which the worm
146 * currently is creeping.
147 * @param struct worm *w The worm that is to be investigated.
148 * w Must not be null.
149 * @return int A value 0 <= value < 4
150 * Note the predefined constants NORTH, SOUTH, EAST, WEST
151 */
152static int get_worm_dir(struct worm *w) {
153 int retVal ;
154 if (w->dirx == 0) {
155 if (w->diry == 1) {
156 retVal = SOUTH;
157 } else {
158 retVal = NORTH;
159 }
160 } else {
161 if (w->dirx == 1) {
162 retVal = EAST;
163 } else {
164 retVal = WEST;
165 }
166 }
167 return retVal;
168}
169
170/**
171 * Set the direction of the specified worm with a direction id.
172 * Increasing the value by 1 means to turn the worm direction
173 * to right by 90 degree.
174 * @param struct worm *w The worm that is to be altered. w Must not be null.
175 * @param int dir The new direction in which the worm is to creep.
176 * dir must be 0 <= dir < 4. Use predefined constants
177 * NORTH, SOUTH, EAST, WEST
178 */
179static void set_worm_dir(struct worm *w, int dir) {
180 switch (dir) {
181 case WEST:
182 w->dirx = -1;
183 w->diry = 0;
184 break;
185 case NORTH:
186 w->dirx = 0;
187 w->diry = - 1;
188 break;
189 case EAST:
190 w->dirx = 1;
191 w->diry = 0;
192 break;
193 case SOUTH:
194 w->dirx = 0;
195 w->diry = 1;
196 break;
197 }
198}
199
200/**
201 * Returns the current length of the worm array. This
202 * is also a value for the number of bends that are in the worm.
203 * @return int a positive value with 0 <= value < MAX_WORM_SEGMENTS
204 */
205static int get_worm_array_length(struct worm *w) {
206 /* initial simple calculation will be overwritten if wrong. */
207 int retVal = w->head - w->tail;
208
209 /* if the worm 'crosses' the boundaries of the ringbuffer */
210 if (retVal < 0) {
211 retVal = w->head + MAX_WORM_SEGMENTS - w->tail;
212 }
213
214 return retVal;
215}
216
217/**
218 * Returns the score the specified worm. The score is the length
219 * of the worm.
220 * @param struct worm *w The worm that is to be investigated.
221 * w must not be null.
222 * @return int The length of the worm (>= 0).
223 */
224static int get_score(struct worm *w) {
225 int retval = 0;
226 int length = get_worm_array_length(w);
227 int i;
228 for (i = 0; i < length; i++) {
229
230 /* The iteration iterates the length of the worm.
231 Here's the conversion to the true indices within the worm arrays. */
232 int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS;
233 int lineend = (linestart + 1) % MAX_WORM_SEGMENTS;
234 int startx = w->x[linestart];
235 int starty = w->y[linestart];
236 int endx = w->x[lineend];
237 int endy = w->y[lineend];
238
239 int minimum, maximum;
240
241 if (startx == endx) {
242 minimum = MIN(starty, endy);
243 maximum = MAX(starty, endy);
244 } else {
245 minimum = MIN(startx, endx);
246 maximum = MAX(startx, endx);
247 }
248 retval += abs(maximum - minimum);
249 }
250 return retval;
251}
252
253/**
254 * Determines wether the line specified by startx, starty, endx, endy intersects
255 * the rectangle specified by x, y, width, height. Note that the line must be exactly
256 * horizontal or vertical (startx == endx or starty == endy).
257 * @param int startx The x coordinate of the start point of the line.
258 * @param int starty The y coordinate of the start point of the line.
259 * @param int endx The x coordinate of the end point of the line.
260 * @param int endy The y coordinate of the end point of the line.
261 * @param int x The x coordinate of the top left corner of the rectangle.
262 * @param int y The y coordinate of the top left corner of the rectangle.
263 * @param int width The width of the rectangle.
264 * @param int height The height of the rectangle.
265 * @return bool Returns true if the specified line intersects with the recangle.
266 */
267static bool line_in_rect(int startx, int starty, int endx, int endy, int x, int y, int width, int height) {
268 bool retval = false;
269 int simple, simplemin, simplemax;
270 int compa, compb, compmin, compmax;
271 int temp;
272 if (startx == endx) {
273 simple = startx;
274 simplemin = x;
275 simplemax = x + width;
276
277 compa = starty;
278 compb = endy;
279 compmin = y;
280 compmax = y + height;
281 } else {
282 simple = starty;
283 simplemin = y;
284 simplemax = y + height;
285
286 compa = startx;
287 compb = endx;
288 compmin = x;
289 compmax = x + width;
290 };
291
292 temp = compa;
293 compa = MIN(compa, compb);
294 compb = MAX(temp, compb);
295
296 if (simplemin <= simple && simple <= simplemax) {
297 if ((compmin <= compa && compa <= compmax) ||
298 (compmin <= compb && compb <= compmax) ||
299 (compa <= compmin && compb >= compmax)) {
300 retval = true;
301 }
302 }
303 return retval;
304}
305
306/**
307 * Tests wether the specified worm intersects with the rect.
308 * @param struct worm *w The worm to be investigated
309 * @param int x The x coordinate of the top left corner of the rect
310 * @param int y The y coordinate of the top left corner of the rect
311 * @param int widht The width of the rect
312 * @param int height The height of the rect
313 * @return bool Returns true if the worm intersects with the rect
314 */
315static bool worm_in_rect(struct worm *w, int x, int y, int width, int height) {
316 bool retval = false;
317
318
319 /* get_worm_array_length is expensive -> buffer the value */
320 int wormLength = get_worm_array_length(w);
321 int i;
322
323 /* test each entry that is part of the worm */
324 for (i = 0; i < wormLength && retval == false; i++) {
325
326 /* The iteration iterates the length of the worm.
327 Here's the conversion to the true indices within the worm arrays. */
328 int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS;
329 int lineend = (linestart + 1) % MAX_WORM_SEGMENTS;
330 int startx = w->x[linestart];
331 int starty = w->y[linestart];
332 int endx = w->x[lineend];
333 int endy = w->y[lineend];
334
335 retval = line_in_rect(startx, starty, endx, endy, x, y, width, height);
336 }
337
338 return retval;
339}
340
341/**
342 * Checks wether a specific food in the food arrays is at the
343 * specified coordinates.
344 * @param int foodIndex The index of the food in the food arrays
345 * @param int x the x coordinate.
346 * @param int y the y coordinate.
347 * @return Returns true if the coordinate hits the food specified by
348 * foodIndex.
349 */
350static bool specific_food_collision(int foodIndex, int x, int y) {
351 bool retVal = false;
352 if (x >= foodx[foodIndex] &&
353 x < foodx[foodIndex] + FOOD_SIZE &&
354 y >= foody[foodIndex] &&
355 y < foody[foodIndex] + FOOD_SIZE) {
356
357 retVal = true;
358 }
359 return retVal;
360}
361
362/**
363 * Returns the index of the food that is at the
364 * given coordinates. If no food is at the coordinates
365 * -1 is returned.
366 * @return int -1 <= value < MAX_FOOD
367 */
368static int food_collision(int x, int y) {
369 int i = 0;
370 int retVal = -1;
371 for (i = 0; i < MAX_FOOD; i++) {
372 if (specific_food_collision(i, x, y)) {
373 retVal = i;
374 break;
375 }
376 }
377 return retVal;
378}
379
380/**
381 * Checks wether a specific argh in the argh arrays is at the
382 * specified coordinates.
383 * @param int arghIndex The index of the argh in the argh arrays
384 * @param int x the x coordinate.
385 * @param int y the y coordinate.
386 * @return Returns true if the coordinate hits the argh specified by
387 * arghIndex.
388 */
389static bool specific_argh_collision(int arghIndex, int x, int y) {
390
391 if ( x >= arghx[arghIndex] &&
392 y >= arghy[arghIndex] &&
393 x < arghx[arghIndex] + ARGH_SIZE &&
394 y < arghy[arghIndex] + ARGH_SIZE )
395 {
396 return true;
397 }
398
399 return false;
400}
401
402/**
403 * Returns the index of the argh that is at the
404 * given coordinates. If no argh is at the coordinates
405 * -1 is returned.
406 * @param int x The x coordinate.
407 * @param int y The y coordinate.
408 * @return int -1 <= value < argh_count <= MAX_ARGH
409 */
410static int argh_collision(int x, int y) {
411 int i = 0;
412 int retVal = -1;
413
414 /* search for the argh that has the specified coords */
415 for (i = 0; i < argh_count; i++) {
416 if (specific_argh_collision(i, x, y)) {
417 retVal = i;
418 break;
419 }
420 }
421 return retVal;
422}
423
424/**
425 * Checks wether the worm collides with the food at the specfied food-arrays.
426 * @param int foodIndex The index of the food in the arrays. Ensure the value is
427 * 0 <= foodIndex <= MAX_FOOD
428 * @return Returns true if the worm collides with the specified food.
429 */
430static bool worm_food_collision(struct worm *w, int foodIndex)
431{
432 bool retVal = false;
433
434 retVal = worm_in_rect(w, foodx[foodIndex], foody[foodIndex],
435 FOOD_SIZE - 1, FOOD_SIZE - 1);
436
437 return retVal;
438}
439
440/**
441 * Returns true if the worm hits the argh within the next moves (unless
442 * the worm changes it's direction).
443 * @param struct worm *w - The worm to investigate
444 * @param int argh_idx - The index of the argh
445 * @param int moves - The number of moves that are considered.
446 * @return Returns false if the specified argh is not hit within the next
447 * moves.
448 */
449static bool worm_argh_collision_in_moves(struct worm *w, int argh_idx, int moves){
450 bool retVal = false;
451 int x1, y1, x2, y2;
452 x1 = w->x[w->head];
453 y1 = w->y[w->head];
454
455 x2 = w->x[w->head] + moves * w->dirx;
456 y2 = w->y[w->head] + moves * w->diry;
457
458 retVal = line_in_rect(x1, y1, x2, y2, arghx[argh_idx], arghy[argh_idx],
459 ARGH_SIZE, ARGH_SIZE);
460 return retVal;
461}
462
463/**
464 * Checks wether the worm collides with the argh at the specfied argh-arrays.
465 * @param int arghIndex The index of the argh in the arrays.
466 * Ensure the value is 0 <= arghIndex < argh_count <= MAX_ARGH
467 * @return Returns true if the worm collides with the specified argh.
468 */
469static bool worm_argh_collision(struct worm *w, int arghIndex)
470{
471 bool retVal = false;
472
473 retVal = worm_in_rect(w, arghx[arghIndex], arghy[arghIndex],
474 ARGH_SIZE - 1, ARGH_SIZE - 1);
475
476 return retVal;
477}
478
479/**
480 * Find new coordinates for the food stored in foodx[index], foody[index]
481 * that don't collide with any other food or argh
482 * @param int index
483 * Ensure that 0 <= index < MAX_FOOD.
484 */
485static int make_food(int index) {
486
487 int x = 0;
488 int y = 0;
489 bool collisionDetected = false;
490 int tries = 0;
491 int i;
492
493 do {
494 /* make coordinates for a new food so that
495 the entire food lies within the FIELD */
496 x = rb->rand() % (FIELD_RECT_WIDTH - FOOD_SIZE);
497 y = rb->rand() % (FIELD_RECT_HEIGHT - FOOD_SIZE);
498 tries ++;
499
500 /* Ensure that the new food doesn't collide with any
501 existing foods or arghs.
502 If one or more corners of the new food hit any existing
503 argh or food a collision is detected.
504 */
505 collisionDetected =
506 food_collision(x , y ) >= 0 ||
507 food_collision(x , y + FOOD_SIZE - 1) >= 0 ||
508 food_collision(x + FOOD_SIZE - 1, y ) >= 0 ||
509 food_collision(x + FOOD_SIZE - 1, y + FOOD_SIZE - 1) >= 0 ||
510 argh_collision(x , y ) >= 0 ||
511 argh_collision(x , y + FOOD_SIZE - 1) >= 0 ||
512 argh_collision(x + FOOD_SIZE - 1, y ) >= 0 ||
513 argh_collision(x + FOOD_SIZE - 1, y + FOOD_SIZE - 1) >= 0;
514
515 /* use coordinates for further testing */
516 foodx[index] = x;
517 foody[index] = y;
518
519 /* now test wether we accidently hit the worm with food ;) */
520 i = 0;
521 for (i = 0; i < worm_count && !collisionDetected; i++) {
522 collisionDetected |= worm_food_collision(&worms[i], index);
523 }
524 }
525 while (collisionDetected);
526 return tries;
527}
528
529/**
530 * Clears a food from the lcd buffer.
531 * @param int index The index of the food arrays under which
532 * the coordinates of the desired food can be found. Ensure
533 * that the value is 0 <= index <= MAX_FOOD.
534 */
535static void clear_food(int index)
536{
537 /* remove the old food from the screen */
538 rb->lcd_clearrect(foodx[index] + FIELD_RECT_X,
539 foody[index] + FIELD_RECT_Y,
540 FOOD_SIZE, FOOD_SIZE);
541}
542
543/**
544 * Draws a food in the lcd buffer.
545 * @param int index The index of the food arrays under which
546 * the coordinates of the desired food can be found. Ensure
547 * that the value is 0 <= index <= MAX_FOOD.
548 */
549static void draw_food(int index)
550{
551 /* draw the food object */
552 rb->lcd_fillrect(foodx[index] + FIELD_RECT_X,
553 foody[index] + FIELD_RECT_Y,
554 FOOD_SIZE, FOOD_SIZE);
555 rb->lcd_clearrect(foodx[index] + FIELD_RECT_X + 1,
556 foody[index] + FIELD_RECT_Y + 1,
557 FOOD_SIZE - 2, FOOD_SIZE - 2);
558}
559
560/**
561 * Find new coordinates for the argh stored in arghx[index], arghy[index]
562 * that don't collide with any other food or argh.
563 * @param int index
564 * Ensure that 0 <= index < argh_count < MAX_ARGH.
565 */
566static int make_argh(int index)
567{
568 int x = -1;
569 int y = -1;
570 bool collisionDetected = false;
571 int tries = 0;
572 int i;
573
574 do {
575 /* make coordinates for a new argh so that
576 the entire food lies within the FIELD */
577 x = rb->rand() % (FIELD_RECT_WIDTH - ARGH_SIZE);
578 y = rb->rand() % (FIELD_RECT_HEIGHT - ARGH_SIZE);
579 tries ++;
580
581 /* Ensure that the new argh doesn't intersect with any
582 existing foods or arghs.
583 If one or more corners of the new argh hit any existing
584 argh or food an intersection is detected.
585 */
586 collisionDetected =
587 food_collision(x , y ) >= 0 ||
588 food_collision(x , y + ARGH_SIZE - 1) >= 0 ||
589 food_collision(x + ARGH_SIZE - 1, y ) >= 0 ||
590 food_collision(x + ARGH_SIZE - 1, y + ARGH_SIZE - 1) >= 0 ||
591 argh_collision(x , y ) >= 0 ||
592 argh_collision(x , y + ARGH_SIZE - 1) >= 0 ||
593 argh_collision(x + ARGH_SIZE - 1, y ) >= 0 ||
594 argh_collision(x + ARGH_SIZE - 1, y + ARGH_SIZE - 1) >= 0;
595
596 /* use the candidate coordinates to make a real argh */
597 arghx[index] = x;
598 arghy[index] = y;
599
600 /* now test wether we accidently hit the worm with argh ;) */
601 for (i = 0; i < worm_count && !collisionDetected; i++) {
602 collisionDetected |= worm_argh_collision(&worms[i], index);
603 collisionDetected |= worm_argh_collision_in_moves(&worms[i], index,
604 MIN_ARGH_DIST);
605 }
606 }
607 while (collisionDetected);
608 return tries;
609}
610
611/**
612 * Draws an argh in the lcd buffer.
613 * @param int index The index of the argh arrays under which
614 * the coordinates of the desired argh can be found. Ensure
615 * that the value is 0 <= index < argh_count <= MAX_ARGH.
616 */
617static void draw_argh(int index)
618{
619 /* draw the new argh */
620 rb->lcd_fillrect(arghx[index] + FIELD_RECT_X,
621 arghy[index] + FIELD_RECT_Y,
622 ARGH_SIZE, ARGH_SIZE);
623}
624
625static void virtual_player(struct worm *w);
626/**
627 * Initialzes the specified worm with INITIAL_WORM_LENGTH
628 * and the tail at the specified position. The worm will
629 * be initialized alive and creeping EAST.
630 * @param struct worm *w The worm that is to be initialized
631 * @param int x The x coordinate at which the tail of the worm starts.
632 * x must be 0 <= x < FIELD_RECT_WIDTH.
633 * @param int y The y coordinate at which the tail of the worm starts
634 * y must be 0 <= y < FIELD_RECT_WIDTH.
635 */
636static void init_worm(struct worm *w, int x, int y){
637 /* initialize the worm size */
638 w->head = 1;
639 w->tail = 0;
640
641 w->x[w->head] = x + 1;
642 w->y[w->head] = y;
643
644 w->x[w->tail] = x;
645 w->y[w->tail] = y;
646
647 /* set the initial direction the worm creeps to */
648 w->dirx = 1;
649 w->diry = 0;
650
651 w->growing = INITIAL_WORM_LENGTH - 1;
652 w->alive = true;
653 w->fetch_worm_direction = virtual_player;
654}
655
656/**
657 * Writes the direction that was stored for
658 * human player 1 into the specified worm. This function
659 * may be used to be stored in worm.fetch_worm_direction.
660 * The value of
661 * the direction is read from player1_dir.
662 * @param struct worm *w - The worm of which the direction
663 * is altered.
664 */
665static void human_player1(struct worm *w) {
666 set_worm_dir(w, player1_dir);
667}
668
669/**
670 * Writes the direction that was stored for
671 * human player 2 into the specified worm. This function
672 * may be used to be stored in worm.fetch_worm_direction.
673 * The value of
674 * the direction is read from player2_dir.
675 * @param struct worm *w - The worm of which the direction
676 * is altered.
677 */
678static void human_player2(struct worm *w) {
679 set_worm_dir(w, player2_dir);
680}
681
682/**
683 * Writes the direction that was stored for
684 * human player using a remote control
685 * into the specified worm. This function
686 * may be used to be stored in worm.fetch_worm_direction.
687 * The value of
688 * the direction is read from player3_dir.
689 * @param struct worm *w - The worm of which the direction
690 * is altered.
691 */
692static void remote_player(struct worm *w) {
693 set_worm_dir(w, player3_dir);
694}
695
696/**
697 * Initializes the worm-, food- and argh-arrays, draws a frame,
698 * makes some food and argh and display all that stuff.
699 */
700static void init_wormlet(void)
701{
702 int i;
703
704 for (i = 0; i< worm_count; i++) {
705 /* Initialize all the worm coordinates to center. */
706 int x = (int)(FIELD_RECT_WIDTH / 2);
707 int y = (int)((FIELD_RECT_HEIGHT - 20)/ 2) + i * 10;
708
709 init_worm(&worms[i], x, y);
710 }
711
712 player1_dir = EAST;
713 player2_dir = EAST;
714 player3_dir = EAST;
715
716 if (players > 0) {
717 worms[0].fetch_worm_direction = human_player1;
718 }
719
720 if (players > 1) {
721 if (use_remote) {
722 worms[1].fetch_worm_direction = remote_player;
723 } else {
724 worms[1].fetch_worm_direction = human_player2;
725 }
726 }
727
728 if (players > 2) {
729 worms[2].fetch_worm_direction = human_player2;
730 }
731
732 /* Needed when the game is restarted using BUTTON_ON */
733 rb->lcd_clear_display();
734
735 /* make and display some food and argh */
736 argh_count = MAX_FOOD;
737 for (i = 0; i < MAX_FOOD; i++) {
738 make_food(i);
739 draw_food(i);
740 make_argh(i);
741 draw_argh(i);
742 }
743
744 /* draw the game field */
745 rb->lcd_invertrect(0, 0, FIELD_RECT_WIDTH + 2, FIELD_RECT_HEIGHT + 2);
746 rb->lcd_invertrect(1, 1, FIELD_RECT_WIDTH, FIELD_RECT_HEIGHT);
747
748 /* make everything visible */
749 rb->lcd_update();
750}
751
752
753/**
754 * Move the worm one step further if it is alive.
755 * The direction in which the worm moves is taken from dirx and diry.
756 * move_worm decreases growing if > 0. While the worm is growing the tail
757 * is left untouched.
758 * @param struct worm *w The worm to move. w must not be NULL.
759 */
760static void move_worm(struct worm *w)
761{
762 if (w->alive) {
763 /* determine the head point and its precessor */
764 int headx = w->x[w->head];
765 int heady = w->y[w->head];
766 int prehead = (w->head + MAX_WORM_SEGMENTS - 1) % MAX_WORM_SEGMENTS;
767 int preheadx = w->x[prehead];
768 int preheady = w->y[prehead];
769
770 /* determine the old direction */
771 int olddirx;
772 int olddiry;
773 if (headx == preheadx) {
774 olddirx = 0;
775 olddiry = (heady > preheady) ? 1 : -1;
776 } else {
777 olddiry = 0;
778 olddirx = (headx > preheadx) ? 1 : -1;
779 }
780
781 /* olddir == dir?
782 a change of direction means a new segment
783 has been opened */
784 if (olddirx != w->dirx ||
785 olddiry != w->diry) {
786 w->head = (w->head + 1) % MAX_WORM_SEGMENTS;
787 }
788
789 /* new head position */
790 w->x[w->head] = headx + w->dirx;
791 w->y[w->head] = heady + w->diry;
792
793
794 /* while the worm is growing no tail procession is necessary */
795 if (w->growing > 0) {
796 /* update the worms grow state */
797 w->growing--;
798 }
799
800 /* if the worm isn't growing the tail has to be dragged */
801 else {
802 /* index of the end of the tail segment */
803 int tail_segment_end = (w->tail + 1) % MAX_WORM_SEGMENTS;
804
805 /* drag the end of the tail */
806 /* only one coordinate has to be altered. Here it is
807 determined which one */
808 int dir = 0; /* specifies wether the coord has to be in- or decreased */
809 if (w->x[w->tail] == w->x[tail_segment_end]) {
810 dir = (w->y[w->tail] - w->y[tail_segment_end] < 0) ? 1 : -1;
811 w->y[w->tail] += dir;
812 } else {
813 dir = (w->x[w->tail] - w->x[tail_segment_end] < 0) ? 1 : -1;
814 w->x[w->tail] += dir;
815 }
816
817 /* when the tail has been dragged so far that it meets
818 the next segment start the tail segment is obsolete and
819 must be freed */
820 if (w->x[w->tail] == w->x[tail_segment_end] &&
821 w->y[w->tail] == w->y[tail_segment_end]){
822
823 /* drop the last tail point */
824 w->tail = tail_segment_end;
825 }
826 }
827 }
828}
829
830/**
831 * Draws the head and clears the tail of the worm in
832 * the display buffer. lcd_update() is NOT called thus
833 * the caller has to take care that the buffer is displayed.
834 */
835static void draw_worm(struct worm *w)
836{
837 /* draw the new head */
838 int x = w->x[w->head];
839 int y = w->y[w->head];
840 if (x >= 0 && x < FIELD_RECT_WIDTH && y >= 0 && y < FIELD_RECT_HEIGHT) {
841 rb->lcd_drawpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y);
842 }
843
844 /* clear the space behind the worm */
845 x = w->x[w->tail] ;
846 y = w->y[w->tail] ;
847 if (x >= 0 && x < FIELD_RECT_WIDTH && y >= 0 && y < FIELD_RECT_HEIGHT) {
848 rb->lcd_clearpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y);
849 }
850}
851
852/**
853 * Checks wether the coordinate is part of the worm. Returns
854 * true if any part of the worm was hit - including the head.
855 * @param x int The x coordinate
856 * @param y int The y coordinate
857 * @return int The index of the worm arrays that contain x, y.
858 * Returns -1 if the coordinates are not part of the worm.
859 */
860static int specific_worm_collision(struct worm *w, int x, int y)
861{
862 int retVal = -1;
863
864 /* get_worm_array_length is expensive -> buffer the value */
865 int wormLength = get_worm_array_length(w);
866 int i;
867
868 /* test each entry that is part of the worm */
869 for (i = 0; i < wormLength && retVal == -1; i++) {
870
871 /* The iteration iterates the length of the worm.
872 Here's the conversion to the true indices within the worm arrays. */
873 int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS;
874 int lineend = (linestart + 1) % MAX_WORM_SEGMENTS;
875 bool samex = (w->x[linestart] == x) && (w->x[lineend] == x);
876 bool samey = (w->y[linestart] == y) && (w->y[lineend] == y);
877 if (samex || samey){
878 int test, min, max, tmp;
879
880 if (samey) {
881 min = w->x[linestart];
882 max = w->x[lineend];
883 test = x;
884 } else {
885 min = w->y[linestart];
886 max = w->y[lineend];
887 test = y;
888 }
889
890 tmp = min;
891 min = MIN(min, max);
892 max = MAX(tmp, max);
893
894 if (min <= test && test <= max) {
895 retVal = lineend;
896 }
897 }
898 }
899 return retVal;
900}
901
902/**
903 * Increases the length of the specified worm by marking
904 * that it may grow by len pixels. Note that the worm has
905 * to move to make the growing happen.
906 * @param worm *w The worm that is to be altered.
907 * @param int len A positive value specifying the amount of
908 * pixels the worm may grow.
909 */
910static void add_growing(struct worm *w, int len) {
911 w->growing += len;
912}
913
914/**
915 * Determins the worm that is at the coordinates x, y. The parameter
916 * w is a switch parameter that changes the functionality of worm_collision.
917 * If w is specified and x,y hits the head of w NULL is returned.
918 * This is a useful way to determine wether the head of w hits
919 * any worm but including itself but excluding its own head.
920 * (It hits always its own head ;))
921 * If w is set to NULL worm_collision returns any worm including all heads
922 * that is at position of x,y.
923 * @param struct worm *w The worm of which the head should be excluded in
924 * the test. w may be set to NULL.
925 * @param int x The x coordinate that is checked
926 * @param int y The y coordinate that is checkec
927 * @return struct worm* The worm that has been hit by x,y. If no worm
928 * was at the position NULL is returned.
929 */
930static struct worm* worm_collision(struct worm *w, int x, int y){
931 struct worm *retVal = NULL;
932 int i;
933 for (i = 0; (i < worm_count) && (retVal == NULL); i++) {
934 int collision_at = specific_worm_collision(&worms[i], x, y);
935 if (collision_at != -1) {
936 if (!(w == &worms[i] && collision_at == w->head)){
937 retVal = &worms[i];
938 }
939 }
940 }
941 return retVal;
942}
943
944/**
945 * Returns true if the head of the worm just has
946 * crossed the field boundaries.
947 * @return bool true if the worm just has wrapped.
948 */
949static bool field_collision(struct worm *w)
950{
951 bool retVal = false;
952 if ((w->x[w->head] >= FIELD_RECT_WIDTH) ||
953 (w->y[w->head] >= FIELD_RECT_HEIGHT) ||
954 (w->x[w->head] < 0) ||
955 (w->y[w->head] < 0))
956 {
957 retVal = true;
958 }
959 return retVal;
960}
961
962
963/**
964 * Returns true if the specified coordinates are within the
965 * field specified by the FIELD_RECT_XXX constants.
966 * @param int x The x coordinate of the point that is investigated
967 * @param int y The y coordinate of the point that is investigated
968 * @return bool Returns false if x,y specifies a point outside the
969 * field of worms.
970 */
971static bool is_in_field_rect(int x, int y) {
972 bool retVal = false;
973 retVal = (x >= 0 && x < FIELD_RECT_WIDTH &&
974 y >= 0 && y < FIELD_RECT_HEIGHT);
975 return retVal;
976}
977
978/**
979 * Checks and returns wether the head of the w
980 * is colliding with something currently.
981 * @return int One of the values:
982 * COLLISION_NONE
983 * COLLISION_w
984 * COLLISION_FOOD
985 * COLLISION_ARGH
986 * COLLISION_FIELD
987 */
988static int check_collision(struct worm *w)
989{
990 int retVal = COLLISION_NONE;
991
992 if (worm_collision(w, w->x[w->head], w->y[w->head]) != NULL)
993 retVal = COLLISION_WORM;
994
995 if (food_collision(w->x[w->head], w->y[w->head]) >= 0)
996 retVal = COLLISION_FOOD;
997
998 if (argh_collision(w->x[w->head], w->y[w->head]) >= 0)
999 retVal = COLLISION_ARGH;
1000
1001 if (field_collision(w))
1002 retVal = COLLISION_FIELD;
1003
1004 return retVal;
1005}
1006
1007/**
1008 * Returns the index of the food that is closest to the point
1009 * specified by x, y. This index may be used in the foodx and
1010 * foody arrays.
1011 * @param int x The x coordinate of the point
1012 * @param int y The y coordinate of the point
1013 * @return int A value usable as index in foodx and foody.
1014 */
1015static int get_nearest_food(int x, int y){
1016 int nearestfood = 0;
1017 int olddistance = FIELD_RECT_WIDTH + FIELD_RECT_HEIGHT;
1018 int deltax = 0;
1019 int deltay = 0;
1020 int foodindex;
1021 for (foodindex = 0; foodindex < MAX_FOOD; foodindex++) {
1022 int distance;
1023 deltax = foodx[foodindex] - x;
1024 deltay = foody[foodindex] - y;
1025 deltax = deltax > 0 ? deltax : deltax * (-1);
1026 deltay = deltay > 0 ? deltay : deltay * (-1);
1027 distance = deltax + deltay;
1028
1029 if (distance < olddistance) {
1030 olddistance = distance;
1031 nearestfood = foodindex;
1032 }
1033 }
1034 return nearestfood;
1035}
1036
1037/**
1038 * Returns wether the specified position is next to the worm
1039 * and in the direction the worm looks. Use this method to
1040 * test wether this position would be hit with the next move of
1041 * the worm unless the worm changes its direction.
1042 * @param struct worm *w - The worm to be investigated
1043 * @param int x - The x coordinate of the position to test.
1044 * @param int y - The y coordinate of the position to test.
1045 * @return Returns true if the worm will hit the position unless
1046 * it change its direction before the next move.
1047 */
1048static bool is_in_front_of_worm(struct worm *w, int x, int y) {
1049 bool infront = false;
1050 int deltax = x - w->x[w->head];
1051 int deltay = y - w->y[w->head];
1052
1053 if (w->dirx == 0) {
1054 infront = (w->diry * deltay) > 0;
1055 } else {
1056 infront = (w->dirx * deltax) > 0;
1057 }
1058 return infront;
1059}
1060
1061/**
1062 * Returns true if the worm will collide with the next move unless
1063 * it changes its direction.
1064 * @param struct worm *w - The worm to be investigated.
1065 * @return Returns true if the worm will collide with the next move
1066 * unless it changes its direction.
1067 */
1068static bool will_worm_collide(struct worm *w) {
1069 int x = w->x[w->head] + w->dirx;
1070 int y = w->y[w->head] + w->diry;
1071 bool retVal = !is_in_field_rect(x, y);
1072 if (!retVal) {
1073 retVal = (argh_collision(x, y) != -1);
1074 }
1075
1076 if (!retVal) {
1077 retVal = (worm_collision(w, x, y) != NULL);
1078 }
1079 return retVal;
1080}
1081
1082/**
1083 * This function
1084 * may be used to be stored in worm.fetch_worm_direction for
1085 * worms that are not controlled by humans but by artificial stupidity.
1086 * A direction is searched that doesn't lead to collision but to the nearest
1087 * food - but not very intelligent. The direction is written to the specified
1088 * worm.
1089 * @param struct worm *w - The worm of which the direction
1090 * is altered.
1091 */
1092static void virtual_player(struct worm *w) {
1093 bool isright;
1094 int plana, planb, planc;
1095 /* find the next lunch */
1096 int nearestfood = get_nearest_food(w->x[w->head], w->y[w->head]);
1097
1098 /* determine in which direction it is */
1099
1100 /* in front of me? */
1101 bool infront = is_in_front_of_worm(w, foodx[nearestfood], foody[nearestfood]);
1102
1103 /* left right of me? */
1104 int olddir = get_worm_dir(w);
1105 set_worm_dir(w, (olddir + 1) % 4);
1106 isright = is_in_front_of_worm(w, foodx[nearestfood], foody[nearestfood]);
1107 set_worm_dir(w, olddir);
1108
1109 /* detect situation, set strategy */
1110 if (infront) {
1111 if (isright) {
1112 plana = olddir;
1113 planb = (olddir + 1) % 4;
1114 planc = (olddir + 3) % 4;
1115 } else {
1116 plana = olddir;
1117 planb = (olddir + 3) % 4;
1118 planc = (olddir + 1) % 4;
1119 }
1120 } else {
1121 if (isright) {
1122 plana = (olddir + 1) % 4;
1123 planb = olddir;
1124 planc = (olddir + 3) % 4;
1125 } else {
1126 plana = (olddir + 3) % 4;
1127 planb = olddir;
1128 planc = (olddir + 1) % 4;
1129 }
1130 }
1131
1132 /* test for collision */
1133 set_worm_dir(w, plana);
1134 if (will_worm_collide(w)){
1135
1136 /* plan b */
1137 set_worm_dir(w, planb);
1138
1139 /* test for collision */
1140 if (will_worm_collide(w)) {
1141
1142 /* plan c */
1143 set_worm_dir(w, planc);
1144 }
1145 }
1146}
1147
1148/**
1149 * prints out the score board with all the status information
1150 * about the game.
1151 */
1152static void score_board(void)
1153{
1154 char buf[15];
1155 char* buf2 = NULL;
1156 int i;
1157 int y = 0;
1158 rb->lcd_clearrect(FIELD_RECT_WIDTH + 2, 0, LCD_WIDTH - FIELD_RECT_WIDTH - 2, LCD_HEIGHT);
1159 for (i = 0; i < worm_count; i++) {
1160 int score = get_score(&worms[i]);
1161
1162 /* high score */
1163 if (worms[i].fetch_worm_direction != virtual_player){
1164 if (highscore < score) {
1165 highscore = score;
1166 }
1167 }
1168
1169 /* length */
1170 rb->snprintf(buf, sizeof (buf),"Len:%d", score);
1171
1172 /* worm state */
1173 switch (check_collision(&worms[i])) {
1174 case COLLISION_NONE:
1175 if (worms[i].growing > 0)
1176 buf2 = "Growing";
1177 else {
1178 if (worms[i].alive)
1179 buf2 = "Hungry";
1180 else
1181 buf2 = "Wormed";
1182 }
1183 break;
1184
1185 case COLLISION_WORM:
1186 buf2 = "Wormed";
1187 break;
1188
1189 case COLLISION_FOOD:
1190 buf2 = "Growing";
1191 break;
1192
1193 case COLLISION_ARGH:
1194 buf2 = "Argh";
1195 break;
1196
1197 case COLLISION_FIELD:
1198 buf2 = "Crashed";
1199 break;
1200 }
1201 rb->lcd_putsxy(FIELD_RECT_WIDTH + 3, y , buf);
1202 rb->lcd_putsxy(FIELD_RECT_WIDTH + 3, y+8, buf2);
1203
1204 if (!worms[i].alive){
1205 rb->lcd_invertrect(FIELD_RECT_WIDTH + 2, y,
1206 LCD_WIDTH - FIELD_RECT_WIDTH - 2, 17);
1207 }
1208 y += 19;
1209 }
1210 rb->snprintf(buf , sizeof(buf), "Hs: %d", highscore);
1211#ifndef DEBUG_WORMLET
1212 rb->lcd_putsxy(FIELD_RECT_WIDTH + 3, LCD_HEIGHT - 8, buf);
1213#else
1214 rb->lcd_putsxy(FIELD_RECT_WIDTH + 3, LCD_HEIGHT - 8, debugout);
1215#endif
1216}
1217
1218/**
1219 * Checks for collisions of the worm and its environment and
1220 * takes appropriate actions like growing the worm or killing it.
1221 * @return bool Returns true if the worm is dead. Returns
1222 * false if the worm is healthy, up and creeping.
1223 */
1224static bool process_collisions(struct worm *w)
1225{
1226 int index = -1;
1227
1228 w->alive &= !field_collision(w);
1229
1230 if (w->alive) {
1231
1232 /* check if food was eaten */
1233 index = food_collision(w->x[w->head], w->y[w->head]);
1234 if (index != -1){
1235 int i;
1236
1237 clear_food(index);
1238 make_food(index);
1239 draw_food(index);
1240
1241 for (i = 0; i < ARGHS_PER_FOOD; i++) {
1242 argh_count++;
1243 if (argh_count > MAX_ARGH)
1244 argh_count = MAX_ARGH;
1245 make_argh(argh_count - 1);
1246 draw_argh(argh_count - 1);
1247 }
1248
1249 add_growing(w, WORM_PER_FOOD);
1250
1251 draw_worm(w);
1252 }
1253
1254 /* check if argh was eaten */
1255 else {
1256 index = argh_collision(w->x[w->head], w->y[w->head]);
1257 if (index != -1) {
1258 w->alive = false;
1259 }
1260 else {
1261 if (worm_collision(w, w->x[w->head], w->y[w->head]) != NULL) {
1262 w->alive = false;
1263 }
1264 }
1265 }
1266 }
1267 return !w->alive;
1268}
1269
1270/**
1271 * The main loop of the game.
1272 * @return bool Returns true if the game ended
1273 * with a dead worm. Returns false if the user
1274 * aborted the game manually.
1275 */
1276static bool run(void)
1277{
1278 int button = 0;
1279 int wormDead = false;
1280
1281 /* ticks are counted to compensate speed variations */
1282 long cycle_start = 0, cycle_end = 0;
1283#ifdef DEBUG_WORMLET
1284 int ticks_to_max_cycle_reset = 20;
1285 long max_cycle = 0;
1286 char buf[20];
1287#endif
1288
1289 /* initialize the board and so on */
1290 init_wormlet();
1291
1292 cycle_start = *rb->current_tick;
1293 /* change the direction of the worm */
1294 while (button != BUTTON_OFF && ! wormDead)
1295 {
1296 int i;
1297 long cycle_duration ;
1298 switch (button) {
1299 case BUTTON_UP:
1300 if (players == 1 && !use_remote) {
1301 player1_dir = NORTH;
1302 }
1303 break;
1304
1305 case BUTTON_DOWN:
1306 if (players == 1 && !use_remote) {
1307 player1_dir = SOUTH;
1308 }
1309 break;
1310
1311 case BUTTON_LEFT:
1312 if (players != 1 || use_remote) {
1313 player1_dir = (player1_dir + 3) % 4;
1314 } else {
1315 player1_dir = WEST;
1316 }
1317 break;
1318
1319 case BUTTON_RIGHT:
1320 if (players != 1 || use_remote) {
1321 player1_dir = (player1_dir + 1) % 4;
1322 } else {
1323 player1_dir = EAST;
1324 }
1325 break;
1326
1327 case BUTTON_F2:
1328 player2_dir = (player2_dir + 3) % 4;
1329 break;
1330
1331 case BUTTON_F3:
1332 player2_dir = (player2_dir + 1) % 4;
1333 break;
1334
1335 case BUTTON_RC_VOL_UP:
1336 player3_dir = (player3_dir + 1) % 4;
1337 break;
1338
1339 case BUTTON_RC_VOL_DOWN:
1340 player3_dir = (player3_dir + 3) % 4;
1341 break;
1342
1343 case BUTTON_PLAY:
1344 do {
1345 button = rb->button_get(true);
1346 } while (button != BUTTON_PLAY &&
1347 button != BUTTON_OFF &&
1348 button != BUTTON_ON);
1349 break;
1350 }
1351
1352 for (i = 0; i < worm_count; i++) {
1353 worms[i].fetch_worm_direction(&worms[i]);
1354 }
1355
1356 wormDead = true;
1357 for (i = 0; i < worm_count; i++){
1358 struct worm *w = &worms[i];
1359 move_worm(w);
1360 wormDead &= process_collisions(w);
1361 draw_worm(w);
1362 }
1363 score_board();
1364 rb->lcd_update();
1365 if (button == BUTTON_ON) {
1366 wormDead = true;
1367 }
1368
1369 /* here the wormlet game cycle ends
1370 thus the current tick is stored
1371 as end time */
1372 cycle_end = *rb->current_tick;
1373
1374 /* The duration of the game cycle */
1375 cycle_duration = cycle_end - cycle_start;
1376 cycle_duration = MAX(0, cycle_duration);
1377 cycle_duration = MIN(SPEED -1, cycle_duration);
1378
1379
1380#ifdef DEBUG_WORMLET
1381 ticks_to_max_cycle_reset--;
1382 if (ticks_to_max_cycle_reset <= 0) {
1383 max_cycle = 0;
1384 }
1385
1386 if (max_cycle < cycle_duration) {
1387 max_cycle = cycle_duration;
1388 ticks_to_max_cycle_reset = 20;
1389 }
1390 rb->snprintf(buf, sizeof buf, "ticks %d", max_cycle);
1391 set_debug_out(buf);
1392#endif
1393 /* adjust the number of ticks to wait for a button.
1394 This ensures that a complete game cycle including
1395 user input runs in constant time */
1396 button = rb->button_get_w_tmo(SPEED - cycle_duration);
1397 cycle_start = *rb->current_tick;
1398 }
1399 return wormDead;
1400}
1401
1402#ifdef DEBUG_WORMLET
1403
1404/**
1405 * Just a test routine that checks that worm_food_collision works
1406 * in some typical situations.
1407 */
1408static void test_worm_food_collision(void) {
1409 int collision_count = 0;
1410 int i;
1411 rb->lcd_clear_display();
1412 init_worm(&worms[0], 10, 10);
1413 add_growing(&worms[0], 10);
1414 set_worm_dir(&worms[0], EAST);
1415 for (i = 0; i < 10; i++) {
1416 move_worm(&worms[0]);
1417 draw_worm(&worms[0]);
1418 }
1419
1420 set_worm_dir(&worms[0], SOUTH);
1421 for (i = 0; i < 10; i++) {
1422 move_worm(&worms[0]);
1423 draw_worm(&worms[0]);
1424 }
1425
1426 foodx[0] = 15;
1427 foody[0] = 12;
1428 for (foody[0] = 20; foody[0] > 0; foody[0] --) {
1429 char buf[20];
1430 bool collision;
1431 draw_worm(&worms[0]);
1432 draw_food(0);
1433 collision = worm_food_collision(&worms[0], 0);
1434 if (collision) {
1435 collision_count++;
1436 }
1437 rb->snprintf(buf, sizeof buf, "collisions: %d", collision_count);
1438 rb->lcd_putsxy(0, LCD_HEIGHT -8, buf);
1439 rb->lcd_update();
1440 }
1441 if (collision_count != FOOD_SIZE) {
1442 rb->button_get(true);
1443 }
1444
1445
1446 foody[0] = 15;
1447 for (foodx[0] = 30; foodx[0] > 0; foodx[0] --) {
1448 char buf[20];
1449 bool collision;
1450 draw_worm(&worms[0]);
1451 draw_food(0);
1452 collision = worm_food_collision(&worms[0], 0);
1453 if (collision) {
1454 collision_count ++;
1455 }
1456 rb->snprintf(buf, sizeof buf, "collisions: %d", collision_count);
1457 rb->lcd_putsxy(0, LCD_HEIGHT -8, buf);
1458 rb->lcd_update();
1459 }
1460 if (collision_count != FOOD_SIZE * 2) {
1461 rb->button_get(true);
1462 }
1463
1464}
1465
1466
1467static bool expensive_worm_in_rect(struct worm *w, int rx, int ry, int rw, int rh){
1468 int x, y;
1469 bool retVal = false;
1470 for (x = rx; x < rx + rw; x++){
1471 for (y = ry; y < ry + rh; y++) {
1472 if (specific_worm_collision(w, x, y) != -1) {
1473 retVal = true;
1474 }
1475 }
1476 }
1477 return retVal;
1478}
1479
1480static void test_worm_argh_collision(void) {
1481 int i;
1482 int dir;
1483 int collision_count = 0;
1484 rb->lcd_clear_display();
1485 init_worm(&worms[0], 10, 10);
1486 add_growing(&worms[0], 40);
1487 for (dir = 0; dir < 4; dir++) {
1488 set_worm_dir(&worms[0], (EAST + dir) % 4);
1489 for (i = 0; i < 10; i++) {
1490 move_worm(&worms[0]);
1491 draw_worm(&worms[0]);
1492 }
1493 }
1494
1495 arghx[0] = 12;
1496 for (arghy[0] = 0; arghy[0] < FIELD_RECT_HEIGHT - ARGH_SIZE; arghy[0]++){
1497 char buf[20];
1498 bool collision;
1499 draw_argh(0);
1500 collision = worm_argh_collision(&worms[0], 0);
1501 if (collision) {
1502 collision_count ++;
1503 }
1504 rb->snprintf(buf, sizeof buf, "collisions: %d", collision_count);
1505 rb->lcd_putsxy(0, LCD_HEIGHT -8, buf);
1506 rb->lcd_update();
1507 }
1508 if (collision_count != ARGH_SIZE * 2) {
1509 rb->button_get(true);
1510 }
1511
1512 arghy[0] = 12;
1513 for (arghx[0] = 0; arghx[0] < FIELD_RECT_HEIGHT - ARGH_SIZE; arghx[0]++){
1514 char buf[20];
1515 bool collision;
1516 draw_argh(0);
1517 collision = worm_argh_collision(&worms[0], 0);
1518 if (collision) {
1519 collision_count ++;
1520 }
1521 rb->snprintf(buf, sizeof buf, "collisions: %d", collision_count);
1522 rb->lcd_putsxy(0, LCD_HEIGHT -8, buf);
1523 rb->lcd_update();
1524 }
1525 if (collision_count != ARGH_SIZE * 4) {
1526 rb->button_get(true);
1527 }
1528}
1529
1530static int testline_in_rect(void) {
1531 int testfailed = -1;
1532
1533 int rx = 10;
1534 int ry = 15;
1535 int rw = 20;
1536 int rh = 25;
1537
1538 /* Test 1 */
1539 int x1 = 12;
1540 int y1 = 8;
1541 int x2 = 12;
1542 int y2 = 42;
1543
1544 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1545 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1546 rb->lcd_drawrect(rx, ry, rw, rh);
1547 rb->lcd_drawline(x1, y1, x2, y2);
1548 rb->lcd_update();
1549 rb->lcd_putsxy(0, 0, "failed 1");
1550 rb->button_get(true);
1551 testfailed = 1;
1552 }
1553
1554 /* test 2 */
1555 y2 = 20;
1556 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1557 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1558 rb->lcd_drawrect(rx, ry, rw, rh);
1559 rb->lcd_drawline(x1, y1, x2, y2);
1560 rb->lcd_putsxy(0, 0, "failed 2");
1561 rb->lcd_update();
1562 rb->button_get(true);
1563 testfailed = 2;
1564 }
1565
1566 /* test 3 */
1567 y1 = 30;
1568 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1569 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1570 rb->lcd_drawrect(rx, ry, rw, rh);
1571 rb->lcd_drawline(x1, y1, x2, y2);
1572 rb->lcd_putsxy(0, 0, "failed 3");
1573 rb->lcd_update();
1574 rb->button_get(true);
1575 testfailed = 3;
1576 }
1577
1578 /* test 4 */
1579 y2 = 45;
1580 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1581 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1582 rb->lcd_drawrect(rx, ry, rw, rh);
1583 rb->lcd_drawline(x1, y1, x2, y2);
1584 rb->lcd_putsxy(0, 0, "failed 4");
1585 rb->lcd_update();
1586 rb->button_get(true);
1587 testfailed = 4;
1588 }
1589
1590 /* test 5 */
1591 y1 = 50;
1592 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
1593 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1594 rb->lcd_drawrect(rx, ry, rw, rh);
1595 rb->lcd_drawline(x1, y1, x2, y2);
1596 rb->lcd_putsxy(0, 0, "failed 5");
1597 rb->lcd_update();
1598 rb->button_get(true);
1599 testfailed = 5;
1600 }
1601
1602 /* test 6 */
1603 y1 = 5;
1604 y2 = 7;
1605 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
1606 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1607 rb->lcd_drawrect(rx, ry, rw, rh);
1608 rb->lcd_drawline(x1, y1, x2, y2);
1609 rb->lcd_putsxy(0, 0, "failed 6");
1610 rb->lcd_update();
1611 rb->button_get(true);
1612 testfailed = 6;
1613 }
1614
1615 /* test 7 */
1616 x1 = 8;
1617 y1 = 20;
1618 x2 = 35;
1619 y2 = 20;
1620 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1621 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1622 rb->lcd_drawrect(rx, ry, rw, rh);
1623 rb->lcd_drawline(x1, y1, x2, y2);
1624 rb->lcd_putsxy(0, 0, "failed 7");
1625 rb->lcd_update();
1626 rb->button_get(true);
1627 testfailed = 7;
1628 }
1629
1630 /* test 8 */
1631 x2 = 12;
1632 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1633 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1634 rb->lcd_drawrect(rx, ry, rw, rh);
1635 rb->lcd_drawline(x1, y1, x2, y2);
1636 rb->lcd_putsxy(0, 0, "failed 8");
1637 rb->lcd_update();
1638 rb->button_get(true);
1639 testfailed = 8;
1640 }
1641
1642 /* test 9 */
1643 x1 = 25;
1644 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1645 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1646 rb->lcd_drawrect(rx, ry, rw, rh);
1647 rb->lcd_drawline(x1, y1, x2, y2);
1648 rb->lcd_putsxy(0, 0, "failed 9");
1649 rb->lcd_update();
1650 rb->button_get(true);
1651 testfailed = 9;
1652 }
1653
1654 /* test 10 */
1655 x2 = 37;
1656 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1657 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1658 rb->lcd_drawrect(rx, ry, rw, rh);
1659 rb->lcd_drawline(x1, y1, x2, y2);
1660 rb->lcd_putsxy(0, 0, "failed 10");
1661 rb->lcd_update();
1662 rb->button_get(true);
1663 testfailed = 10;
1664 }
1665
1666 /* test 11 */
1667 x1 = 42;
1668 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
1669 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1670 rb->lcd_drawrect(rx, ry, rw, rh);
1671 rb->lcd_drawline(x1, y1, x2, y2);
1672 rb->lcd_putsxy(0, 0, "failed 11");
1673 rb->lcd_update();
1674 rb->button_get(true);
1675 testfailed = 11;
1676 }
1677
1678 /* test 12 */
1679 x1 = 5;
1680 x2 = 7;
1681 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
1682 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1683 rb->lcd_drawrect(rx, ry, rw, rh);
1684 rb->lcd_drawline(x1, y1, x2, y2);
1685 rb->lcd_putsxy(0, 0, "failed 12");
1686 rb->lcd_update();
1687 rb->button_get(true);
1688 testfailed = 12;
1689 }
1690
1691 /* test 13 */
1692 rx = 9;
1693 ry = 15;
1694 rw = FOOD_SIZE;
1695 rh = FOOD_SIZE;
1696
1697 x1 = 10;
1698 y1 = 10;
1699 x2 = 10;
1700 y2 = 20;
1701 if (!(line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1702 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh))) {
1703 rb->lcd_drawrect(rx, ry, rw, rh);
1704 rb->lcd_drawline(x1, y1, x2, y2);
1705 rb->lcd_putsxy(0, 0, "failed 13");
1706 rb->lcd_update();
1707 rb->button_get(true);
1708 testfailed = 13;
1709 }
1710
1711 /* test 14 */
1712 rx = 9;
1713 ry = 15;
1714 rw = 4;
1715 rh = 4;
1716
1717 x1 = 10;
1718 y1 = 10;
1719 x2 = 10;
1720 y2 = 19;
1721 if (!(line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1722 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh))) {
1723 rb->lcd_drawline(x1, y1, x2, y2);
1724 rb->lcd_invertrect(rx, ry, rw, rh);
1725 rb->lcd_putsxy(0, 0, "failed 14");
1726 rb->lcd_update();
1727 rb->button_get(true);
1728 testfailed = 14;
1729 }
1730
1731 rb->lcd_clear_display();
1732
1733 return testfailed;
1734}
1735
1736/**
1737 * Just a test routine to test wether specific_worm_collision might work properly
1738 */
1739static int test_specific_worm_collision(void) {
1740 int collisions = 0;
1741 int dir;
1742 int x = 0;
1743 int y = 0;
1744 char buf[20];
1745 rb->lcd_clear_display();
1746 init_worm(&worms[0], 10, 20);
1747 add_growing(&worms[0], 20 - INITIAL_WORM_LENGTH);
1748
1749 for (dir = EAST; dir < EAST + 4; dir++) {
1750 int i;
1751 set_worm_dir(&worms[0], dir % 4);
1752 for (i = 0; i < 5; i++) {
1753 if (!(dir % 4 == NORTH && i == 9)) {
1754 move_worm(&worms[0]);
1755 draw_worm(&worms[0]);
1756 }
1757 }
1758 }
1759
1760 for (y = 15; y < 30; y ++){
1761 for (x = 5; x < 20; x++) {
1762 if (specific_worm_collision(&worms[0], x, y) != -1) {
1763 collisions ++;
1764 }
1765 rb->lcd_invertpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y);
1766 rb->snprintf(buf, sizeof buf, "collisions %d", collisions);
1767 rb->lcd_putsxy(0, LCD_HEIGHT - 8, buf);
1768 rb->lcd_update();
1769 }
1770 }
1771 if (collisions != 21) {
1772 rb->button_get(true);
1773 }
1774 return collisions;
1775}
1776
1777static void test_make_argh(void){
1778 int dir;
1779 int seed = 0;
1780 int hit = 0;
1781 int failures = 0;
1782 int last_failures = 0;
1783 int i, worm_idx;
1784 rb->lcd_clear_display();
1785 worm_count = 3;
1786
1787 for (worm_idx = 0; worm_idx < worm_count; worm_idx++) {
1788 init_worm(&worms[worm_idx], 10 + worm_idx * 20, 20);
1789 add_growing(&worms[worm_idx], 40 - INITIAL_WORM_LENGTH);
1790 }
1791
1792 for (dir = EAST; dir < EAST + 4; dir++) {
1793 for (worm_idx = 0; worm_idx < worm_count; worm_idx++) {
1794 set_worm_dir(&worms[worm_idx], dir % 4);
1795 for (i = 0; i < 10; i++) {
1796 if (!(dir % 4 == NORTH && i == 9)) {
1797 move_worm(&worms[worm_idx]);
1798 draw_worm(&worms[worm_idx]);
1799 }
1800 }
1801 }
1802 }
1803
1804 rb->lcd_update();
1805
1806 for (seed = 0; hit < 20; seed += 2) {
1807 char buf[20];
1808 int x, y;
1809 rb->srand(seed);
1810 x = rb->rand() % (FIELD_RECT_WIDTH - ARGH_SIZE);
1811 y = rb->rand() % (FIELD_RECT_HEIGHT - ARGH_SIZE);
1812
1813 for (worm_idx = 0; worm_idx < worm_count; worm_idx++){
1814 if (expensive_worm_in_rect(&worms[worm_idx], x, y, ARGH_SIZE, ARGH_SIZE)) {
1815 int tries = 0;
1816 rb->srand(seed);
1817
1818 tries = make_argh(0);
1819 if ((x == arghx[0] && y == arghy[0]) || tries < 2) {
1820 failures ++;
1821 }
1822
1823 rb->snprintf(buf, sizeof buf, "(%d;%d) fail%d try%d", x, y, failures, tries);
1824 rb->lcd_putsxy(0, LCD_HEIGHT - 8, buf);
1825 rb->lcd_update();
1826 rb->lcd_invertrect(x + FIELD_RECT_X, y+ FIELD_RECT_Y, ARGH_SIZE, ARGH_SIZE);
1827 rb->lcd_update();
1828 draw_argh(0);
1829 rb->lcd_update();
1830 rb->lcd_invertrect(x + FIELD_RECT_X, y + FIELD_RECT_Y, ARGH_SIZE, ARGH_SIZE);
1831 rb->lcd_clearrect(arghx[0] + FIELD_RECT_X, arghy[0] + FIELD_RECT_Y, ARGH_SIZE, ARGH_SIZE);
1832
1833 if (failures > last_failures) {
1834 rb->button_get(true);
1835 }
1836 last_failures = failures;
1837 hit ++;
1838 }
1839 }
1840 }
1841}
1842
1843static void test_worm_argh_collision_in_moves(void) {
1844 int hit_count = 0;
1845 int i;
1846 rb->lcd_clear_display();
1847 init_worm(&worms[0], 10, 20);
1848
1849 arghx[0] = 20;
1850 arghy[0] = 18;
1851 draw_argh(0);
1852
1853 set_worm_dir(&worms[0], EAST);
1854 for (i = 0; i < 20; i++) {
1855 char buf[20];
1856 move_worm(&worms[0]);
1857 draw_worm(&worms[0]);
1858 if (worm_argh_collision_in_moves(&worms[0], 0, 5)){
1859 hit_count ++;
1860 }
1861 rb->snprintf(buf, sizeof buf, "in 5 moves hits: %d", hit_count);
1862 rb->lcd_putsxy(0, LCD_HEIGHT - 8, buf);
1863 rb->lcd_update();
1864 }
1865 if (hit_count != ARGH_SIZE + 5) {
1866 rb->button_get(true);
1867 }
1868}
1869#endif /* DEBUG_WORMLET */
1870
1871extern bool use_old_rect;
1872
1873/**
1874 * Main entry point
1875 */
1876enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
1877{
1878 bool worm_dead = false;
1879 int button;
1880
1881 TEST_PLUGIN_API(api);
1882 (void)(parameter);
1883
1884 rb = api;
1885 rb->lcd_setfont(FONT_SYSFIXED);
1886
1887#ifdef DEBUG_WORMLET
1888 testline_in_rect();
1889 test_worm_argh_collision_in_moves();
1890 test_make_argh();
1891 test_worm_food_collision();
1892 test_worm_argh_collision();
1893 test_specific_worm_collision();
1894#endif
1895
1896 /* Setup screen */
1897 do {
1898 char buf[20];
1899 char* ptr;
1900 rb->lcd_clear_display();
1901
1902 /* first line players */
1903 rb->snprintf(buf, sizeof buf, "%d Players UP/DN", players);
1904 rb->lcd_puts(0, 0, buf);
1905
1906 /* second line worms */
1907 rb->snprintf(buf, sizeof buf, "%d Worms L/R", worm_count);
1908 rb->lcd_puts(0, 1, buf);
1909
1910 /* third line control */
1911 if (players > 1) {
1912 if (use_remote) {
1913 ptr = "Remote Control F1";
1914 } else {
1915 ptr = "No Rem. Control F1";
1916 }
1917 } else {
1918 if (players > 0) {
1919 if (use_remote) {
1920 ptr = "2 Key Control F1";
1921 } else {
1922 ptr = "4 Key Control F1";
1923 }
1924 } else {
1925 ptr = "Out Of Control";
1926 }
1927 }
1928 rb->lcd_puts(0, 2, ptr);
1929 rb->lcd_update();
1930
1931 /* user selection */
1932 button = rb->button_get(true);
1933 switch (button) {
1934 case BUTTON_UP:
1935 if (players < 3) {
1936 players ++;
1937 if (players > worm_count) {
1938 worm_count = players;
1939 }
1940 if (players > 2) {
1941 use_remote = true;
1942 }
1943 }
1944 break;
1945 case BUTTON_DOWN:
1946 if (players > 0) {
1947 players --;
1948 }
1949 break;
1950 case BUTTON_LEFT:
1951 if (worm_count > 1) {
1952 worm_count--;
1953 if (worm_count < players) {
1954 players = worm_count;
1955 }
1956 }
1957 break;
1958 case BUTTON_RIGHT:
1959 if (worm_count < MAX_WORMS) {
1960 worm_count ++;
1961 }
1962 break;
1963 case BUTTON_F1:
1964 use_remote = !use_remote;
1965 if (players > 2) {
1966 use_remote = true;
1967 }
1968 break;
1969
1970 case SYS_USB_CONNECTED:
1971 rb->usb_screen();
1972 return PLUGIN_USB_CONNECTED;
1973 }
1974 } while (button != BUTTON_PLAY &&
1975 button != BUTTON_OFF && button != BUTTON_ON);
1976
1977 rb->lcd_clear_display();
1978 /* end of setup */
1979
1980 do {
1981
1982 /* button state will be overridden if
1983 the game quits with the death of the worm.
1984 Initializing button to BUTTON_OFF ensures
1985 that the user can hit BUTTON_OFF during the
1986 game to return to the menu.
1987 */
1988 button = BUTTON_OFF;
1989
1990 /* start the game */
1991 worm_dead = run();
1992
1993 /* if worm isn't dead the game was quit
1994 via BUTTON_OFF -> no need to wait for buttons. */
1995 if (worm_dead) {
1996 do {
1997 button = rb->button_get(true);
1998 }
1999 /* BUTTON_ON -> start new game */
2000 /* BUTTON_OFF -> back to game menu */
2001 while (button != BUTTON_OFF && button != BUTTON_ON);
2002 }
2003 }
2004 while (button != BUTTON_OFF);
2005
2006 return PLUGIN_OK;
2007}
2008
2009#endif