Explicitely say 'minutes' when speaking the battery time, fixes FS#11932.
[kugel-rb.git] / apps / plugins / wormlet.c
blobba5eb2496747605ad02fcda3abef5f049df2a14a
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 Philipp Pertermann
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
21 #include "plugin.h"
22 #include "lib/configfile.h"
23 #include "lib/helper.h"
24 #include "lib/playback_control.h"
26 #ifdef DEBUG_WORMLET
27 static long max_cycle;
28 #endif
30 /* size of the field the worm lives in */
31 #define FIELD_RECT_X 1
32 #define FIELD_RECT_Y 1
33 #define FIELD_RECT_WIDTH (LCD_WIDTH - 45)
34 #define FIELD_RECT_HEIGHT (LCD_HEIGHT - 2)
36 /* when the game starts */
37 #define INITIAL_WORM_LENGTH 10
39 /* num of pixel the worm grows per eaten food */
40 #define WORM_PER_FOOD 7
42 /* num of worms creeping in the FIELD */
43 #define MAX_WORMS 3
45 /* minimal distance between a worm and an argh
46 when a new argh is made */
47 #define MIN_ARGH_DIST 5
49 #if (CONFIG_KEYPAD == RECORDER_PAD)
50 #define BTN_DIR_UP BUTTON_UP
51 #define BTN_DIR_DOWN BUTTON_DOWN
52 #define BTN_DIR_LEFT BUTTON_LEFT
53 #define BTN_DIR_RIGHT BUTTON_RIGHT
54 #define BTN_PLAYER2_DIR1 BUTTON_F2
55 #define BTN_PLAYER2_DIR2 BUTTON_F3
56 #define BTN_STARTPAUSE BUTTON_PLAY
57 #define BTN_QUIT BUTTON_OFF
58 #define BTN_STOPRESET BUTTON_ON
59 #define BTN_TOGGLE_KEYS BUTTON_F1
61 #if BUTTON_REMOTE != 0
62 #define BTN_RC_UP BUTTON_RC_VOL_UP
63 #define BTN_RC_DOWN BUTTON_RC_VOL_DOWN
64 #define REMOTE
65 #define MULTIPLAYER
66 #endif
68 #elif (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
69 #define BTN_DIR_UP BUTTON_UP
70 #define BTN_DIR_DOWN BUTTON_DOWN
71 #define BTN_DIR_LEFT BUTTON_LEFT
72 #define BTN_DIR_RIGHT BUTTON_RIGHT
73 #define BTN_PLAYER2_DIR1 BUTTON_F2
74 #define BTN_PLAYER2_DIR2 BUTTON_F3
75 #define BTN_STARTPAUSE BUTTON_SELECT
76 #define BTN_QUIT BUTTON_OFF
77 #define BTN_STOPRESET BUTTON_ON
78 #define BTN_TOGGLE_KEYS BUTTON_F1
80 #elif (CONFIG_KEYPAD == ONDIO_PAD)
81 #define BTN_DIR_UP BUTTON_UP
82 #define BTN_DIR_DOWN BUTTON_DOWN
83 #define BTN_DIR_LEFT BUTTON_LEFT
84 #define BTN_DIR_RIGHT BUTTON_RIGHT
85 #define BTN_STARTPAUSE (BUTTON_MENU|BUTTON_REL)
86 #define BTN_QUIT (BUTTON_OFF|BUTTON_REL)
87 #define BTN_STOPRESET (BUTTON_OFF|BUTTON_MENU)
89 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \
90 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
92 #define BTN_DIR_UP BUTTON_MENU
93 #define BTN_DIR_DOWN BUTTON_PLAY
94 #define BTN_DIR_LEFT BUTTON_LEFT
95 #define BTN_DIR_RIGHT BUTTON_RIGHT
96 #define BTN_STARTPAUSE (BUTTON_SELECT|BUTTON_REL)
97 #define BTN_QUIT (BUTTON_SELECT|BUTTON_MENU)
98 #define BTN_STOPRESET (BUTTON_SELECT|BUTTON_PLAY)
100 #elif (CONFIG_KEYPAD == IRIVER_H300_PAD) || (CONFIG_KEYPAD == IRIVER_H100_PAD)
102 #define BTN_DIR_UP BUTTON_UP
103 #define BTN_DIR_DOWN BUTTON_DOWN
104 #define BTN_DIR_LEFT BUTTON_LEFT
105 #define BTN_DIR_RIGHT BUTTON_RIGHT
106 #define BTN_STARTPAUSE (BUTTON_SELECT|BUTTON_REL)
107 #define BTN_QUIT BUTTON_OFF
108 #define BTN_STOPRESET BUTTON_ON
110 #define BTN_RC_QUIT BUTTON_RC_STOP
112 #elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD)
114 #define BTN_DIR_UP BUTTON_UP
115 #define BTN_DIR_DOWN BUTTON_DOWN
116 #define BTN_DIR_LEFT BUTTON_LEFT
117 #define BTN_DIR_RIGHT BUTTON_RIGHT
118 #define BTN_STARTPAUSE BUTTON_PLAY
119 #define BTN_QUIT BUTTON_POWER
120 #define BTN_STOPRESET BUTTON_REC
122 #elif (CONFIG_KEYPAD == GIGABEAT_PAD)
124 #define BTN_DIR_UP BUTTON_UP
125 #define BTN_DIR_DOWN BUTTON_DOWN
126 #define BTN_DIR_LEFT BUTTON_LEFT
127 #define BTN_DIR_RIGHT BUTTON_RIGHT
128 #define BTN_STARTPAUSE BUTTON_SELECT
129 #define BTN_QUIT BUTTON_POWER
130 #define BTN_STOPRESET BUTTON_A
132 #elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \
133 (CONFIG_KEYPAD == SANSA_C200_PAD)
135 #define BTN_DIR_UP BUTTON_UP
136 #define BTN_DIR_DOWN BUTTON_DOWN
137 #define BTN_DIR_LEFT BUTTON_LEFT
138 #define BTN_DIR_RIGHT BUTTON_RIGHT
139 #define BTN_STARTPAUSE BUTTON_SELECT
140 #define BTN_QUIT BUTTON_POWER
141 #define BTN_STOPRESET BUTTON_REC
143 #elif (CONFIG_KEYPAD == SANSA_CLIP_PAD)
145 #define BTN_DIR_UP BUTTON_UP
146 #define BTN_DIR_DOWN BUTTON_DOWN
147 #define BTN_DIR_LEFT BUTTON_LEFT
148 #define BTN_DIR_RIGHT BUTTON_RIGHT
149 #define BTN_STARTPAUSE BUTTON_SELECT
150 #define BTN_QUIT BUTTON_POWER
151 #define BTN_STOPRESET BUTTON_HOME
153 #elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
155 #define BTN_DIR_UP BUTTON_UP
156 #define BTN_DIR_DOWN BUTTON_DOWN
157 #define BTN_DIR_LEFT BUTTON_LEFT
158 #define BTN_DIR_RIGHT BUTTON_RIGHT
159 #define BTN_STARTPAUSE BUTTON_SELECT
160 #define BTN_QUIT (BUTTON_HOME|BUTTON_REPEAT)
161 #define BTN_STOPRESET (BUTTON_SELECT | BUTTON_UP)
163 #elif (CONFIG_KEYPAD == SANSA_M200_PAD)
165 #define BTN_DIR_UP BUTTON_UP
166 #define BTN_DIR_DOWN BUTTON_DOWN
167 #define BTN_DIR_LEFT BUTTON_LEFT
168 #define BTN_DIR_RIGHT BUTTON_RIGHT
169 #define BTN_STARTPAUSE (BUTTON_SELECT | BUTTON_REL)
170 #define BTN_QUIT BUTTON_POWER
171 #define BTN_STOPRESET (BUTTON_SELECT | BUTTON_UP)
173 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
175 #define BTN_DIR_UP BUTTON_SCROLL_UP
176 #define BTN_DIR_DOWN BUTTON_SCROLL_DOWN
177 #define BTN_DIR_LEFT BUTTON_LEFT
178 #define BTN_DIR_RIGHT BUTTON_RIGHT
179 #define BTN_STARTPAUSE BUTTON_PLAY
180 #define BTN_QUIT BUTTON_POWER
181 #define BTN_STOPRESET BUTTON_REW
183 #elif (CONFIG_KEYPAD == GIGABEAT_S_PAD)
185 #define BTN_DIR_UP BUTTON_UP
186 #define BTN_DIR_DOWN BUTTON_DOWN
187 #define BTN_DIR_LEFT BUTTON_LEFT
188 #define BTN_DIR_RIGHT BUTTON_RIGHT
189 #define BTN_STARTPAUSE BUTTON_SELECT
190 #define BTN_QUIT BUTTON_BACK
191 #define BTN_STOPRESET BUTTON_MENU
193 #elif (CONFIG_KEYPAD == MROBE100_PAD)
195 #define BTN_DIR_UP BUTTON_UP
196 #define BTN_DIR_DOWN BUTTON_DOWN
197 #define BTN_DIR_LEFT BUTTON_LEFT
198 #define BTN_DIR_RIGHT BUTTON_RIGHT
199 #define BTN_STARTPAUSE BUTTON_SELECT
200 #define BTN_QUIT BUTTON_POWER
201 #define BTN_STOPRESET BUTTON_DISPLAY
203 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
205 #define BTN_DIR_UP BUTTON_RC_VOL_UP
206 #define BTN_DIR_DOWN BUTTON_RC_VOL_DOWN
207 #define BTN_DIR_LEFT BUTTON_RC_REW
208 #define BTN_DIR_RIGHT BUTTON_RC_FF
209 #define BTN_STARTPAUSE BUTTON_RC_PLAY
210 #define BTN_QUIT BUTTON_RC_REC
211 #define BTN_STOPRESET BUTTON_RC_MODE
213 #elif (CONFIG_KEYPAD == COWON_D2_PAD)
215 #define BTN_QUIT BUTTON_POWER
217 #elif CONFIG_KEYPAD == CREATIVEZVM_PAD
219 #define BTN_DIR_UP BUTTON_UP
220 #define BTN_DIR_DOWN BUTTON_DOWN
221 #define BTN_DIR_LEFT BUTTON_LEFT
222 #define BTN_DIR_RIGHT BUTTON_RIGHT
223 #define BTN_STARTPAUSE BUTTON_PLAY
224 #define BTN_QUIT BUTTON_BACK
225 #define BTN_STOPRESET BUTTON_MENU
227 #elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
229 #define BTN_DIR_UP BUTTON_UP
230 #define BTN_DIR_DOWN BUTTON_DOWN
231 #define BTN_DIR_LEFT BUTTON_LEFT
232 #define BTN_DIR_RIGHT BUTTON_RIGHT
233 #define BTN_STARTPAUSE BUTTON_MENU
234 #define BTN_QUIT BUTTON_POWER
235 #define BTN_STOPRESET BUTTON_VIEW
237 #elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD
239 #define BTN_DIR_UP BUTTON_UP
240 #define BTN_DIR_DOWN BUTTON_DOWN
241 #define BTN_DIR_LEFT BUTTON_PREV
242 #define BTN_DIR_RIGHT BUTTON_NEXT
243 #define BTN_STARTPAUSE BUTTON_MENU
244 #define BTN_QUIT BUTTON_POWER
245 #define BTN_STOPRESET BUTTON_RIGHT
247 #elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
249 #define BTN_DIR_UP BUTTON_UP
250 #define BTN_DIR_DOWN BUTTON_DOWN
251 #define BTN_DIR_LEFT BUTTON_PREV
252 #define BTN_DIR_RIGHT BUTTON_RIGHT
253 #define BTN_STARTPAUSE BUTTON_MENU
254 #define BTN_QUIT BUTTON_POWER
255 #define BTN_STOPRESET BUTTON_RIGHT
257 #elif (CONFIG_KEYPAD == ONDAVX747_PAD) || \
258 (CONFIG_KEYPAD == ONDAVX777_PAD) || \
259 CONFIG_KEYPAD == MROBE500_PAD
261 #define BTN_QUIT BUTTON_POWER
263 #elif CONFIG_KEYPAD == SAMSUNG_YH_PAD
265 #define BTN_DIR_UP BUTTON_UP
266 #define BTN_DIR_DOWN BUTTON_DOWN
267 #define BTN_DIR_LEFT BUTTON_LEFT
268 #define BTN_DIR_RIGHT BUTTON_RIGHT
269 #define BTN_STARTPAUSE BUTTON_PLAY
270 #define BTN_QUIT BUTTON_FFWD
271 #define BTN_STOPRESET BUTTON_REW
273 #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
275 #define BTN_DIR_UP BUTTON_UP
276 #define BTN_DIR_DOWN BUTTON_DOWN
277 #define BTN_DIR_LEFT BUTTON_PREV
278 #define BTN_DIR_RIGHT BUTTON_NEXT
279 #define BTN_STARTPAUSE BUTTON_PLAY
280 #define BTN_QUIT BUTTON_REC
281 #define BTN_STOPRESET BUTTON_CANCEL
283 #elif CONFIG_KEYPAD == MPIO_HD200_PAD
285 #define BTN_DIR_UP BUTTON_REC
286 #define BTN_DIR_DOWN BUTTON_PLAY
287 #define BTN_DIR_LEFT BUTTON_REW
288 #define BTN_DIR_RIGHT BUTTON_FF
289 #define BTN_STARTPAUSE BUTTON_FUNC
290 #define BTN_QUIT (BUTTON_REC|BUTTON_PLAY)
291 #define BTN_STOPRESET (BUTTON_FUNC|BUTTON_REPEAT)
293 #elif CONFIG_KEYPAD == MPIO_HD300_PAD
295 #define BTN_DIR_UP BUTTON_UP
296 #define BTN_DIR_DOWN BUTTON_DOWN
297 #define BTN_DIR_LEFT BUTTON_MENU
298 #define BTN_DIR_RIGHT BUTTON_ENTER
299 #define BTN_STARTPAUSE BUTTON_PLAY
300 #define BTN_QUIT BUTTON_REC
301 #define BTN_STOPRESET (BUTTON_MENU | BUTTON_REPEAT)
303 #else
304 #error No keymap defined!
305 #endif
307 #ifdef HAVE_TOUCHSCREEN
308 #ifndef BTN_DIR_UP
309 #define BTN_DIR_UP BUTTON_TOPMIDDLE
310 #endif
311 #ifndef BTN_DIR_DOWN
312 #define BTN_DIR_DOWN BUTTON_BOTTOMMIDDLE
313 #endif
314 #ifndef BTN_DIR_LEFT
315 #define BTN_DIR_LEFT BUTTON_MIDLEFT
316 #endif
317 #ifndef BTN_DIR_RIGHT
318 #define BTN_DIR_RIGHT BUTTON_MIDRIGHT
319 #endif
320 #ifndef BTN_STARTPAUSE
321 #define BTN_STARTPAUSE BUTTON_CENTER
322 #endif
323 #ifndef BTN_QUIT
324 #define BTN_QUIT BUTTON_TOPLEFT
325 #endif
326 #ifndef BTN_STOPRESET
327 #define BTN_STOPRESET BUTTON_TOPRIGHT
328 #endif
329 #endif
331 #if (LCD_WIDTH == 112) && (LCD_HEIGHT == 64)
332 #define FOOD_SIZE 3
333 #define ARGH_SIZE 4
334 #define SPEED 14
335 #define MAX_WORM_SEGMENTS 128
336 #elif (LCD_WIDTH == 128) && (LCD_HEIGHT == 64)
337 #define FOOD_SIZE 3
338 #define ARGH_SIZE 4
339 #define SPEED 14
340 #define MAX_WORM_SEGMENTS 128
341 #elif (LCD_WIDTH == 132) && (LCD_HEIGHT == 80)
342 #define FOOD_SIZE 3
343 #define ARGH_SIZE 4
344 #define SPEED 14
345 #define MAX_WORM_SEGMENTS 128
346 #elif (LCD_WIDTH == 128) && (LCD_HEIGHT == 96)
347 #define FOOD_SIZE 3
348 #define ARGH_SIZE 4
349 #define SPEED 12
350 #define MAX_WORM_SEGMENTS 128
351 #elif (LCD_WIDTH == 138) && (LCD_HEIGHT == 110)
352 #define FOOD_SIZE 4
353 #define ARGH_SIZE 5
354 #define SPEED 10
355 #define MAX_WORM_SEGMENTS 128
356 #elif (LCD_WIDTH == 128) && (LCD_HEIGHT == 128)
357 #define FOOD_SIZE 4
358 #define ARGH_SIZE 5
359 #define SPEED 9
360 #define MAX_WORM_SEGMENTS 128
361 #elif ((LCD_WIDTH == 160) && (LCD_HEIGHT == 128)) || \
362 ((LCD_WIDTH == 128) && (LCD_HEIGHT == 160))
363 #define FOOD_SIZE 4
364 #define ARGH_SIZE 5
365 #define SPEED 8
366 #define MAX_WORM_SEGMENTS 256
367 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
368 #define FOOD_SIZE 4
369 #define ARGH_SIZE 5
370 #define SPEED 6
371 #define MAX_WORM_SEGMENTS 256
372 #elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
373 #define FOOD_SIZE 5
374 #define ARGH_SIZE 6
375 #define SPEED 4
376 #define MAX_WORM_SEGMENTS 512
377 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
378 #define FOOD_SIZE 5
379 #define ARGH_SIZE 6
380 #define SPEED 4
381 #define MAX_WORM_SEGMENTS 512
382 #elif ((LCD_WIDTH == 320) && (LCD_HEIGHT == 240)) || \
383 ((LCD_WIDTH == 240) && ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400)))
384 #define FOOD_SIZE 7
385 #define ARGH_SIZE 8
386 #define SPEED 4
387 #define MAX_WORM_SEGMENTS 512
388 #elif ((LCD_WIDTH == 640) && (LCD_HEIGHT == 480)) || \
389 ((LCD_WIDTH == 480) && (LCD_HEIGHT == 640))
390 #define FOOD_SIZE 14
391 #define ARGH_SIZE 16
392 #define SPEED 4
393 #define MAX_WORM_SEGMENTS 512
394 #endif
396 #ifdef HAVE_LCD_COLOR
397 #define COLOR_WORM LCD_RGBPACK(80, 40, 0)
398 #define COLOR_ARGH LCD_RGBPACK(175, 0, 0)
399 #define COLOR_FOOD LCD_RGBPACK(0, 150, 0)
400 #define COLOR_FG LCD_RGBPACK(0, 0, 0)
401 #define COLOR_BG LCD_RGBPACK(181, 199, 231)
402 #endif
404 #define CHECK_SQUARE_COLLISION(x1,y1,s1,x2,y2,s2) (x1+s1>x2)&&(x2+s2>x1)&&(y1+s1>y2)&&(y2+s2>y1)
407 * All the properties that a worm has.
409 static struct worm {
410 /* The worm is stored in a ring of xy coordinates */
411 int x[MAX_WORM_SEGMENTS];
412 int y[MAX_WORM_SEGMENTS];
414 int head; /* index of the head within the buffer */
415 int tail; /* index of the tail within the buffer */
416 int growing; /* number of cyles the worm still keeps growing */
417 bool alive; /* the worms living state */
419 /* direction vector in which the worm moves */
420 int dirx; /* only values -1 0 1 allowed */
421 int diry; /* only values -1 0 1 allowed */
423 /* this method is used to fetch the direction the user
424 has selected. It can be one of the values
425 human_player1, human_player2, remote_player, virtual_player.
426 All these values are fuctions, that can change the direction
427 of the worm */
428 void (*fetch_worm_direction)(struct worm *w);
429 } worms[MAX_WORMS];
431 /* stores the highscore - besides it was scored by a virtual player */
432 static int highscore;
434 #define MAX_FOOD 5 /* maximal number of food items */
436 /* The arrays store the food coordinates */
437 static int foodx[MAX_FOOD];
438 static int foody[MAX_FOOD];
440 #define MAX_ARGH 100 /* maximal number of argh items */
441 #define ARGHS_PER_FOOD 2 /* number of arghs produced per eaten food */
443 /* The arrays store the argh coordinates */
444 static int arghx[MAX_ARGH];
445 static int arghy[MAX_ARGH];
447 /* the number of arghs that are currently in use */
448 static int argh_count;
450 /* the number of arghs per food, settable by user */
451 static int arghs_per_food = ARGHS_PER_FOOD;
452 /* the size of the argh, settable by user */
453 static int argh_size = ARGH_SIZE;
454 /* the size of the food, settable by user */
455 static int food_size = FOOD_SIZE;
456 /* the speed of the worm, settable by user */
457 static int speed = SPEED;
458 /* the amount a worm grows by eating a food, settable by user */
459 static int worm_food = WORM_PER_FOOD;
461 /* End additional variables */
463 /* the number of active worms (dead or alive) */
464 static int worm_count = MAX_WORMS;
466 /* in multiplayer mode: en- / disables the remote worm control
467 in singleplayer mode: toggles 4 / 2 button worm control */
468 static bool use_remote = false;
470 /* return values of check_collision */
471 #define COLLISION_NONE 0
472 #define COLLISION_WORM 1
473 #define COLLISION_FOOD 2
474 #define COLLISION_ARGH 3
475 #define COLLISION_FIELD 4
477 static const char *const state_desc[] = {
478 [COLLISION_NONE] = NULL,
479 [COLLISION_WORM] = "Wormed",
480 [COLLISION_FOOD] = "Growing",
481 [COLLISION_ARGH] = "Argh",
482 [COLLISION_FIELD] = "Crashed",
485 /* constants for use as directions.
486 Note that the values are ordered clockwise.
487 Thus increasing / decreasing the values
488 is equivalent to right / left turns. */
489 #define WEST 0
490 #define NORTH 1
491 #define EAST 2
492 #define SOUTH 3
494 /* direction of human player 1 */
495 static int player1_dir = EAST;
496 /* direction of human player 2 */
497 static int player2_dir = EAST;
498 /* direction of human player 3 */
499 static int player3_dir = EAST;
501 /* the number of (human) players that currently
502 control a worm */
503 static int players = 1;
505 #define SETTINGS_VERSION 1
506 #define SETTINGS_MIN_VERSION 1
507 #define SETTINGS_FILENAME "wormlet.cfg"
509 static struct configdata config[] =
511 {TYPE_INT, 0, 1024, { .int_p = &highscore }, "highscore", NULL},
512 {TYPE_INT, 0, 15, { .int_p = &arghs_per_food }, "arghs per food", NULL},
513 {TYPE_INT, 0, 15, { .int_p = &argh_size }, "argh size", NULL},
514 {TYPE_INT, 0, 15, { .int_p = &food_size }, "food size", NULL},
515 {TYPE_INT, 0, 3, { .int_p = &players }, "players", NULL},
516 {TYPE_INT, 0, 3, { .int_p = &worm_count }, "worms", NULL},
517 {TYPE_INT, 0, 20, { .int_p = &speed }, "speed", NULL},
518 {TYPE_INT, 0, 15, { .int_p = &worm_food }, "Worm Growth Per Food", NULL}
522 * Returns the direction id in which the worm
523 * currently is creeping.
524 * @param struct worm *w The worm that is to be investigated.
525 * w Must not be null.
526 * @return int A value 0 <= value < 4
527 * Note the predefined constants NORTH, SOUTH, EAST, WEST
529 static int get_worm_dir(struct worm *w)
531 int retVal ;
532 if (w->dirx == 0) {
533 if (w->diry == 1) {
534 retVal = SOUTH;
535 } else {
536 retVal = NORTH;
538 } else {
539 if (w->dirx == 1) {
540 retVal = EAST;
541 } else {
542 retVal = WEST;
545 return retVal;
549 * Set the direction of the specified worm with a direction id.
550 * Increasing the value by 1 means to turn the worm direction
551 * to right by 90 degree.
552 * @param struct worm *w The worm that is to be altered. w Must not be null.
553 * @param int dir The new direction in which the worm is to creep.
554 * dir must be 0 <= dir < 4. Use predefined constants
555 * NORTH, SOUTH, EAST, WEST
557 static void set_worm_dir(struct worm *w, int dir)
559 switch (dir) {
560 case WEST:
561 w->dirx = -1;
562 w->diry = 0;
563 break;
564 case NORTH:
565 w->dirx = 0;
566 w->diry = - 1;
567 break;
568 case EAST:
569 w->dirx = 1;
570 w->diry = 0;
571 break;
572 case SOUTH:
573 w->dirx = 0;
574 w->diry = 1;
575 break;
580 * Returns the current length of the worm array. This
581 * is also a value for the number of bends that are in the worm.
582 * @return int a positive value with 0 <= value < MAX_WORM_SEGMENTS
584 static int get_worm_array_length(struct worm *w)
586 /* initial simple calculation will be overwritten if wrong. */
587 int retVal = w->head - w->tail;
589 /* if the worm 'crosses' the boundaries of the ringbuffer */
590 if (retVal < 0) {
591 retVal = w->head + MAX_WORM_SEGMENTS - w->tail;
594 return retVal;
598 * Returns the score the specified worm. The score is the length
599 * of the worm.
600 * @param struct worm *w The worm that is to be investigated.
601 * w must not be null.
602 * @return int The length of the worm (>= 0).
604 static int get_score(struct worm *w)
606 int retval = 0;
607 int length = get_worm_array_length(w);
608 int i;
609 for (i = 0; i < length; i++) {
611 /* The iteration iterates the length of the worm.
612 Here's the conversion to the true indices within the worm arrays. */
613 int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS;
614 int lineend = (linestart + 1) % MAX_WORM_SEGMENTS;
615 int startx = w->x[linestart];
616 int starty = w->y[linestart];
617 int endx = w->x[lineend];
618 int endy = w->y[lineend];
620 int minimum, maximum;
622 if (startx == endx) {
623 minimum = MIN(starty, endy);
624 maximum = MAX(starty, endy);
625 } else {
626 minimum = MIN(startx, endx);
627 maximum = MAX(startx, endx);
629 retval += abs(maximum - minimum);
631 return retval;
635 * Determines wether the line specified by startx, starty, endx, endy intersects
636 * the rectangle specified by x, y, width, height. Note that the line must be exactly
637 * horizontal or vertical (startx == endx or starty == endy).
638 * @param int startx The x coordinate of the start point of the line.
639 * @param int starty The y coordinate of the start point of the line.
640 * @param int endx The x coordinate of the end point of the line.
641 * @param int endy The y coordinate of the end point of the line.
642 * @param int x The x coordinate of the top left corner of the rectangle.
643 * @param int y The y coordinate of the top left corner of the rectangle.
644 * @param int width The width of the rectangle.
645 * @param int height The height of the rectangle.
646 * @return bool Returns true if the specified line intersects with the recangle.
648 static bool line_in_rect(int startx, int starty, int endx, int endy,
649 int x, int y, int width, int height)
651 bool retval = false;
652 int simple, simplemin, simplemax;
653 int compa, compb, compmin, compmax;
654 int temp;
655 if (startx == endx) {
656 simple = startx;
657 simplemin = x;
658 simplemax = x + width;
660 compa = starty;
661 compb = endy;
662 compmin = y;
663 compmax = y + height;
664 } else {
665 simple = starty;
666 simplemin = y;
667 simplemax = y + height;
669 compa = startx;
670 compb = endx;
671 compmin = x;
672 compmax = x + width;
675 temp = compa;
676 compa = MIN(compa, compb);
677 compb = MAX(temp, compb);
679 if (simplemin <= simple && simple <= simplemax) {
680 if ((compmin <= compa && compa <= compmax) ||
681 (compmin <= compb && compb <= compmax) ||
682 (compa <= compmin && compb >= compmax)) {
683 retval = true;
686 return retval;
690 * Tests wether the specified worm intersects with the rect.
691 * @param struct worm *w The worm to be investigated
692 * @param int x The x coordinate of the top left corner of the rect
693 * @param int y The y coordinate of the top left corner of the rect
694 * @param int widht The width of the rect
695 * @param int height The height of the rect
696 * @return bool Returns true if the worm intersects with the rect
698 static bool worm_in_rect(struct worm *w, int x, int y, int width, int height)
700 bool retval = false;
703 /* get_worm_array_length is expensive -> buffer the value */
704 int wormLength = get_worm_array_length(w);
705 int i;
707 /* test each entry that is part of the worm */
708 for (i = 0; i < wormLength && retval == false; i++) {
710 /* The iteration iterates the length of the worm.
711 Here's the conversion to the true indices within the worm arrays. */
712 int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS;
713 int lineend = (linestart + 1) % MAX_WORM_SEGMENTS;
714 int startx = w->x[linestart];
715 int starty = w->y[linestart];
716 int endx = w->x[lineend];
717 int endy = w->y[lineend];
719 retval = line_in_rect(startx, starty, endx, endy, x, y, width, height);
722 return retval;
726 * Checks wether a specific food in the food arrays is at the
727 * specified coordinates.
728 * @param int foodIndex The index of the food in the food arrays
729 * @param int x the x coordinate.
730 * @param int y the y coordinate.
731 * @return Returns true if the coordinate hits the food specified by
732 * foodIndex.
734 static bool specific_food_collision(int foodIndex, int x, int y)
736 bool retVal = false;
737 if (x >= foodx[foodIndex] &&
738 x < foodx[foodIndex] + food_size &&
739 y >= foody[foodIndex] &&
740 y < foody[foodIndex] + food_size) {
742 retVal = true;
744 return retVal;
748 * Returns the index of the food that is at the
749 * given coordinates. If no food is at the coordinates
750 * -1 is returned.
751 * @return int -1 <= value < MAX_FOOD
753 static int food_collision(int x, int y)
755 int i = 0;
756 int retVal = -1;
757 for (i = 0; i < MAX_FOOD; i++) {
758 if (specific_food_collision(i, x, y)) {
759 retVal = i;
760 break;
763 return retVal;
767 * Checks wether a specific argh in the argh arrays is at the
768 * specified coordinates.
769 * @param int arghIndex The index of the argh in the argh arrays
770 * @param int x the x coordinate.
771 * @param int y the y coordinate.
772 * @return Returns true if the coordinate hits the argh specified by
773 * arghIndex.
775 static bool specific_argh_collision(int arghIndex, int x, int y)
777 if ( x >= arghx[arghIndex] &&
778 y >= arghy[arghIndex] &&
779 x < arghx[arghIndex] + argh_size &&
780 y < arghy[arghIndex] + argh_size )
782 return true;
785 return false;
789 * Returns the index of the argh that is at the
790 * given coordinates. If no argh is at the coordinates
791 * -1 is returned.
792 * @param int x The x coordinate.
793 * @param int y The y coordinate.
794 * @return int -1 <= value < argh_count <= MAX_ARGH
796 static int argh_collision(int x, int y)
798 int i = 0;
799 int retVal = -1;
801 /* search for the argh that has the specified coords */
802 for (i = 0; i < argh_count; i++) {
803 if (specific_argh_collision(i, x, y)) {
804 retVal = i;
805 break;
808 return retVal;
812 * Checks wether the worm collides with the food at the specfied food-arrays.
813 * @param int foodIndex The index of the food in the arrays. Ensure the value is
814 * 0 <= foodIndex <= MAX_FOOD
815 * @return Returns true if the worm collides with the specified food.
817 static bool worm_food_collision(struct worm *w, int foodIndex)
819 bool retVal = false;
821 retVal = worm_in_rect(w, foodx[foodIndex], foody[foodIndex],
822 food_size - 1, food_size - 1);
824 return retVal;
828 * Returns true if the worm hits the argh within the next moves (unless
829 * the worm changes it's direction).
830 * @param struct worm *w - The worm to investigate
831 * @param int argh_idx - The index of the argh
832 * @param int moves - The number of moves that are considered.
833 * @return Returns false if the specified argh is not hit within the next
834 * moves.
836 static bool worm_argh_collision_in_moves(struct worm *w, int argh_idx, int moves)
838 bool retVal = false;
839 int x1, y1, x2, y2;
840 x1 = w->x[w->head];
841 y1 = w->y[w->head];
843 x2 = w->x[w->head] + moves * w->dirx;
844 y2 = w->y[w->head] + moves * w->diry;
846 retVal = line_in_rect(x1, y1, x2, y2, arghx[argh_idx], arghy[argh_idx],
847 argh_size, argh_size);
848 return retVal;
852 * Checks wether the worm collides with the argh at the specfied argh-arrays.
853 * @param int arghIndex The index of the argh in the arrays.
854 * Ensure the value is 0 <= arghIndex < argh_count <= MAX_ARGH
855 * @return Returns true if the worm collides with the specified argh.
857 static bool worm_argh_collision(struct worm *w, int arghIndex)
859 bool retVal = false;
861 retVal = worm_in_rect(w, arghx[arghIndex], arghy[arghIndex],
862 argh_size - 1, argh_size - 1);
864 return retVal;
868 * Find new coordinates for the food stored in foodx[index], foody[index]
869 * that don't collide with any other food or argh
870 * @param int index
871 * Ensure that 0 <= index < MAX_FOOD.
873 static void make_food(int index)
875 int x = 0;
876 int y = 0;
877 bool collisionDetected = false;
878 int i;
880 do {
881 /* make coordinates for a new food so that
882 the entire food lies within the FIELD */
883 x = rb->rand() % (FIELD_RECT_WIDTH - food_size);
884 y = rb->rand() % (FIELD_RECT_HEIGHT - food_size);
885 collisionDetected = false;
886 /* Ensure that the new food doesn't collide with any
887 existing foods or arghs.
888 If the new food hit any existing
889 argh or food a collision is detected.
892 for (i=0; i<MAX_FOOD && !collisionDetected; i++) {
893 collisionDetected = CHECK_SQUARE_COLLISION(x,y,food_size,foodx[i],foody[i],food_size);
895 for (i=0; i<argh_count && !collisionDetected; i++) {
896 collisionDetected = CHECK_SQUARE_COLLISION(x,y,food_size,arghx[i],arghy[i],argh_size);
899 /* use coordinates for further testing */
900 foodx[index] = x;
901 foody[index] = y;
903 /* now test wether we accidently hit the worm with food ;) */
904 i = 0;
905 for (i = 0; i < worm_count && !collisionDetected; i++) {
907 collisionDetected = worm_food_collision(&worms[i], index);
911 while (collisionDetected);
912 return;
916 * Clears a food from the lcd buffer.
917 * @param int index The index of the food arrays under which
918 * the coordinates of the desired food can be found. Ensure
919 * that the value is 0 <= index <= MAX_FOOD.
921 static void clear_food(int index)
923 /* remove the old food from the screen */
924 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
925 rb->lcd_fillrect(foodx[index] + FIELD_RECT_X,
926 foody[index] + FIELD_RECT_Y,
927 food_size, food_size);
928 rb->lcd_set_drawmode(DRMODE_SOLID);
932 * Draws a food in the lcd buffer.
933 * @param int index The index of the food arrays under which
934 * the coordinates of the desired food can be found. Ensure
935 * that the value is 0 <= index <= MAX_FOOD.
937 static void draw_food(int index)
939 /* draw the food object */
940 #ifdef HAVE_LCD_COLOR
941 rb->lcd_set_foreground(COLOR_FOOD);
942 #endif
943 rb->lcd_fillrect(foodx[index] + FIELD_RECT_X,
944 foody[index] + FIELD_RECT_Y,
945 food_size, food_size);
946 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
947 rb->lcd_fillrect(foodx[index] + FIELD_RECT_X + 1,
948 foody[index] + FIELD_RECT_Y + 1,
949 food_size - 2, food_size - 2);
950 rb->lcd_set_drawmode(DRMODE_SOLID);
951 #ifdef HAVE_LCD_COLOR
952 rb->lcd_set_foreground(COLOR_FG);
953 #endif
957 * Find new coordinates for the argh stored in arghx[index], arghy[index]
958 * that don't collide with any other food or argh.
959 * @param int index
960 * Ensure that 0 <= index < argh_count < MAX_ARGH.
962 static void make_argh(int index)
964 int x = -1;
965 int y = -1;
966 bool collisionDetected = false;
967 int i;
969 do {
970 /* make coordinates for a new argh so that
971 the entire food lies within the FIELD */
972 x = rb->rand() % (FIELD_RECT_WIDTH - argh_size);
973 y = rb->rand() % (FIELD_RECT_HEIGHT - argh_size);
974 collisionDetected = false;
975 /* Ensure that the new argh doesn't intersect with any
976 existing foods or arghs.
977 If the new argh hit any existing
978 argh or food an intersection is detected.
981 for (i=0; i<MAX_FOOD && !collisionDetected; i++) {
982 collisionDetected = CHECK_SQUARE_COLLISION(x,y,argh_size,foodx[i],foody[i],food_size);
984 for (i=0; i<argh_count && !collisionDetected; i++) {
985 collisionDetected = CHECK_SQUARE_COLLISION(x,y,argh_size,arghx[i],arghy[i],argh_size);
988 /* use the candidate coordinates to make a real argh */
989 arghx[index] = x;
990 arghy[index] = y;
992 /* now test wether we accidently hit the worm with argh ;) */
993 for (i = 0; i < worm_count && !collisionDetected; i++) {
994 collisionDetected |= worm_argh_collision(&worms[i], index);
995 collisionDetected |= worm_argh_collision_in_moves(&worms[i], index,
996 MIN_ARGH_DIST);
999 while (collisionDetected);
1000 return;
1004 * Draws an argh in the lcd buffer.
1005 * @param int index The index of the argh arrays under which
1006 * the coordinates of the desired argh can be found. Ensure
1007 * that the value is 0 <= index < argh_count <= MAX_ARGH.
1009 static void draw_argh(int index)
1011 /* draw the new argh */
1012 #ifdef HAVE_LCD_COLOR
1013 rb->lcd_set_foreground(COLOR_ARGH);
1014 #endif
1015 rb->lcd_fillrect(arghx[index] + FIELD_RECT_X,
1016 arghy[index] + FIELD_RECT_Y,
1017 argh_size, argh_size);
1018 #ifdef HAVE_LCD_COLOR
1019 rb->lcd_set_foreground(COLOR_FG);
1020 #endif
1023 static void virtual_player(struct worm *w);
1025 * Initialzes the specified worm with INITIAL_WORM_LENGTH
1026 * and the tail at the specified position. The worm will
1027 * be initialized alive and creeping EAST.
1028 * @param struct worm *w The worm that is to be initialized
1029 * @param int x The x coordinate at which the tail of the worm starts.
1030 * x must be 0 <= x < FIELD_RECT_WIDTH.
1031 * @param int y The y coordinate at which the tail of the worm starts
1032 * y must be 0 <= y < FIELD_RECT_WIDTH.
1034 static void init_worm(struct worm *w, int x, int y)
1036 /* initialize the worm size */
1037 w->head = 1;
1038 w->tail = 0;
1040 w->x[w->head] = x + 1;
1041 w->y[w->head] = y;
1043 w->x[w->tail] = x;
1044 w->y[w->tail] = y;
1046 /* set the initial direction the worm creeps to */
1047 w->dirx = 1;
1048 w->diry = 0;
1050 w->growing = INITIAL_WORM_LENGTH - 1;
1051 w->alive = true;
1052 w->fetch_worm_direction = virtual_player;
1056 * Writes the direction that was stored for
1057 * human player 1 into the specified worm. This function
1058 * may be used to be stored in worm.fetch_worm_direction.
1059 * The value of
1060 * the direction is read from player1_dir.
1061 * @param struct worm *w - The worm of which the direction
1062 * is altered.
1064 static void human_player1(struct worm *w) {
1065 set_worm_dir(w, player1_dir);
1069 * Writes the direction that was stored for
1070 * human player 2 into the specified worm. This function
1071 * may be used to be stored in worm.fetch_worm_direction.
1072 * The value of
1073 * the direction is read from player2_dir.
1074 * @param struct worm *w - The worm of which the direction
1075 * is altered.
1077 static void human_player2(struct worm *w) {
1078 set_worm_dir(w, player2_dir);
1082 * Writes the direction that was stored for
1083 * human player using a remote control
1084 * into the specified worm. This function
1085 * may be used to be stored in worm.fetch_worm_direction.
1086 * The value of
1087 * the direction is read from player3_dir.
1088 * @param struct worm *w - The worm of which the direction
1089 * is altered.
1091 static void remote_player(struct worm *w) {
1092 set_worm_dir(w, player3_dir);
1096 * Initializes the worm-, food- and argh-arrays, draws a frame,
1097 * makes some food and argh and display all that stuff.
1099 static void init_wormlet(void)
1101 int i;
1103 for (i = 0; i< worm_count; i++) {
1104 /* Initialize all the worm coordinates to center. */
1105 int x = (int)(FIELD_RECT_WIDTH / 2);
1106 int y = (int)((FIELD_RECT_HEIGHT - 20)/ 2) + i * 10;
1108 init_worm(&worms[i], x, y);
1111 player1_dir = EAST;
1112 player2_dir = EAST;
1113 player3_dir = EAST;
1115 if (players > 0) {
1116 worms[0].fetch_worm_direction = human_player1;
1119 if (players > 1) {
1120 if (use_remote) {
1121 worms[1].fetch_worm_direction = remote_player;
1122 } else {
1123 worms[1].fetch_worm_direction = human_player2;
1127 if (players > 2) {
1128 worms[2].fetch_worm_direction = human_player2;
1131 /* Needed when the game is restarted using BTN_STOPRESET */
1132 rb->lcd_clear_display();
1134 /* make and display some food and argh */
1135 argh_count = MAX_FOOD;
1136 for (i = 0; i < MAX_FOOD; i++) {
1137 make_food(i);
1138 draw_food(i);
1139 make_argh(i);
1140 draw_argh(i);
1143 /* draw the game field */
1144 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
1145 rb->lcd_fillrect(0, 0, FIELD_RECT_WIDTH + 2, FIELD_RECT_HEIGHT + 2);
1146 rb->lcd_fillrect(1, 1, FIELD_RECT_WIDTH, FIELD_RECT_HEIGHT);
1147 rb->lcd_set_drawmode(DRMODE_SOLID);
1149 /* make everything visible */
1150 rb->lcd_update();
1155 * Move the worm one step further if it is alive.
1156 * The direction in which the worm moves is taken from dirx and diry.
1157 * move_worm decreases growing if > 0. While the worm is growing the tail
1158 * is left untouched.
1159 * @param struct worm *w The worm to move. w must not be NULL.
1161 static void move_worm(struct worm *w)
1163 if (w->alive) {
1164 /* determine the head point and its precessor */
1165 int headx = w->x[w->head];
1166 int heady = w->y[w->head];
1167 int prehead = (w->head + MAX_WORM_SEGMENTS - 1) % MAX_WORM_SEGMENTS;
1168 int preheadx = w->x[prehead];
1169 int preheady = w->y[prehead];
1171 /* determine the old direction */
1172 int olddirx;
1173 int olddiry;
1174 if (headx == preheadx) {
1175 olddirx = 0;
1176 olddiry = (heady > preheady) ? 1 : -1;
1177 } else {
1178 olddiry = 0;
1179 olddirx = (headx > preheadx) ? 1 : -1;
1182 /* olddir == dir?
1183 a change of direction means a new segment
1184 has been opened */
1185 if (olddirx != w->dirx ||
1186 olddiry != w->diry) {
1187 w->head = (w->head + 1) % MAX_WORM_SEGMENTS;
1190 /* new head position */
1191 w->x[w->head] = headx + w->dirx;
1192 w->y[w->head] = heady + w->diry;
1195 /* while the worm is growing no tail procession is necessary */
1196 if (w->growing > 0) {
1197 /* update the worms grow state */
1198 w->growing--;
1201 /* if the worm isn't growing the tail has to be dragged */
1202 else {
1203 /* index of the end of the tail segment */
1204 int tail_segment_end = (w->tail + 1) % MAX_WORM_SEGMENTS;
1206 /* drag the end of the tail */
1207 /* only one coordinate has to be altered. Here it is
1208 determined which one */
1209 int dir = 0; /* specifies wether the coord has to be in- or decreased */
1210 if (w->x[w->tail] == w->x[tail_segment_end]) {
1211 dir = (w->y[w->tail] - w->y[tail_segment_end] < 0) ? 1 : -1;
1212 w->y[w->tail] += dir;
1213 } else {
1214 dir = (w->x[w->tail] - w->x[tail_segment_end] < 0) ? 1 : -1;
1215 w->x[w->tail] += dir;
1218 /* when the tail has been dragged so far that it meets
1219 the next segment start the tail segment is obsolete and
1220 must be freed */
1221 if (w->x[w->tail] == w->x[tail_segment_end] &&
1222 w->y[w->tail] == w->y[tail_segment_end]){
1224 /* drop the last tail point */
1225 w->tail = tail_segment_end;
1232 * Draws the head and clears the tail of the worm in
1233 * the display buffer. lcd_update() is NOT called thus
1234 * the caller has to take care that the buffer is displayed.
1236 static void draw_worm(struct worm *w)
1238 /* draw the new head */
1239 int x = w->x[w->head];
1240 int y = w->y[w->head];
1241 #ifdef HAVE_LCD_COLOR
1242 rb->lcd_set_foreground(COLOR_WORM);
1243 #endif
1244 if (x >= 0 && x < FIELD_RECT_WIDTH && y >= 0 && y < FIELD_RECT_HEIGHT) {
1245 rb->lcd_drawpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y);
1248 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1250 /* clear the space behind the worm */
1251 x = w->x[w->tail] ;
1252 y = w->y[w->tail] ;
1253 if (x >= 0 && x < FIELD_RECT_WIDTH && y >= 0 && y < FIELD_RECT_HEIGHT) {
1254 rb->lcd_drawpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y);
1256 rb->lcd_set_drawmode(DRMODE_SOLID);
1257 #ifdef HAVE_LCD_COLOR
1258 rb->lcd_set_foreground(COLOR_FG);
1259 #endif
1263 * Checks wether the coordinate is part of the worm. Returns
1264 * true if any part of the worm was hit - including the head.
1265 * @param x int The x coordinate
1266 * @param y int The y coordinate
1267 * @return int The index of the worm arrays that contain x, y.
1268 * Returns -1 if the coordinates are not part of the worm.
1270 static int specific_worm_collision(struct worm *w, int x, int y)
1272 int retVal = -1;
1274 /* get_worm_array_length is expensive -> buffer the value */
1275 int wormLength = get_worm_array_length(w);
1276 int i;
1278 /* test each entry that is part of the worm */
1279 for (i = 0; i < wormLength && retVal == -1; i++) {
1281 /* The iteration iterates the length of the worm.
1282 Here's the conversion to the true indices within the worm arrays. */
1283 int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS;
1284 int lineend = (linestart + 1) % MAX_WORM_SEGMENTS;
1285 bool samex = (w->x[linestart] == x) && (w->x[lineend] == x);
1286 bool samey = (w->y[linestart] == y) && (w->y[lineend] == y);
1287 if (samex || samey){
1288 int test, min, max, tmp;
1290 if (samey) {
1291 min = w->x[linestart];
1292 max = w->x[lineend];
1293 test = x;
1294 } else {
1295 min = w->y[linestart];
1296 max = w->y[lineend];
1297 test = y;
1300 tmp = min;
1301 min = MIN(min, max);
1302 max = MAX(tmp, max);
1304 if (min <= test && test <= max) {
1305 retVal = lineend;
1309 return retVal;
1313 * Increases the length of the specified worm by marking
1314 * that it may grow by len pixels. Note that the worm has
1315 * to move to make the growing happen.
1316 * @param worm *w The worm that is to be altered.
1317 * @param int len A positive value specifying the amount of
1318 * pixels the worm may grow.
1320 static void add_growing(struct worm *w, int len) {
1321 w->growing += len;
1325 * Determins the worm that is at the coordinates x, y. The parameter
1326 * w is a switch parameter that changes the functionality of worm_collision.
1327 * If w is specified and x,y hits the head of w NULL is returned.
1328 * This is a useful way to determine wether the head of w hits
1329 * any worm but including itself but excluding its own head.
1330 * (It hits always its own head ;))
1331 * If w is set to NULL worm_collision returns any worm including all heads
1332 * that is at position of x,y.
1333 * @param struct worm *w The worm of which the head should be excluded in
1334 * the test. w may be set to NULL.
1335 * @param int x The x coordinate that is checked
1336 * @param int y The y coordinate that is checkec
1337 * @return struct worm* The worm that has been hit by x,y. If no worm
1338 * was at the position NULL is returned.
1340 static struct worm* worm_collision(struct worm *w, int x, int y)
1342 struct worm *retVal = NULL;
1343 int i;
1344 for (i = 0; (i < worm_count) && (retVal == NULL); i++) {
1345 int collision_at = specific_worm_collision(&worms[i], x, y);
1346 if (collision_at != -1) {
1347 if (!(w == &worms[i] && collision_at == w->head)){
1348 retVal = &worms[i];
1352 return retVal;
1356 * Returns true if the head of the worm just has
1357 * crossed the field boundaries.
1358 * @return bool true if the worm just has wrapped.
1360 static bool field_collision(struct worm *w)
1362 bool retVal = false;
1363 if ((w->x[w->head] >= FIELD_RECT_WIDTH) ||
1364 (w->y[w->head] >= FIELD_RECT_HEIGHT) ||
1365 (w->x[w->head] < 0) ||
1366 (w->y[w->head] < 0))
1368 retVal = true;
1370 return retVal;
1375 * Returns true if the specified coordinates are within the
1376 * field specified by the FIELD_RECT_XXX constants.
1377 * @param int x The x coordinate of the point that is investigated
1378 * @param int y The y coordinate of the point that is investigated
1379 * @return bool Returns false if x,y specifies a point outside the
1380 * field of worms.
1382 static bool is_in_field_rect(int x, int y)
1384 bool retVal = false;
1385 retVal = (x >= 0 && x < FIELD_RECT_WIDTH &&
1386 y >= 0 && y < FIELD_RECT_HEIGHT);
1387 return retVal;
1391 * Checks and returns wether the head of the w
1392 * is colliding with something currently.
1393 * @return int One of the values:
1394 * COLLISION_NONE
1395 * COLLISION_w
1396 * COLLISION_FOOD
1397 * COLLISION_ARGH
1398 * COLLISION_FIELD
1400 static int check_collision(struct worm *w)
1402 int retVal = COLLISION_NONE;
1404 if (worm_collision(w, w->x[w->head], w->y[w->head]) != NULL)
1405 retVal = COLLISION_WORM;
1407 if (food_collision(w->x[w->head], w->y[w->head]) >= 0)
1408 retVal = COLLISION_FOOD;
1410 if (argh_collision(w->x[w->head], w->y[w->head]) >= 0)
1411 retVal = COLLISION_ARGH;
1413 if (field_collision(w))
1414 retVal = COLLISION_FIELD;
1416 return retVal;
1420 * Returns the index of the food that is closest to the point
1421 * specified by x, y. This index may be used in the foodx and
1422 * foody arrays.
1423 * @param int x The x coordinate of the point
1424 * @param int y The y coordinate of the point
1425 * @return int A value usable as index in foodx and foody.
1427 static int get_nearest_food(int x, int y)
1429 int nearestfood = 0;
1430 int olddistance = FIELD_RECT_WIDTH + FIELD_RECT_HEIGHT;
1431 int deltax = 0;
1432 int deltay = 0;
1433 int foodindex;
1434 for (foodindex = 0; foodindex < MAX_FOOD; foodindex++) {
1435 int distance;
1436 deltax = foodx[foodindex] - x;
1437 deltay = foody[foodindex] - y;
1438 deltax = deltax > 0 ? deltax : deltax * (-1);
1439 deltay = deltay > 0 ? deltay : deltay * (-1);
1440 distance = deltax + deltay;
1442 if (distance < olddistance) {
1443 olddistance = distance;
1444 nearestfood = foodindex;
1447 return nearestfood;
1451 * Returns wether the specified position is next to the worm
1452 * and in the direction the worm looks. Use this method to
1453 * test wether this position would be hit with the next move of
1454 * the worm unless the worm changes its direction.
1455 * @param struct worm *w - The worm to be investigated
1456 * @param int x - The x coordinate of the position to test.
1457 * @param int y - The y coordinate of the position to test.
1458 * @return Returns true if the worm will hit the position unless
1459 * it change its direction before the next move.
1461 static bool is_in_front_of_worm(struct worm *w, int x, int y)
1463 bool infront = false;
1464 int deltax = x - w->x[w->head];
1465 int deltay = y - w->y[w->head];
1467 if (w->dirx == 0) {
1468 infront = (w->diry * deltay) > 0;
1469 } else {
1470 infront = (w->dirx * deltax) > 0;
1472 return infront;
1476 * Returns true if the worm will collide with the next move unless
1477 * it changes its direction.
1478 * @param struct worm *w - The worm to be investigated.
1479 * @return Returns true if the worm will collide with the next move
1480 * unless it changes its direction.
1482 static bool will_worm_collide(struct worm *w)
1484 int x = w->x[w->head] + w->dirx;
1485 int y = w->y[w->head] + w->diry;
1486 bool retVal = !is_in_field_rect(x, y);
1487 if (!retVal) {
1488 retVal = (argh_collision(x, y) != -1);
1491 if (!retVal) {
1492 retVal = (worm_collision(w, x, y) != NULL);
1494 return retVal;
1498 * This function
1499 * may be used to be stored in worm.fetch_worm_direction for
1500 * worms that are not controlled by humans but by artificial stupidity.
1501 * A direction is searched that doesn't lead to collision but to the nearest
1502 * food - but not very intelligent. The direction is written to the specified
1503 * worm.
1504 * @param struct worm *w - The worm of which the direction
1505 * is altered.
1507 static void virtual_player(struct worm *w)
1509 bool isright;
1510 int plana, planb, planc;
1511 /* find the next lunch */
1512 int nearestfood = get_nearest_food(w->x[w->head], w->y[w->head]);
1514 /* determine in which direction it is */
1516 /* in front of me? */
1517 bool infront = is_in_front_of_worm(w, foodx[nearestfood], foody[nearestfood]);
1519 /* left right of me? */
1520 int olddir = get_worm_dir(w);
1521 set_worm_dir(w, (olddir + 1) % 4);
1522 isright = is_in_front_of_worm(w, foodx[nearestfood], foody[nearestfood]);
1523 set_worm_dir(w, olddir);
1525 /* detect situation, set strategy */
1526 if (infront) {
1527 if (isright) {
1528 plana = olddir;
1529 planb = (olddir + 1) % 4;
1530 planc = (olddir + 3) % 4;
1531 } else {
1532 plana = olddir;
1533 planb = (olddir + 3) % 4;
1534 planc = (olddir + 1) % 4;
1536 } else {
1537 if (isright) {
1538 plana = (olddir + 1) % 4;
1539 planb = olddir;
1540 planc = (olddir + 3) % 4;
1541 } else {
1542 plana = (olddir + 3) % 4;
1543 planb = olddir;
1544 planc = (olddir + 1) % 4;
1548 /* test for collision */
1549 set_worm_dir(w, plana);
1550 if (will_worm_collide(w)){
1552 /* plan b */
1553 set_worm_dir(w, planb);
1555 /* test for collision */
1556 if (will_worm_collide(w)) {
1558 /* plan c */
1559 set_worm_dir(w, planc);
1565 * prints out the score board with all the status information
1566 * about the game.
1568 static void score_board(void)
1570 int i;
1571 int y = 0;
1572 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1573 rb->lcd_fillrect(FIELD_RECT_WIDTH + 2, 0,
1574 LCD_WIDTH - FIELD_RECT_WIDTH - 2, LCD_HEIGHT);
1575 rb->lcd_set_drawmode(DRMODE_SOLID);
1576 for (i = 0; i < worm_count; i++) {
1577 int score = get_score(&worms[i]);
1578 int collision = check_collision(&worms[i]);
1579 const char *state_str;
1581 /* high score */
1582 if (worms[i].fetch_worm_direction != virtual_player){
1583 if (highscore < score) {
1584 highscore = score;
1588 /* worm state */
1589 if (collision == COLLISION_NONE) {
1590 if (worms[i].growing > 0)
1591 state_str = "Growing";
1592 else {
1593 state_str = worms[i].alive ? "Hungry" : "Wormed";
1595 } else {
1596 state_str = state_desc[collision];
1599 /* length */
1600 rb->lcd_putsxyf(FIELD_RECT_WIDTH + 3, y , "Len:%d", score);
1601 rb->lcd_putsxyf(FIELD_RECT_WIDTH + 3, y+8, state_str);
1603 if (!worms[i].alive){
1604 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
1605 rb->lcd_fillrect(FIELD_RECT_WIDTH + 2, y,
1606 LCD_WIDTH - FIELD_RECT_WIDTH - 2, 17);
1607 rb->lcd_set_drawmode(DRMODE_SOLID);
1609 y += 19;
1611 #ifdef DEBUG_WORMLET
1612 rb->lcd_putsxyf(FIELD_RECT_WIDTH + 3, LCD_HEIGHT - 8, "ticks %d", max_cycle);
1613 #else
1614 rb->lcd_putsxyf(FIELD_RECT_WIDTH + 3, LCD_HEIGHT - 8, "Hs: %d", highscore);
1615 #endif
1619 * Checks for collisions of the worm and its environment and
1620 * takes appropriate actions like growing the worm or killing it.
1621 * @return bool Returns true if the worm is dead. Returns
1622 * false if the worm is healthy, up and creeping.
1624 static bool process_collisions(struct worm *w)
1626 int index = -1;
1628 w->alive &= !field_collision(w);
1630 if (w->alive) {
1632 /* check if food was eaten */
1633 index = food_collision(w->x[w->head], w->y[w->head]);
1634 if (index != -1){
1635 int i;
1637 clear_food(index);
1638 make_food(index);
1639 draw_food(index);
1641 for (i = 0; i < arghs_per_food; i++) {
1642 argh_count++;
1643 if (argh_count > MAX_ARGH)
1644 argh_count = MAX_ARGH;
1645 make_argh(argh_count - 1);
1646 draw_argh(argh_count - 1);
1649 add_growing(w, worm_food);
1651 draw_worm(w);
1654 /* check if argh was eaten */
1655 else {
1656 index = argh_collision(w->x[w->head], w->y[w->head]);
1657 if (index != -1) {
1658 w->alive = false;
1660 else {
1661 if (worm_collision(w, w->x[w->head], w->y[w->head]) != NULL) {
1662 w->alive = false;
1667 return !w->alive;
1671 * The main loop of the game.
1672 * @return bool Returns true if the game ended
1673 * with a dead worm. Returns false if the user
1674 * aborted the game manually.
1676 static int run(void)
1678 int button = 0;
1679 int wormDead = false;
1680 bool paused = false;
1682 /* ticks are counted to compensate speed variations */
1683 long cycle_start = 0, cycle_end = 0;
1684 #ifdef DEBUG_WORMLET
1685 int ticks_to_max_cycle_reset = 20;
1686 max_cycle = 0;
1687 #endif
1689 /* initialize the board and so on */
1690 init_wormlet();
1692 cycle_start = *rb->current_tick;
1693 /* change the direction of the worm */
1694 while (!wormDead)
1696 int i;
1697 long cycle_duration=0;
1699 #ifdef HAS_BUTTON_HOLD
1700 if (rb->button_hold())
1701 paused = true;
1702 #endif
1704 switch (button) {
1705 case BTN_STARTPAUSE:
1706 paused = !paused;
1707 break;
1708 case BTN_STOPRESET:
1709 if (paused)
1710 return 1; /* restart game */
1711 else
1712 paused = true;
1713 break;
1714 #ifdef BTN_RC_QUIT
1715 case BTN_RC_QUIT:
1716 #endif
1717 case BTN_QUIT:
1718 return 2; /* back to menu */
1719 break;
1721 if (!paused)
1723 switch (button) {
1724 case BTN_DIR_UP:
1725 if (players == 1 && !use_remote) {
1726 player1_dir = NORTH;
1728 break;
1730 case BTN_DIR_DOWN:
1731 if (players == 1 && !use_remote) {
1732 player1_dir = SOUTH;
1734 break;
1736 case BTN_DIR_LEFT:
1737 if (players != 1 || use_remote) {
1738 player1_dir = (player1_dir + 3) % 4;
1739 } else {
1740 player1_dir = WEST;
1742 break;
1744 case BTN_DIR_RIGHT:
1745 if (players != 1 || use_remote) {
1746 player1_dir = (player1_dir + 1) % 4;
1747 } else {
1748 player1_dir = EAST;
1750 break;
1752 #ifdef MULTIPLAYER
1753 case BTN_PLAYER2_DIR1:
1754 player2_dir = (player2_dir + 3) % 4;
1755 break;
1757 case BTN_PLAYER2_DIR2:
1758 player2_dir = (player2_dir + 1) % 4;
1759 break;
1760 #endif
1762 #ifdef REMOTE
1763 case BTN_RC_UP:
1764 player3_dir = (player3_dir + 1) % 4;
1765 break;
1767 case BTN_RC_DOWN:
1768 player3_dir = (player3_dir + 3) % 4;
1769 break;
1770 #endif
1774 for (i = 0; i < worm_count; i++) {
1775 worms[i].fetch_worm_direction(&worms[i]);
1778 wormDead = true;
1779 for (i = 0; i < worm_count; i++){
1780 struct worm *w = &worms[i];
1781 move_worm(w);
1782 wormDead &= process_collisions(w);
1783 draw_worm(w);
1785 score_board();
1786 rb->lcd_update();
1787 if (button == BTN_STOPRESET) {
1788 wormDead = true;
1791 /* here the wormlet game cycle ends
1792 thus the current tick is stored
1793 as end time */
1794 cycle_end = *rb->current_tick;
1796 /* The duration of the game cycle */
1797 cycle_duration = cycle_end - cycle_start;
1798 cycle_duration = MAX(0, cycle_duration);
1799 cycle_duration = MIN(speed -1, cycle_duration);
1802 #ifdef DEBUG_WORMLET
1803 ticks_to_max_cycle_reset--;
1804 if (ticks_to_max_cycle_reset <= 0) {
1805 max_cycle = 0;
1808 if (max_cycle < cycle_duration) {
1809 max_cycle = cycle_duration;
1810 ticks_to_max_cycle_reset = 20;
1812 #endif
1814 /* adjust the number of ticks to wait for a button.
1815 This ensures that a complete game cycle including
1816 user input runs in constant time */
1817 button = rb->button_get_w_tmo(speed - cycle_duration);
1818 cycle_start = *rb->current_tick;
1821 rb->splash(HZ*2, "Game Over!");
1823 return 2; /* back to menu */
1826 #ifdef DEBUG_WORMLET
1829 * Just a test routine that checks that worm_food_collision works
1830 * in some typical situations.
1832 static void test_worm_food_collision(void)
1834 int collision_count = 0;
1835 int i;
1836 rb->lcd_clear_display();
1837 init_worm(&worms[0], 10, 10);
1838 add_growing(&worms[0], 10);
1839 set_worm_dir(&worms[0], EAST);
1840 for (i = 0; i < 10; i++) {
1841 move_worm(&worms[0]);
1842 draw_worm(&worms[0]);
1845 set_worm_dir(&worms[0], SOUTH);
1846 for (i = 0; i < 10; i++) {
1847 move_worm(&worms[0]);
1848 draw_worm(&worms[0]);
1851 foodx[0] = 15;
1852 foody[0] = 12;
1853 for (foody[0] = 20; foody[0] > 0; foody[0] --) {
1854 bool collision;
1855 draw_worm(&worms[0]);
1856 draw_food(0);
1857 collision = worm_food_collision(&worms[0], 0);
1858 if (collision) {
1859 collision_count++;
1861 rb->lcd_putsxyf(0, LCD_HEIGHT -8, "collisions: %d", collision_count);
1862 rb->lcd_update();
1864 if (collision_count != food_size) {
1865 rb->button_get(true);
1869 foody[0] = 15;
1870 for (foodx[0] = 30; foodx[0] > 0; foodx[0] --) {
1871 bool collision;
1872 draw_worm(&worms[0]);
1873 draw_food(0);
1874 collision = worm_food_collision(&worms[0], 0);
1875 if (collision) {
1876 collision_count ++;
1878 rb->lcd_putsxyf(0, LCD_HEIGHT -8, "collisions: %d", collision_count);
1879 rb->lcd_update();
1881 if (collision_count != food_size * 2) {
1882 rb->button_get(true);
1887 static bool expensive_worm_in_rect(struct worm *w, int rx, int ry, int rw, int rh)
1889 int x, y;
1890 bool retVal = false;
1891 for (x = rx; x < rx + rw; x++){
1892 for (y = ry; y < ry + rh; y++) {
1893 if (specific_worm_collision(w, x, y) != -1) {
1894 retVal = true;
1898 return retVal;
1901 static void test_worm_argh_collision(void)
1903 int i;
1904 int dir;
1905 int collision_count = 0;
1906 rb->lcd_clear_display();
1907 init_worm(&worms[0], 10, 10);
1908 add_growing(&worms[0], 40);
1909 for (dir = 0; dir < 4; dir++) {
1910 set_worm_dir(&worms[0], (EAST + dir) % 4);
1911 for (i = 0; i < 10; i++) {
1912 move_worm(&worms[0]);
1913 draw_worm(&worms[0]);
1917 arghx[0] = 12;
1918 for (arghy[0] = 0; arghy[0] < FIELD_RECT_HEIGHT - argh_size; arghy[0]++){
1919 bool collision;
1920 draw_argh(0);
1921 collision = worm_argh_collision(&worms[0], 0);
1922 if (collision) {
1923 collision_count ++;
1925 rb->lcd_putsxyf(0, LCD_HEIGHT -8, "collisions: %d", collision_count);
1926 rb->lcd_update();
1928 if (collision_count != argh_size * 2) {
1929 rb->button_get(true);
1932 arghy[0] = 12;
1933 for (arghx[0] = 0; arghx[0] < FIELD_RECT_HEIGHT - argh_size; arghx[0]++){
1934 bool collision;
1935 draw_argh(0);
1936 collision = worm_argh_collision(&worms[0], 0);
1937 if (collision) {
1938 collision_count ++;
1940 rb->lcd_putsxyf(0, LCD_HEIGHT -8, "collisions: %d", collision_count);
1941 rb->lcd_update();
1943 if (collision_count != argh_size * 4) {
1944 rb->button_get(true);
1948 static int testline_in_rect(void)
1950 int testfailed = -1;
1952 int rx = 10;
1953 int ry = 15;
1954 int rw = 20;
1955 int rh = 25;
1957 /* Test 1 */
1958 int x1 = 12;
1959 int y1 = 8;
1960 int x2 = 12;
1961 int y2 = 42;
1963 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1964 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1965 rb->lcd_drawrect(rx, ry, rw, rh);
1966 rb->lcd_drawline(x1, y1, x2, y2);
1967 rb->lcd_update();
1968 rb->lcd_putsxy(0, 0, "failed 1");
1969 rb->button_get(true);
1970 testfailed = 1;
1973 /* test 2 */
1974 y2 = 20;
1975 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1976 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1977 rb->lcd_drawrect(rx, ry, rw, rh);
1978 rb->lcd_drawline(x1, y1, x2, y2);
1979 rb->lcd_putsxy(0, 0, "failed 2");
1980 rb->lcd_update();
1981 rb->button_get(true);
1982 testfailed = 2;
1985 /* test 3 */
1986 y1 = 30;
1987 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1988 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1989 rb->lcd_drawrect(rx, ry, rw, rh);
1990 rb->lcd_drawline(x1, y1, x2, y2);
1991 rb->lcd_putsxy(0, 0, "failed 3");
1992 rb->lcd_update();
1993 rb->button_get(true);
1994 testfailed = 3;
1997 /* test 4 */
1998 y2 = 45;
1999 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2000 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2001 rb->lcd_drawrect(rx, ry, rw, rh);
2002 rb->lcd_drawline(x1, y1, x2, y2);
2003 rb->lcd_putsxy(0, 0, "failed 4");
2004 rb->lcd_update();
2005 rb->button_get(true);
2006 testfailed = 4;
2009 /* test 5 */
2010 y1 = 50;
2011 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
2012 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2013 rb->lcd_drawrect(rx, ry, rw, rh);
2014 rb->lcd_drawline(x1, y1, x2, y2);
2015 rb->lcd_putsxy(0, 0, "failed 5");
2016 rb->lcd_update();
2017 rb->button_get(true);
2018 testfailed = 5;
2021 /* test 6 */
2022 y1 = 5;
2023 y2 = 7;
2024 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
2025 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2026 rb->lcd_drawrect(rx, ry, rw, rh);
2027 rb->lcd_drawline(x1, y1, x2, y2);
2028 rb->lcd_putsxy(0, 0, "failed 6");
2029 rb->lcd_update();
2030 rb->button_get(true);
2031 testfailed = 6;
2034 /* test 7 */
2035 x1 = 8;
2036 y1 = 20;
2037 x2 = 35;
2038 y2 = 20;
2039 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2040 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2041 rb->lcd_drawrect(rx, ry, rw, rh);
2042 rb->lcd_drawline(x1, y1, x2, y2);
2043 rb->lcd_putsxy(0, 0, "failed 7");
2044 rb->lcd_update();
2045 rb->button_get(true);
2046 testfailed = 7;
2049 /* test 8 */
2050 x2 = 12;
2051 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2052 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2053 rb->lcd_drawrect(rx, ry, rw, rh);
2054 rb->lcd_drawline(x1, y1, x2, y2);
2055 rb->lcd_putsxy(0, 0, "failed 8");
2056 rb->lcd_update();
2057 rb->button_get(true);
2058 testfailed = 8;
2061 /* test 9 */
2062 x1 = 25;
2063 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2064 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2065 rb->lcd_drawrect(rx, ry, rw, rh);
2066 rb->lcd_drawline(x1, y1, x2, y2);
2067 rb->lcd_putsxy(0, 0, "failed 9");
2068 rb->lcd_update();
2069 rb->button_get(true);
2070 testfailed = 9;
2073 /* test 10 */
2074 x2 = 37;
2075 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2076 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2077 rb->lcd_drawrect(rx, ry, rw, rh);
2078 rb->lcd_drawline(x1, y1, x2, y2);
2079 rb->lcd_putsxy(0, 0, "failed 10");
2080 rb->lcd_update();
2081 rb->button_get(true);
2082 testfailed = 10;
2085 /* test 11 */
2086 x1 = 42;
2087 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
2088 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2089 rb->lcd_drawrect(rx, ry, rw, rh);
2090 rb->lcd_drawline(x1, y1, x2, y2);
2091 rb->lcd_putsxy(0, 0, "failed 11");
2092 rb->lcd_update();
2093 rb->button_get(true);
2094 testfailed = 11;
2097 /* test 12 */
2098 x1 = 5;
2099 x2 = 7;
2100 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
2101 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2102 rb->lcd_drawrect(rx, ry, rw, rh);
2103 rb->lcd_drawline(x1, y1, x2, y2);
2104 rb->lcd_putsxy(0, 0, "failed 12");
2105 rb->lcd_update();
2106 rb->button_get(true);
2107 testfailed = 12;
2110 /* test 13 */
2111 rx = 9;
2112 ry = 15;
2113 rw = food_size;
2114 rh = food_size;
2116 x1 = 10;
2117 y1 = 10;
2118 x2 = 10;
2119 y2 = 20;
2120 if (!(line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2121 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh))) {
2122 rb->lcd_drawrect(rx, ry, rw, rh);
2123 rb->lcd_drawline(x1, y1, x2, y2);
2124 rb->lcd_putsxy(0, 0, "failed 13");
2125 rb->lcd_update();
2126 rb->button_get(true);
2127 testfailed = 13;
2130 /* test 14 */
2131 rx = 9;
2132 ry = 15;
2133 rw = 4;
2134 rh = 4;
2136 x1 = 10;
2137 y1 = 10;
2138 x2 = 10;
2139 y2 = 19;
2140 if (!(line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2141 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh))) {
2142 rb->lcd_drawline(x1, y1, x2, y2);
2143 rb->lcd_invertrect(rx, ry, rw, rh);
2144 rb->lcd_putsxy(0, 0, "failed 14");
2145 rb->lcd_update();
2146 rb->button_get(true);
2147 testfailed = 14;
2150 rb->lcd_clear_display();
2152 return testfailed;
2156 * Just a test routine to test wether specific_worm_collision might work properly
2158 static int test_specific_worm_collision(void)
2160 int collisions = 0;
2161 int dir;
2162 int x = 0;
2163 int y = 0;
2164 rb->lcd_clear_display();
2165 init_worm(&worms[0], 10, 20);
2166 add_growing(&worms[0], 20 - INITIAL_WORM_LENGTH);
2168 for (dir = EAST; dir < EAST + 4; dir++) {
2169 int i;
2170 set_worm_dir(&worms[0], dir % 4);
2171 for (i = 0; i < 5; i++) {
2172 if (!(dir % 4 == NORTH && i == 9)) {
2173 move_worm(&worms[0]);
2174 draw_worm(&worms[0]);
2179 for (y = 15; y < 30; y ++){
2180 for (x = 5; x < 20; x++) {
2181 if (specific_worm_collision(&worms[0], x, y) != -1) {
2182 collisions ++;
2184 rb->lcd_invertpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y);
2185 rb->lcd_putsxyf(0, LCD_HEIGHT - 8, "collisions %d", collisions);
2186 rb->lcd_update();
2189 if (collisions != 21) {
2190 rb->button_get(true);
2192 return collisions;
2195 static void test_make_argh(void)
2197 int dir;
2198 int seed = 0;
2199 int hit = 0;
2200 int failures = 0;
2201 int last_failures = 0;
2202 int i, worm_idx;
2203 rb->lcd_clear_display();
2204 worm_count = 3;
2206 for (worm_idx = 0; worm_idx < worm_count; worm_idx++) {
2207 init_worm(&worms[worm_idx], 10 + worm_idx * 20, 20);
2208 add_growing(&worms[worm_idx], 40 - INITIAL_WORM_LENGTH);
2211 for (dir = EAST; dir < EAST + 4; dir++) {
2212 for (worm_idx = 0; worm_idx < worm_count; worm_idx++) {
2213 set_worm_dir(&worms[worm_idx], dir % 4);
2214 for (i = 0; i < 10; i++) {
2215 if (!(dir % 4 == NORTH && i == 9)) {
2216 move_worm(&worms[worm_idx]);
2217 draw_worm(&worms[worm_idx]);
2223 rb->lcd_update();
2225 for (seed = 0; hit < 20; seed += 2) {
2226 int x, y;
2227 rb->srand(seed);
2228 x = rb->rand() % (FIELD_RECT_WIDTH - argh_size);
2229 y = rb->rand() % (FIELD_RECT_HEIGHT - argh_size);
2231 for (worm_idx = 0; worm_idx < worm_count; worm_idx++){
2232 if (expensive_worm_in_rect(&worms[worm_idx], x, y, argh_size, argh_size)) {
2233 int tries = 0;
2234 rb->srand(seed);
2236 tries = make_argh(0);
2237 if ((x == arghx[0] && y == arghy[0]) || tries < 2) {
2238 failures ++;
2241 rb->lcd_putsxyf(0, LCD_HEIGHT - 8, "(%d;%d) fail%d try%d",
2242 x, y, failures, tries);
2243 rb->lcd_update();
2244 rb->lcd_invertrect(x + FIELD_RECT_X, y+ FIELD_RECT_Y,
2245 argh_size, argh_size);
2246 rb->lcd_update();
2247 draw_argh(0);
2248 rb->lcd_update();
2249 rb->lcd_invertrect(x + FIELD_RECT_X, y + FIELD_RECT_Y,
2250 argh_size, argh_size);
2251 rb->lcd_clearrect(arghx[0] + FIELD_RECT_X, arghy[0] + FIELD_RECT_Y,
2252 argh_size, argh_size);
2254 if (failures > last_failures) {
2255 rb->button_get(true);
2257 last_failures = failures;
2258 hit ++;
2264 static void test_worm_argh_collision_in_moves(void) {
2265 int hit_count = 0;
2266 int i;
2267 rb->lcd_clear_display();
2268 init_worm(&worms[0], 10, 20);
2270 arghx[0] = 20;
2271 arghy[0] = 18;
2272 draw_argh(0);
2274 set_worm_dir(&worms[0], EAST);
2275 for (i = 0; i < 20; i++) {
2276 move_worm(&worms[0]);
2277 draw_worm(&worms[0]);
2278 if (worm_argh_collision_in_moves(&worms[0], 0, 5)){
2279 hit_count ++;
2281 rb->lcd_putsxyf(0, LCD_HEIGHT - 8, "in 5 moves hits: %d", hit_count);
2282 rb->lcd_update();
2284 if (hit_count != argh_size + 5) {
2285 rb->button_get(true);
2288 #endif /* DEBUG_WORMLET */
2291 * Reverts default settings
2293 static void default_settings(void)
2295 arghs_per_food = ARGHS_PER_FOOD;
2296 argh_size = ARGH_SIZE;
2297 food_size = FOOD_SIZE;
2298 speed = SPEED;
2299 worm_food = WORM_PER_FOOD;
2300 players = 1;
2301 worm_count = MAX_WORMS;
2302 use_remote = false;
2303 return;
2307 * Launches the wormlet game
2309 static bool launch_wormlet(void)
2311 int game_result = 1;
2313 rb->lcd_clear_display();
2315 /* Turn off backlight timeout */
2316 backlight_ignore_timeout();
2318 /* start the game */
2319 while (game_result == 1)
2320 game_result = run();
2322 switch (game_result)
2324 case 2:
2325 /* Turn on backlight timeout (revert to settings) */
2326 backlight_use_settings();
2327 return false;
2328 break;
2330 return false;
2334 * Main entry point
2336 enum plugin_status plugin_start(const void* parameter)
2338 int result;
2339 int menu_quit = 0;
2340 int new_setting;
2342 (void)(parameter);
2344 default_settings();
2345 if (configfile_load(SETTINGS_FILENAME, config,
2346 sizeof(config)/sizeof(*config),
2347 SETTINGS_MIN_VERSION ) < 0)
2349 /* If the loading failed, save a new config file (as the disk is
2350 already spinning) */
2351 configfile_save(SETTINGS_FILENAME, config,
2352 sizeof(config)/sizeof(*config),
2353 SETTINGS_VERSION);
2356 #ifdef HAVE_LCD_COLOR
2357 rb->lcd_set_foreground(COLOR_FG);
2358 rb->lcd_set_background(COLOR_BG);
2359 #endif
2361 #if LCD_DEPTH > 1
2362 rb->lcd_set_backdrop(NULL);
2363 #endif
2365 #ifdef DEBUG_WORMLET
2366 testline_in_rect();
2367 test_worm_argh_collision_in_moves();
2368 test_make_argh();
2369 test_worm_food_collision();
2370 test_worm_argh_collision();
2371 test_specific_worm_collision();
2372 #endif
2374 /* Setup screen */
2376 static const struct opt_items noyes[2] = {
2377 { "No", -1 },
2378 { "Yes", -1 },
2381 static const struct opt_items remoteonly_option[1] = {
2382 { "Remote Control", -1 }
2385 static const struct opt_items key24_option[2] = {
2386 { "4 Key Control", -1 },
2387 { "2 Key Control", -1 }
2390 #ifdef REMOTE
2391 static const struct opt_items remote_option[2] = {
2392 { "Remote Control", -1 },
2393 { "No Rem. Control", -1 }
2395 #else
2396 static const struct opt_items key2_option[1] = {
2397 { "2 Key Control", -1 }
2399 #endif
2401 static const struct opt_items nokey_option[1] = {
2402 { "Out of Control", -1 }
2405 MENUITEM_STRINGLIST(menu, "Wormlet Menu", NULL, "Play Wormlet!",
2406 "Number of Worms", "Number of Players", "Control Style",
2407 "Worm Growth Per Food","Worm Speed","Arghs Per Food",
2408 "Argh Size","Food Size","Revert to Default Settings",
2409 "Playback Control", "Quit");
2411 rb->button_clear_queue();
2413 while (!menu_quit) {
2414 switch(rb->do_menu(&menu, &result, NULL, false))
2416 case 0:
2417 rb->lcd_setfont(FONT_SYSFIXED);
2418 launch_wormlet();
2419 break;
2420 case 1:
2421 rb->set_int("Number of Worms", "", UNIT_INT, &worm_count, NULL,
2422 1, 1, 3, NULL);
2423 if (worm_count < players) {
2424 worm_count = players;
2426 break;
2427 case 2:
2428 #ifdef MULTIPLAYER
2429 rb->set_int("Number of Players", "", UNIT_INT, &players, NULL,
2430 1, 0, 4, NULL);
2431 #else
2432 rb->set_int("Number of Players", "", UNIT_INT, &players, NULL,
2433 1, 0, 2, NULL);
2434 #endif
2435 if (players > worm_count) {
2436 worm_count = players;
2438 if (players > 2) {
2439 use_remote = true;
2441 break;
2442 case 3:
2443 switch(players) {
2444 case 0:
2445 rb->set_option("Control Style",&use_remote,INT,
2446 nokey_option, 1, NULL);
2447 break;
2448 case 1:
2449 rb->set_option("Control Style",&use_remote,INT,
2450 key24_option, 2, NULL);
2451 break;
2452 case 2:
2453 #ifdef REMOTE
2454 rb->set_option("Control Style",&use_remote,INT,
2455 remote_option, 2, NULL);
2456 #else
2457 rb->set_option("Control Style",&use_remote,INT,
2458 key2_option, 1, NULL);
2459 #endif
2460 break;
2461 case 3:
2462 rb->set_option("Control Style",&use_remote,INT,
2463 remoteonly_option, 1, NULL);
2464 break;
2466 break;
2467 case 4:
2468 rb->set_int("Worm Growth Per Food", "", UNIT_INT, &worm_food,
2469 NULL, 1, 0, 15, NULL);
2470 break;
2471 case 5:
2472 new_setting = 20 - speed;
2473 rb->set_int("Worm Speed", "", UNIT_INT, &new_setting,
2474 NULL, 1, 0, 20, NULL);
2475 speed = 20 - new_setting;
2476 break;
2477 case 6:
2478 rb->set_int("Arghs Per Food", "", UNIT_INT, &arghs_per_food,
2479 NULL, 1, 0, 8, NULL);
2480 break;
2481 case 7:
2482 rb->set_int("Argh Size", "", UNIT_INT, &argh_size,
2483 NULL, 1, 2, 10, NULL);
2484 break;
2485 case 8:
2486 rb->set_int("Food Size", "", UNIT_INT, &food_size,
2487 NULL, 1, 2, 10, NULL);
2488 break;
2489 case 9:
2490 new_setting = 0;
2491 rb->set_option("Reset Settings?", &new_setting, INT, noyes , 2, NULL);
2492 if (new_setting == 1)
2493 default_settings();
2494 break;
2495 case 10:
2496 playback_control(NULL);
2497 break;
2498 default:
2499 menu_quit=1;
2500 break;
2504 configfile_save(SETTINGS_FILENAME, config,
2505 sizeof(config)/sizeof(*config),
2506 SETTINGS_VERSION);
2508 return PLUGIN_OK;