Fix warning about missing newline at the EOF
[maemo-rb.git] / apps / plugins / wormlet.c
blobf1834aade5ed0ab9b389e1b02417db82fd7d0673
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) || \
184 (CONFIG_KEYPAD == SAMSUNG_YPR0_PAD)
186 #define BTN_DIR_UP BUTTON_UP
187 #define BTN_DIR_DOWN BUTTON_DOWN
188 #define BTN_DIR_LEFT BUTTON_LEFT
189 #define BTN_DIR_RIGHT BUTTON_RIGHT
190 #define BTN_STARTPAUSE BUTTON_SELECT
191 #define BTN_QUIT BUTTON_BACK
192 #define BTN_STOPRESET BUTTON_MENU
194 #elif (CONFIG_KEYPAD == MROBE100_PAD)
196 #define BTN_DIR_UP BUTTON_UP
197 #define BTN_DIR_DOWN BUTTON_DOWN
198 #define BTN_DIR_LEFT BUTTON_LEFT
199 #define BTN_DIR_RIGHT BUTTON_RIGHT
200 #define BTN_STARTPAUSE BUTTON_SELECT
201 #define BTN_QUIT BUTTON_POWER
202 #define BTN_STOPRESET BUTTON_DISPLAY
204 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
206 #define BTN_DIR_UP BUTTON_RC_VOL_UP
207 #define BTN_DIR_DOWN BUTTON_RC_VOL_DOWN
208 #define BTN_DIR_LEFT BUTTON_RC_REW
209 #define BTN_DIR_RIGHT BUTTON_RC_FF
210 #define BTN_STARTPAUSE BUTTON_RC_PLAY
211 #define BTN_QUIT BUTTON_RC_REC
212 #define BTN_STOPRESET BUTTON_RC_MODE
214 #elif (CONFIG_KEYPAD == COWON_D2_PAD)
216 #define BTN_QUIT BUTTON_POWER
218 #elif CONFIG_KEYPAD == CREATIVEZVM_PAD
220 #define BTN_DIR_UP BUTTON_UP
221 #define BTN_DIR_DOWN BUTTON_DOWN
222 #define BTN_DIR_LEFT BUTTON_LEFT
223 #define BTN_DIR_RIGHT BUTTON_RIGHT
224 #define BTN_STARTPAUSE BUTTON_PLAY
225 #define BTN_QUIT BUTTON_BACK
226 #define BTN_STOPRESET BUTTON_MENU
228 #elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
230 #define BTN_DIR_UP BUTTON_UP
231 #define BTN_DIR_DOWN BUTTON_DOWN
232 #define BTN_DIR_LEFT BUTTON_LEFT
233 #define BTN_DIR_RIGHT BUTTON_RIGHT
234 #define BTN_STARTPAUSE BUTTON_MENU
235 #define BTN_QUIT BUTTON_POWER
236 #define BTN_STOPRESET BUTTON_VIEW
238 #elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD
240 #define BTN_DIR_UP BUTTON_UP
241 #define BTN_DIR_DOWN BUTTON_DOWN
242 #define BTN_DIR_LEFT BUTTON_PREV
243 #define BTN_DIR_RIGHT BUTTON_NEXT
244 #define BTN_STARTPAUSE BUTTON_MENU
245 #define BTN_QUIT BUTTON_POWER
246 #define BTN_STOPRESET BUTTON_RIGHT
248 #elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
250 #define BTN_DIR_UP BUTTON_UP
251 #define BTN_DIR_DOWN BUTTON_DOWN
252 #define BTN_DIR_LEFT BUTTON_PREV
253 #define BTN_DIR_RIGHT BUTTON_RIGHT
254 #define BTN_STARTPAUSE BUTTON_MENU
255 #define BTN_QUIT BUTTON_POWER
256 #define BTN_STOPRESET BUTTON_RIGHT
258 #elif (CONFIG_KEYPAD == ONDAVX747_PAD) || \
259 (CONFIG_KEYPAD == ONDAVX777_PAD) || \
260 CONFIG_KEYPAD == MROBE500_PAD
262 #define BTN_QUIT BUTTON_POWER
264 #elif CONFIG_KEYPAD == SAMSUNG_YH_PAD
266 #define BTN_DIR_UP BUTTON_UP
267 #define BTN_DIR_DOWN BUTTON_DOWN
268 #define BTN_DIR_LEFT BUTTON_LEFT
269 #define BTN_DIR_RIGHT BUTTON_RIGHT
270 #define BTN_STARTPAUSE BUTTON_PLAY
271 #define BTN_QUIT BUTTON_FFWD
272 #define BTN_STOPRESET BUTTON_REW
274 #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
276 #define BTN_DIR_UP BUTTON_UP
277 #define BTN_DIR_DOWN BUTTON_DOWN
278 #define BTN_DIR_LEFT BUTTON_PREV
279 #define BTN_DIR_RIGHT BUTTON_NEXT
280 #define BTN_STARTPAUSE BUTTON_PLAY
281 #define BTN_QUIT BUTTON_REC
282 #define BTN_STOPRESET BUTTON_CANCEL
284 #elif CONFIG_KEYPAD == MPIO_HD200_PAD
286 #define BTN_DIR_UP BUTTON_REC
287 #define BTN_DIR_DOWN BUTTON_PLAY
288 #define BTN_DIR_LEFT BUTTON_REW
289 #define BTN_DIR_RIGHT BUTTON_FF
290 #define BTN_STARTPAUSE BUTTON_FUNC
291 #define BTN_QUIT (BUTTON_REC|BUTTON_PLAY)
292 #define BTN_STOPRESET (BUTTON_FUNC|BUTTON_REPEAT)
294 #elif CONFIG_KEYPAD == MPIO_HD300_PAD
296 #define BTN_DIR_UP BUTTON_UP
297 #define BTN_DIR_DOWN BUTTON_DOWN
298 #define BTN_DIR_LEFT BUTTON_MENU
299 #define BTN_DIR_RIGHT BUTTON_ENTER
300 #define BTN_STARTPAUSE BUTTON_PLAY
301 #define BTN_QUIT BUTTON_REC
302 #define BTN_STOPRESET (BUTTON_MENU | BUTTON_REPEAT)
304 #elif CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD
306 #define BTN_DIR_UP BUTTON_UP
307 #define BTN_DIR_DOWN BUTTON_DOWN
308 #define BTN_DIR_LEFT BUTTON_LEFT
309 #define BTN_DIR_RIGHT BUTTON_RIGHT
310 #define BTN_STARTPAUSE BUTTON_PLAYPAUSE
311 #define BTN_QUIT BUTTON_POWER
312 #define BTN_STOPRESET BUTTON_BACK
314 #elif CONFIG_KEYPAD == SANSA_CONNECT_PAD
316 #define BTN_DIR_UP BUTTON_UP
317 #define BTN_DIR_DOWN BUTTON_DOWN
318 #define BTN_DIR_LEFT BUTTON_LEFT
319 #define BTN_DIR_RIGHT BUTTON_RIGHT
320 #define BTN_STARTPAUSE BUTTON_SELECT
321 #define BTN_QUIT BUTTON_POWER
322 #define BTN_STOPRESET BUTTON_VOL_DOWN
324 #else
325 #error No keymap defined!
326 #endif
328 #ifdef HAVE_TOUCHSCREEN
329 #ifndef BTN_DIR_UP
330 #define BTN_DIR_UP BUTTON_TOPMIDDLE
331 #endif
332 #ifndef BTN_DIR_DOWN
333 #define BTN_DIR_DOWN BUTTON_BOTTOMMIDDLE
334 #endif
335 #ifndef BTN_DIR_LEFT
336 #define BTN_DIR_LEFT BUTTON_MIDLEFT
337 #endif
338 #ifndef BTN_DIR_RIGHT
339 #define BTN_DIR_RIGHT BUTTON_MIDRIGHT
340 #endif
341 #ifndef BTN_STARTPAUSE
342 #define BTN_STARTPAUSE BUTTON_CENTER
343 #endif
344 #ifndef BTN_QUIT
345 #define BTN_QUIT BUTTON_TOPLEFT
346 #endif
347 #ifndef BTN_STOPRESET
348 #define BTN_STOPRESET BUTTON_TOPRIGHT
349 #endif
350 #endif
352 #if (LCD_WIDTH == 96) && (LCD_HEIGHT == 96)
353 #define FOOD_SIZE 3
354 #define ARGH_SIZE 4
355 #define SPEED 14
356 #define MAX_WORM_SEGMENTS 128
357 #elif (LCD_WIDTH == 112) && (LCD_HEIGHT == 64)
358 #define FOOD_SIZE 3
359 #define ARGH_SIZE 4
360 #define SPEED 14
361 #define MAX_WORM_SEGMENTS 128
362 #elif (LCD_WIDTH == 128) && (LCD_HEIGHT == 64)
363 #define FOOD_SIZE 3
364 #define ARGH_SIZE 4
365 #define SPEED 14
366 #define MAX_WORM_SEGMENTS 128
367 #elif (LCD_WIDTH == 132) && (LCD_HEIGHT == 80)
368 #define FOOD_SIZE 3
369 #define ARGH_SIZE 4
370 #define SPEED 14
371 #define MAX_WORM_SEGMENTS 128
372 #elif (LCD_WIDTH == 128) && (LCD_HEIGHT == 96)
373 #define FOOD_SIZE 3
374 #define ARGH_SIZE 4
375 #define SPEED 12
376 #define MAX_WORM_SEGMENTS 128
377 #elif (LCD_WIDTH == 138) && (LCD_HEIGHT == 110)
378 #define FOOD_SIZE 4
379 #define ARGH_SIZE 5
380 #define SPEED 10
381 #define MAX_WORM_SEGMENTS 128
382 #elif (LCD_WIDTH == 128) && (LCD_HEIGHT == 128)
383 #define FOOD_SIZE 4
384 #define ARGH_SIZE 5
385 #define SPEED 9
386 #define MAX_WORM_SEGMENTS 128
387 #elif ((LCD_WIDTH == 160) && (LCD_HEIGHT == 128)) || \
388 ((LCD_WIDTH == 128) && (LCD_HEIGHT == 160))
389 #define FOOD_SIZE 4
390 #define ARGH_SIZE 5
391 #define SPEED 8
392 #define MAX_WORM_SEGMENTS 256
393 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
394 #define FOOD_SIZE 4
395 #define ARGH_SIZE 5
396 #define SPEED 6
397 #define MAX_WORM_SEGMENTS 256
398 #elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
399 #define FOOD_SIZE 5
400 #define ARGH_SIZE 6
401 #define SPEED 4
402 #define MAX_WORM_SEGMENTS 512
403 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
404 #define FOOD_SIZE 5
405 #define ARGH_SIZE 6
406 #define SPEED 4
407 #define MAX_WORM_SEGMENTS 512
408 #elif ((LCD_WIDTH == 320) && (LCD_HEIGHT == 240)) || \
409 ((LCD_WIDTH == 240) && ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400)))
410 #define FOOD_SIZE 7
411 #define ARGH_SIZE 8
412 #define SPEED 4
413 #define MAX_WORM_SEGMENTS 512
414 #elif ((LCD_WIDTH == 640) && (LCD_HEIGHT == 480)) || \
415 ((LCD_WIDTH == 480) && (LCD_HEIGHT == 640))
416 #define FOOD_SIZE 14
417 #define ARGH_SIZE 16
418 #define SPEED 4
419 #define MAX_WORM_SEGMENTS 512
420 #endif
422 #ifdef HAVE_LCD_COLOR
423 #define COLOR_WORM LCD_RGBPACK(80, 40, 0)
424 #define COLOR_ARGH LCD_RGBPACK(175, 0, 0)
425 #define COLOR_FOOD LCD_RGBPACK(0, 150, 0)
426 #define COLOR_FG LCD_RGBPACK(0, 0, 0)
427 #define COLOR_BG LCD_RGBPACK(181, 199, 231)
428 #endif
430 #define CHECK_SQUARE_COLLISION(x1,y1,s1,x2,y2,s2) (x1+s1>x2)&&(x2+s2>x1)&&(y1+s1>y2)&&(y2+s2>y1)
433 * All the properties that a worm has.
435 static struct worm {
436 /* The worm is stored in a ring of xy coordinates */
437 int x[MAX_WORM_SEGMENTS];
438 int y[MAX_WORM_SEGMENTS];
440 int head; /* index of the head within the buffer */
441 int tail; /* index of the tail within the buffer */
442 int growing; /* number of cyles the worm still keeps growing */
443 bool alive; /* the worms living state */
445 /* direction vector in which the worm moves */
446 int dirx; /* only values -1 0 1 allowed */
447 int diry; /* only values -1 0 1 allowed */
449 /* this method is used to fetch the direction the user
450 has selected. It can be one of the values
451 human_player1, human_player2, remote_player, virtual_player.
452 All these values are fuctions, that can change the direction
453 of the worm */
454 void (*fetch_worm_direction)(struct worm *w);
455 } worms[MAX_WORMS];
457 /* stores the highscore - besides it was scored by a virtual player */
458 static int highscore;
460 #define MAX_FOOD 5 /* maximal number of food items */
462 /* The arrays store the food coordinates */
463 static int foodx[MAX_FOOD];
464 static int foody[MAX_FOOD];
466 #define MAX_ARGH 100 /* maximal number of argh items */
467 #define ARGHS_PER_FOOD 2 /* number of arghs produced per eaten food */
469 /* The arrays store the argh coordinates */
470 static int arghx[MAX_ARGH];
471 static int arghy[MAX_ARGH];
473 /* the number of arghs that are currently in use */
474 static int argh_count;
476 /* the number of arghs per food, settable by user */
477 static int arghs_per_food = ARGHS_PER_FOOD;
478 /* the size of the argh, settable by user */
479 static int argh_size = ARGH_SIZE;
480 /* the size of the food, settable by user */
481 static int food_size = FOOD_SIZE;
482 /* the speed of the worm, settable by user */
483 static int speed = SPEED;
484 /* the amount a worm grows by eating a food, settable by user */
485 static int worm_food = WORM_PER_FOOD;
487 /* End additional variables */
489 /* the number of active worms (dead or alive) */
490 static int worm_count = MAX_WORMS;
492 /* in multiplayer mode: en- / disables the remote worm control
493 in singleplayer mode: toggles 4 / 2 button worm control */
494 static bool use_remote = false;
496 /* return values of check_collision */
497 #define COLLISION_NONE 0
498 #define COLLISION_WORM 1
499 #define COLLISION_FOOD 2
500 #define COLLISION_ARGH 3
501 #define COLLISION_FIELD 4
503 static const char *const state_desc[] = {
504 [COLLISION_NONE] = NULL,
505 [COLLISION_WORM] = "Wormed",
506 [COLLISION_FOOD] = "Growing",
507 [COLLISION_ARGH] = "Argh",
508 [COLLISION_FIELD] = "Crashed",
511 /* constants for use as directions.
512 Note that the values are ordered clockwise.
513 Thus increasing / decreasing the values
514 is equivalent to right / left turns. */
515 #define WEST 0
516 #define NORTH 1
517 #define EAST 2
518 #define SOUTH 3
520 /* direction of human player 1 */
521 static int player1_dir = EAST;
522 /* direction of human player 2 */
523 static int player2_dir = EAST;
524 /* direction of human player 3 */
525 static int player3_dir = EAST;
527 /* the number of (human) players that currently
528 control a worm */
529 static int players = 1;
531 #define SETTINGS_VERSION 1
532 #define SETTINGS_MIN_VERSION 1
533 #define SETTINGS_FILENAME "wormlet.cfg"
535 static struct configdata config[] =
537 {TYPE_INT, 0, 1024, { .int_p = &highscore }, "highscore", NULL},
538 {TYPE_INT, 0, 15, { .int_p = &arghs_per_food }, "arghs per food", NULL},
539 {TYPE_INT, 0, 15, { .int_p = &argh_size }, "argh size", NULL},
540 {TYPE_INT, 0, 15, { .int_p = &food_size }, "food size", NULL},
541 {TYPE_INT, 0, 3, { .int_p = &players }, "players", NULL},
542 {TYPE_INT, 0, 3, { .int_p = &worm_count }, "worms", NULL},
543 {TYPE_INT, 0, 20, { .int_p = &speed }, "speed", NULL},
544 {TYPE_INT, 0, 15, { .int_p = &worm_food }, "Worm Growth Per Food", NULL}
548 * Returns the direction id in which the worm
549 * currently is creeping.
550 * @param struct worm *w The worm that is to be investigated.
551 * w Must not be null.
552 * @return int A value 0 <= value < 4
553 * Note the predefined constants NORTH, SOUTH, EAST, WEST
555 static int get_worm_dir(struct worm *w)
557 int retVal ;
558 if (w->dirx == 0) {
559 if (w->diry == 1) {
560 retVal = SOUTH;
561 } else {
562 retVal = NORTH;
564 } else {
565 if (w->dirx == 1) {
566 retVal = EAST;
567 } else {
568 retVal = WEST;
571 return retVal;
575 * Set the direction of the specified worm with a direction id.
576 * Increasing the value by 1 means to turn the worm direction
577 * to right by 90 degree.
578 * @param struct worm *w The worm that is to be altered. w Must not be null.
579 * @param int dir The new direction in which the worm is to creep.
580 * dir must be 0 <= dir < 4. Use predefined constants
581 * NORTH, SOUTH, EAST, WEST
583 static void set_worm_dir(struct worm *w, int dir)
585 switch (dir) {
586 case WEST:
587 w->dirx = -1;
588 w->diry = 0;
589 break;
590 case NORTH:
591 w->dirx = 0;
592 w->diry = - 1;
593 break;
594 case EAST:
595 w->dirx = 1;
596 w->diry = 0;
597 break;
598 case SOUTH:
599 w->dirx = 0;
600 w->diry = 1;
601 break;
606 * Returns the current length of the worm array. This
607 * is also a value for the number of bends that are in the worm.
608 * @return int a positive value with 0 <= value < MAX_WORM_SEGMENTS
610 static int get_worm_array_length(struct worm *w)
612 /* initial simple calculation will be overwritten if wrong. */
613 int retVal = w->head - w->tail;
615 /* if the worm 'crosses' the boundaries of the ringbuffer */
616 if (retVal < 0) {
617 retVal = w->head + MAX_WORM_SEGMENTS - w->tail;
620 return retVal;
624 * Returns the score the specified worm. The score is the length
625 * of the worm.
626 * @param struct worm *w The worm that is to be investigated.
627 * w must not be null.
628 * @return int The length of the worm (>= 0).
630 static int get_score(struct worm *w)
632 int retval = 0;
633 int length = get_worm_array_length(w);
634 int i;
635 for (i = 0; i < length; i++) {
637 /* The iteration iterates the length of the worm.
638 Here's the conversion to the true indices within the worm arrays. */
639 int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS;
640 int lineend = (linestart + 1) % MAX_WORM_SEGMENTS;
641 int startx = w->x[linestart];
642 int starty = w->y[linestart];
643 int endx = w->x[lineend];
644 int endy = w->y[lineend];
646 int minimum, maximum;
648 if (startx == endx) {
649 minimum = MIN(starty, endy);
650 maximum = MAX(starty, endy);
651 } else {
652 minimum = MIN(startx, endx);
653 maximum = MAX(startx, endx);
655 retval += abs(maximum - minimum);
657 return retval;
661 * Determines wether the line specified by startx, starty, endx, endy intersects
662 * the rectangle specified by x, y, width, height. Note that the line must be exactly
663 * horizontal or vertical (startx == endx or starty == endy).
664 * @param int startx The x coordinate of the start point of the line.
665 * @param int starty The y coordinate of the start point of the line.
666 * @param int endx The x coordinate of the end point of the line.
667 * @param int endy The y coordinate of the end point of the line.
668 * @param int x The x coordinate of the top left corner of the rectangle.
669 * @param int y The y coordinate of the top left corner of the rectangle.
670 * @param int width The width of the rectangle.
671 * @param int height The height of the rectangle.
672 * @return bool Returns true if the specified line intersects with the recangle.
674 static bool line_in_rect(int startx, int starty, int endx, int endy,
675 int x, int y, int width, int height)
677 bool retval = false;
678 int simple, simplemin, simplemax;
679 int compa, compb, compmin, compmax;
680 int temp;
681 if (startx == endx) {
682 simple = startx;
683 simplemin = x;
684 simplemax = x + width;
686 compa = starty;
687 compb = endy;
688 compmin = y;
689 compmax = y + height;
690 } else {
691 simple = starty;
692 simplemin = y;
693 simplemax = y + height;
695 compa = startx;
696 compb = endx;
697 compmin = x;
698 compmax = x + width;
701 temp = compa;
702 compa = MIN(compa, compb);
703 compb = MAX(temp, compb);
705 if (simplemin <= simple && simple <= simplemax) {
706 if ((compmin <= compa && compa <= compmax) ||
707 (compmin <= compb && compb <= compmax) ||
708 (compa <= compmin && compb >= compmax)) {
709 retval = true;
712 return retval;
716 * Tests wether the specified worm intersects with the rect.
717 * @param struct worm *w The worm to be investigated
718 * @param int x The x coordinate of the top left corner of the rect
719 * @param int y The y coordinate of the top left corner of the rect
720 * @param int widht The width of the rect
721 * @param int height The height of the rect
722 * @return bool Returns true if the worm intersects with the rect
724 static bool worm_in_rect(struct worm *w, int x, int y, int width, int height)
726 bool retval = false;
729 /* get_worm_array_length is expensive -> buffer the value */
730 int wormLength = get_worm_array_length(w);
731 int i;
733 /* test each entry that is part of the worm */
734 for (i = 0; i < wormLength && retval == false; i++) {
736 /* The iteration iterates the length of the worm.
737 Here's the conversion to the true indices within the worm arrays. */
738 int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS;
739 int lineend = (linestart + 1) % MAX_WORM_SEGMENTS;
740 int startx = w->x[linestart];
741 int starty = w->y[linestart];
742 int endx = w->x[lineend];
743 int endy = w->y[lineend];
745 retval = line_in_rect(startx, starty, endx, endy, x, y, width, height);
748 return retval;
752 * Checks wether a specific food in the food arrays is at the
753 * specified coordinates.
754 * @param int foodIndex The index of the food in the food arrays
755 * @param int x the x coordinate.
756 * @param int y the y coordinate.
757 * @return Returns true if the coordinate hits the food specified by
758 * foodIndex.
760 static bool specific_food_collision(int foodIndex, int x, int y)
762 bool retVal = false;
763 if (x >= foodx[foodIndex] &&
764 x < foodx[foodIndex] + food_size &&
765 y >= foody[foodIndex] &&
766 y < foody[foodIndex] + food_size) {
768 retVal = true;
770 return retVal;
774 * Returns the index of the food that is at the
775 * given coordinates. If no food is at the coordinates
776 * -1 is returned.
777 * @return int -1 <= value < MAX_FOOD
779 static int food_collision(int x, int y)
781 int i = 0;
782 int retVal = -1;
783 for (i = 0; i < MAX_FOOD; i++) {
784 if (specific_food_collision(i, x, y)) {
785 retVal = i;
786 break;
789 return retVal;
793 * Checks wether a specific argh in the argh arrays is at the
794 * specified coordinates.
795 * @param int arghIndex The index of the argh in the argh arrays
796 * @param int x the x coordinate.
797 * @param int y the y coordinate.
798 * @return Returns true if the coordinate hits the argh specified by
799 * arghIndex.
801 static bool specific_argh_collision(int arghIndex, int x, int y)
803 if ( x >= arghx[arghIndex] &&
804 y >= arghy[arghIndex] &&
805 x < arghx[arghIndex] + argh_size &&
806 y < arghy[arghIndex] + argh_size )
808 return true;
811 return false;
815 * Returns the index of the argh that is at the
816 * given coordinates. If no argh is at the coordinates
817 * -1 is returned.
818 * @param int x The x coordinate.
819 * @param int y The y coordinate.
820 * @return int -1 <= value < argh_count <= MAX_ARGH
822 static int argh_collision(int x, int y)
824 int i = 0;
825 int retVal = -1;
827 /* search for the argh that has the specified coords */
828 for (i = 0; i < argh_count; i++) {
829 if (specific_argh_collision(i, x, y)) {
830 retVal = i;
831 break;
834 return retVal;
838 * Checks wether the worm collides with the food at the specfied food-arrays.
839 * @param int foodIndex The index of the food in the arrays. Ensure the value is
840 * 0 <= foodIndex <= MAX_FOOD
841 * @return Returns true if the worm collides with the specified food.
843 static bool worm_food_collision(struct worm *w, int foodIndex)
845 bool retVal = false;
847 retVal = worm_in_rect(w, foodx[foodIndex], foody[foodIndex],
848 food_size - 1, food_size - 1);
850 return retVal;
854 * Returns true if the worm hits the argh within the next moves (unless
855 * the worm changes it's direction).
856 * @param struct worm *w - The worm to investigate
857 * @param int argh_idx - The index of the argh
858 * @param int moves - The number of moves that are considered.
859 * @return Returns false if the specified argh is not hit within the next
860 * moves.
862 static bool worm_argh_collision_in_moves(struct worm *w, int argh_idx, int moves)
864 bool retVal = false;
865 int x1, y1, x2, y2;
866 x1 = w->x[w->head];
867 y1 = w->y[w->head];
869 x2 = w->x[w->head] + moves * w->dirx;
870 y2 = w->y[w->head] + moves * w->diry;
872 retVal = line_in_rect(x1, y1, x2, y2, arghx[argh_idx], arghy[argh_idx],
873 argh_size, argh_size);
874 return retVal;
878 * Checks wether the worm collides with the argh at the specfied argh-arrays.
879 * @param int arghIndex The index of the argh in the arrays.
880 * Ensure the value is 0 <= arghIndex < argh_count <= MAX_ARGH
881 * @return Returns true if the worm collides with the specified argh.
883 static bool worm_argh_collision(struct worm *w, int arghIndex)
885 bool retVal = false;
887 retVal = worm_in_rect(w, arghx[arghIndex], arghy[arghIndex],
888 argh_size - 1, argh_size - 1);
890 return retVal;
894 * Find new coordinates for the food stored in foodx[index], foody[index]
895 * that don't collide with any other food or argh
896 * @param int index
897 * Ensure that 0 <= index < MAX_FOOD.
899 static void make_food(int index)
901 int x = 0;
902 int y = 0;
903 bool collisionDetected = false;
904 int i;
906 do {
907 /* make coordinates for a new food so that
908 the entire food lies within the FIELD */
909 x = rb->rand() % (FIELD_RECT_WIDTH - food_size);
910 y = rb->rand() % (FIELD_RECT_HEIGHT - food_size);
911 collisionDetected = false;
912 /* Ensure that the new food doesn't collide with any
913 existing foods or arghs.
914 If the new food hit any existing
915 argh or food a collision is detected.
918 for (i=0; i<MAX_FOOD && !collisionDetected; i++) {
919 collisionDetected = CHECK_SQUARE_COLLISION(x,y,food_size,foodx[i],foody[i],food_size);
921 for (i=0; i<argh_count && !collisionDetected; i++) {
922 collisionDetected = CHECK_SQUARE_COLLISION(x,y,food_size,arghx[i],arghy[i],argh_size);
925 /* use coordinates for further testing */
926 foodx[index] = x;
927 foody[index] = y;
929 /* now test wether we accidently hit the worm with food ;) */
930 i = 0;
931 for (i = 0; i < worm_count && !collisionDetected; i++) {
933 collisionDetected = worm_food_collision(&worms[i], index);
937 while (collisionDetected);
938 return;
942 * Clears a food from the lcd buffer.
943 * @param int index The index of the food arrays under which
944 * the coordinates of the desired food can be found. Ensure
945 * that the value is 0 <= index <= MAX_FOOD.
947 static void clear_food(int index)
949 /* remove the old food from the screen */
950 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
951 rb->lcd_fillrect(foodx[index] + FIELD_RECT_X,
952 foody[index] + FIELD_RECT_Y,
953 food_size, food_size);
954 rb->lcd_set_drawmode(DRMODE_SOLID);
958 * Draws a food in the lcd buffer.
959 * @param int index The index of the food arrays under which
960 * the coordinates of the desired food can be found. Ensure
961 * that the value is 0 <= index <= MAX_FOOD.
963 static void draw_food(int index)
965 /* draw the food object */
966 #ifdef HAVE_LCD_COLOR
967 rb->lcd_set_foreground(COLOR_FOOD);
968 #endif
969 rb->lcd_fillrect(foodx[index] + FIELD_RECT_X,
970 foody[index] + FIELD_RECT_Y,
971 food_size, food_size);
972 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
973 rb->lcd_fillrect(foodx[index] + FIELD_RECT_X + 1,
974 foody[index] + FIELD_RECT_Y + 1,
975 food_size - 2, food_size - 2);
976 rb->lcd_set_drawmode(DRMODE_SOLID);
977 #ifdef HAVE_LCD_COLOR
978 rb->lcd_set_foreground(COLOR_FG);
979 #endif
983 * Find new coordinates for the argh stored in arghx[index], arghy[index]
984 * that don't collide with any other food or argh.
985 * @param int index
986 * Ensure that 0 <= index < argh_count < MAX_ARGH.
988 static void make_argh(int index)
990 int x = -1;
991 int y = -1;
992 bool collisionDetected = false;
993 int i;
995 do {
996 /* make coordinates for a new argh so that
997 the entire food lies within the FIELD */
998 x = rb->rand() % (FIELD_RECT_WIDTH - argh_size);
999 y = rb->rand() % (FIELD_RECT_HEIGHT - argh_size);
1000 collisionDetected = false;
1001 /* Ensure that the new argh doesn't intersect with any
1002 existing foods or arghs.
1003 If the new argh hit any existing
1004 argh or food an intersection is detected.
1007 for (i=0; i<MAX_FOOD && !collisionDetected; i++) {
1008 collisionDetected = CHECK_SQUARE_COLLISION(x,y,argh_size,foodx[i],foody[i],food_size);
1010 for (i=0; i<argh_count && !collisionDetected; i++) {
1011 collisionDetected = CHECK_SQUARE_COLLISION(x,y,argh_size,arghx[i],arghy[i],argh_size);
1014 /* use the candidate coordinates to make a real argh */
1015 arghx[index] = x;
1016 arghy[index] = y;
1018 /* now test wether we accidently hit the worm with argh ;) */
1019 for (i = 0; i < worm_count && !collisionDetected; i++) {
1020 collisionDetected |= worm_argh_collision(&worms[i], index);
1021 collisionDetected |= worm_argh_collision_in_moves(&worms[i], index,
1022 MIN_ARGH_DIST);
1025 while (collisionDetected);
1026 return;
1030 * Draws an argh in the lcd buffer.
1031 * @param int index The index of the argh arrays under which
1032 * the coordinates of the desired argh can be found. Ensure
1033 * that the value is 0 <= index < argh_count <= MAX_ARGH.
1035 static void draw_argh(int index)
1037 /* draw the new argh */
1038 #ifdef HAVE_LCD_COLOR
1039 rb->lcd_set_foreground(COLOR_ARGH);
1040 #endif
1041 rb->lcd_fillrect(arghx[index] + FIELD_RECT_X,
1042 arghy[index] + FIELD_RECT_Y,
1043 argh_size, argh_size);
1044 #ifdef HAVE_LCD_COLOR
1045 rb->lcd_set_foreground(COLOR_FG);
1046 #endif
1049 static void virtual_player(struct worm *w);
1051 * Initialzes the specified worm with INITIAL_WORM_LENGTH
1052 * and the tail at the specified position. The worm will
1053 * be initialized alive and creeping EAST.
1054 * @param struct worm *w The worm that is to be initialized
1055 * @param int x The x coordinate at which the tail of the worm starts.
1056 * x must be 0 <= x < FIELD_RECT_WIDTH.
1057 * @param int y The y coordinate at which the tail of the worm starts
1058 * y must be 0 <= y < FIELD_RECT_WIDTH.
1060 static void init_worm(struct worm *w, int x, int y)
1062 /* initialize the worm size */
1063 w->head = 1;
1064 w->tail = 0;
1066 w->x[w->head] = x + 1;
1067 w->y[w->head] = y;
1069 w->x[w->tail] = x;
1070 w->y[w->tail] = y;
1072 /* set the initial direction the worm creeps to */
1073 w->dirx = 1;
1074 w->diry = 0;
1076 w->growing = INITIAL_WORM_LENGTH - 1;
1077 w->alive = true;
1078 w->fetch_worm_direction = virtual_player;
1082 * Writes the direction that was stored for
1083 * human player 1 into the specified worm. This function
1084 * may be used to be stored in worm.fetch_worm_direction.
1085 * The value of
1086 * the direction is read from player1_dir.
1087 * @param struct worm *w - The worm of which the direction
1088 * is altered.
1090 static void human_player1(struct worm *w) {
1091 set_worm_dir(w, player1_dir);
1095 * Writes the direction that was stored for
1096 * human player 2 into the specified worm. This function
1097 * may be used to be stored in worm.fetch_worm_direction.
1098 * The value of
1099 * the direction is read from player2_dir.
1100 * @param struct worm *w - The worm of which the direction
1101 * is altered.
1103 static void human_player2(struct worm *w) {
1104 set_worm_dir(w, player2_dir);
1108 * Writes the direction that was stored for
1109 * human player using a remote control
1110 * into the specified worm. This function
1111 * may be used to be stored in worm.fetch_worm_direction.
1112 * The value of
1113 * the direction is read from player3_dir.
1114 * @param struct worm *w - The worm of which the direction
1115 * is altered.
1117 static void remote_player(struct worm *w) {
1118 set_worm_dir(w, player3_dir);
1122 * Initializes the worm-, food- and argh-arrays, draws a frame,
1123 * makes some food and argh and display all that stuff.
1125 static void init_wormlet(void)
1127 int i;
1129 for (i = 0; i< worm_count; i++) {
1130 /* Initialize all the worm coordinates to center. */
1131 int x = (int)(FIELD_RECT_WIDTH / 2);
1132 int y = (int)((FIELD_RECT_HEIGHT - 20)/ 2) + i * 10;
1134 init_worm(&worms[i], x, y);
1137 player1_dir = EAST;
1138 player2_dir = EAST;
1139 player3_dir = EAST;
1141 if (players > 0) {
1142 worms[0].fetch_worm_direction = human_player1;
1145 if (players > 1) {
1146 if (use_remote) {
1147 worms[1].fetch_worm_direction = remote_player;
1148 } else {
1149 worms[1].fetch_worm_direction = human_player2;
1153 if (players > 2) {
1154 worms[2].fetch_worm_direction = human_player2;
1157 /* Needed when the game is restarted using BTN_STOPRESET */
1158 rb->lcd_clear_display();
1160 /* make and display some food and argh */
1161 argh_count = MAX_FOOD;
1162 for (i = 0; i < MAX_FOOD; i++) {
1163 make_food(i);
1164 draw_food(i);
1165 make_argh(i);
1166 draw_argh(i);
1169 /* draw the game field */
1170 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
1171 rb->lcd_fillrect(0, 0, FIELD_RECT_WIDTH + 2, FIELD_RECT_HEIGHT + 2);
1172 rb->lcd_fillrect(1, 1, FIELD_RECT_WIDTH, FIELD_RECT_HEIGHT);
1173 rb->lcd_set_drawmode(DRMODE_SOLID);
1175 /* make everything visible */
1176 rb->lcd_update();
1181 * Move the worm one step further if it is alive.
1182 * The direction in which the worm moves is taken from dirx and diry.
1183 * move_worm decreases growing if > 0. While the worm is growing the tail
1184 * is left untouched.
1185 * @param struct worm *w The worm to move. w must not be NULL.
1187 static void move_worm(struct worm *w)
1189 if (w->alive) {
1190 /* determine the head point and its precessor */
1191 int headx = w->x[w->head];
1192 int heady = w->y[w->head];
1193 int prehead = (w->head + MAX_WORM_SEGMENTS - 1) % MAX_WORM_SEGMENTS;
1194 int preheadx = w->x[prehead];
1195 int preheady = w->y[prehead];
1197 /* determine the old direction */
1198 int olddirx;
1199 int olddiry;
1200 if (headx == preheadx) {
1201 olddirx = 0;
1202 olddiry = (heady > preheady) ? 1 : -1;
1203 } else {
1204 olddiry = 0;
1205 olddirx = (headx > preheadx) ? 1 : -1;
1208 /* olddir == dir?
1209 a change of direction means a new segment
1210 has been opened */
1211 if (olddirx != w->dirx ||
1212 olddiry != w->diry) {
1213 w->head = (w->head + 1) % MAX_WORM_SEGMENTS;
1216 /* new head position */
1217 w->x[w->head] = headx + w->dirx;
1218 w->y[w->head] = heady + w->diry;
1221 /* while the worm is growing no tail procession is necessary */
1222 if (w->growing > 0) {
1223 /* update the worms grow state */
1224 w->growing--;
1227 /* if the worm isn't growing the tail has to be dragged */
1228 else {
1229 /* index of the end of the tail segment */
1230 int tail_segment_end = (w->tail + 1) % MAX_WORM_SEGMENTS;
1232 /* drag the end of the tail */
1233 /* only one coordinate has to be altered. Here it is
1234 determined which one */
1235 int dir = 0; /* specifies wether the coord has to be in- or decreased */
1236 if (w->x[w->tail] == w->x[tail_segment_end]) {
1237 dir = (w->y[w->tail] - w->y[tail_segment_end] < 0) ? 1 : -1;
1238 w->y[w->tail] += dir;
1239 } else {
1240 dir = (w->x[w->tail] - w->x[tail_segment_end] < 0) ? 1 : -1;
1241 w->x[w->tail] += dir;
1244 /* when the tail has been dragged so far that it meets
1245 the next segment start the tail segment is obsolete and
1246 must be freed */
1247 if (w->x[w->tail] == w->x[tail_segment_end] &&
1248 w->y[w->tail] == w->y[tail_segment_end]){
1250 /* drop the last tail point */
1251 w->tail = tail_segment_end;
1258 * Draws the head and clears the tail of the worm in
1259 * the display buffer. lcd_update() is NOT called thus
1260 * the caller has to take care that the buffer is displayed.
1262 static void draw_worm(struct worm *w)
1264 /* draw the new head */
1265 int x = w->x[w->head];
1266 int y = w->y[w->head];
1267 #ifdef HAVE_LCD_COLOR
1268 rb->lcd_set_foreground(COLOR_WORM);
1269 #endif
1270 if (x >= 0 && x < FIELD_RECT_WIDTH && y >= 0 && y < FIELD_RECT_HEIGHT) {
1271 rb->lcd_drawpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y);
1274 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1276 /* clear the space behind the worm */
1277 x = w->x[w->tail] ;
1278 y = w->y[w->tail] ;
1279 if (x >= 0 && x < FIELD_RECT_WIDTH && y >= 0 && y < FIELD_RECT_HEIGHT) {
1280 rb->lcd_drawpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y);
1282 rb->lcd_set_drawmode(DRMODE_SOLID);
1283 #ifdef HAVE_LCD_COLOR
1284 rb->lcd_set_foreground(COLOR_FG);
1285 #endif
1289 * Checks wether the coordinate is part of the worm. Returns
1290 * true if any part of the worm was hit - including the head.
1291 * @param x int The x coordinate
1292 * @param y int The y coordinate
1293 * @return int The index of the worm arrays that contain x, y.
1294 * Returns -1 if the coordinates are not part of the worm.
1296 static int specific_worm_collision(struct worm *w, int x, int y)
1298 int retVal = -1;
1300 /* get_worm_array_length is expensive -> buffer the value */
1301 int wormLength = get_worm_array_length(w);
1302 int i;
1304 /* test each entry that is part of the worm */
1305 for (i = 0; i < wormLength && retVal == -1; i++) {
1307 /* The iteration iterates the length of the worm.
1308 Here's the conversion to the true indices within the worm arrays. */
1309 int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS;
1310 int lineend = (linestart + 1) % MAX_WORM_SEGMENTS;
1311 bool samex = (w->x[linestart] == x) && (w->x[lineend] == x);
1312 bool samey = (w->y[linestart] == y) && (w->y[lineend] == y);
1313 if (samex || samey){
1314 int test, min, max, tmp;
1316 if (samey) {
1317 min = w->x[linestart];
1318 max = w->x[lineend];
1319 test = x;
1320 } else {
1321 min = w->y[linestart];
1322 max = w->y[lineend];
1323 test = y;
1326 tmp = min;
1327 min = MIN(min, max);
1328 max = MAX(tmp, max);
1330 if (min <= test && test <= max) {
1331 retVal = lineend;
1335 return retVal;
1339 * Increases the length of the specified worm by marking
1340 * that it may grow by len pixels. Note that the worm has
1341 * to move to make the growing happen.
1342 * @param worm *w The worm that is to be altered.
1343 * @param int len A positive value specifying the amount of
1344 * pixels the worm may grow.
1346 static void add_growing(struct worm *w, int len) {
1347 w->growing += len;
1351 * Determins the worm that is at the coordinates x, y. The parameter
1352 * w is a switch parameter that changes the functionality of worm_collision.
1353 * If w is specified and x,y hits the head of w NULL is returned.
1354 * This is a useful way to determine wether the head of w hits
1355 * any worm but including itself but excluding its own head.
1356 * (It hits always its own head ;))
1357 * If w is set to NULL worm_collision returns any worm including all heads
1358 * that is at position of x,y.
1359 * @param struct worm *w The worm of which the head should be excluded in
1360 * the test. w may be set to NULL.
1361 * @param int x The x coordinate that is checked
1362 * @param int y The y coordinate that is checkec
1363 * @return struct worm* The worm that has been hit by x,y. If no worm
1364 * was at the position NULL is returned.
1366 static struct worm* worm_collision(struct worm *w, int x, int y)
1368 struct worm *retVal = NULL;
1369 int i;
1370 for (i = 0; (i < worm_count) && (retVal == NULL); i++) {
1371 int collision_at = specific_worm_collision(&worms[i], x, y);
1372 if (collision_at != -1) {
1373 if (!(w == &worms[i] && collision_at == w->head)){
1374 retVal = &worms[i];
1378 return retVal;
1382 * Returns true if the head of the worm just has
1383 * crossed the field boundaries.
1384 * @return bool true if the worm just has wrapped.
1386 static bool field_collision(struct worm *w)
1388 bool retVal = false;
1389 if ((w->x[w->head] >= FIELD_RECT_WIDTH) ||
1390 (w->y[w->head] >= FIELD_RECT_HEIGHT) ||
1391 (w->x[w->head] < 0) ||
1392 (w->y[w->head] < 0))
1394 retVal = true;
1396 return retVal;
1401 * Returns true if the specified coordinates are within the
1402 * field specified by the FIELD_RECT_XXX constants.
1403 * @param int x The x coordinate of the point that is investigated
1404 * @param int y The y coordinate of the point that is investigated
1405 * @return bool Returns false if x,y specifies a point outside the
1406 * field of worms.
1408 static bool is_in_field_rect(int x, int y)
1410 bool retVal = false;
1411 retVal = (x >= 0 && x < FIELD_RECT_WIDTH &&
1412 y >= 0 && y < FIELD_RECT_HEIGHT);
1413 return retVal;
1417 * Checks and returns wether the head of the w
1418 * is colliding with something currently.
1419 * @return int One of the values:
1420 * COLLISION_NONE
1421 * COLLISION_w
1422 * COLLISION_FOOD
1423 * COLLISION_ARGH
1424 * COLLISION_FIELD
1426 static int check_collision(struct worm *w)
1428 int retVal = COLLISION_NONE;
1430 if (worm_collision(w, w->x[w->head], w->y[w->head]) != NULL)
1431 retVal = COLLISION_WORM;
1433 if (food_collision(w->x[w->head], w->y[w->head]) >= 0)
1434 retVal = COLLISION_FOOD;
1436 if (argh_collision(w->x[w->head], w->y[w->head]) >= 0)
1437 retVal = COLLISION_ARGH;
1439 if (field_collision(w))
1440 retVal = COLLISION_FIELD;
1442 return retVal;
1446 * Returns the index of the food that is closest to the point
1447 * specified by x, y. This index may be used in the foodx and
1448 * foody arrays.
1449 * @param int x The x coordinate of the point
1450 * @param int y The y coordinate of the point
1451 * @return int A value usable as index in foodx and foody.
1453 static int get_nearest_food(int x, int y)
1455 int nearestfood = 0;
1456 int olddistance = FIELD_RECT_WIDTH + FIELD_RECT_HEIGHT;
1457 int deltax = 0;
1458 int deltay = 0;
1459 int foodindex;
1460 for (foodindex = 0; foodindex < MAX_FOOD; foodindex++) {
1461 int distance;
1462 deltax = foodx[foodindex] - x;
1463 deltay = foody[foodindex] - y;
1464 deltax = deltax > 0 ? deltax : deltax * (-1);
1465 deltay = deltay > 0 ? deltay : deltay * (-1);
1466 distance = deltax + deltay;
1468 if (distance < olddistance) {
1469 olddistance = distance;
1470 nearestfood = foodindex;
1473 return nearestfood;
1477 * Returns wether the specified position is next to the worm
1478 * and in the direction the worm looks. Use this method to
1479 * test wether this position would be hit with the next move of
1480 * the worm unless the worm changes its direction.
1481 * @param struct worm *w - The worm to be investigated
1482 * @param int x - The x coordinate of the position to test.
1483 * @param int y - The y coordinate of the position to test.
1484 * @return Returns true if the worm will hit the position unless
1485 * it change its direction before the next move.
1487 static bool is_in_front_of_worm(struct worm *w, int x, int y)
1489 bool infront = false;
1490 int deltax = x - w->x[w->head];
1491 int deltay = y - w->y[w->head];
1493 if (w->dirx == 0) {
1494 infront = (w->diry * deltay) > 0;
1495 } else {
1496 infront = (w->dirx * deltax) > 0;
1498 return infront;
1502 * Returns true if the worm will collide with the next move unless
1503 * it changes its direction.
1504 * @param struct worm *w - The worm to be investigated.
1505 * @return Returns true if the worm will collide with the next move
1506 * unless it changes its direction.
1508 static bool will_worm_collide(struct worm *w)
1510 int x = w->x[w->head] + w->dirx;
1511 int y = w->y[w->head] + w->diry;
1512 bool retVal = !is_in_field_rect(x, y);
1513 if (!retVal) {
1514 retVal = (argh_collision(x, y) != -1);
1517 if (!retVal) {
1518 retVal = (worm_collision(w, x, y) != NULL);
1520 return retVal;
1524 * This function
1525 * may be used to be stored in worm.fetch_worm_direction for
1526 * worms that are not controlled by humans but by artificial stupidity.
1527 * A direction is searched that doesn't lead to collision but to the nearest
1528 * food - but not very intelligent. The direction is written to the specified
1529 * worm.
1530 * @param struct worm *w - The worm of which the direction
1531 * is altered.
1533 static void virtual_player(struct worm *w)
1535 bool isright;
1536 int plana, planb, planc;
1537 /* find the next lunch */
1538 int nearestfood = get_nearest_food(w->x[w->head], w->y[w->head]);
1540 /* determine in which direction it is */
1542 /* in front of me? */
1543 bool infront = is_in_front_of_worm(w, foodx[nearestfood], foody[nearestfood]);
1545 /* left right of me? */
1546 int olddir = get_worm_dir(w);
1547 set_worm_dir(w, (olddir + 1) % 4);
1548 isright = is_in_front_of_worm(w, foodx[nearestfood], foody[nearestfood]);
1549 set_worm_dir(w, olddir);
1551 /* detect situation, set strategy */
1552 if (infront) {
1553 if (isright) {
1554 plana = olddir;
1555 planb = (olddir + 1) % 4;
1556 planc = (olddir + 3) % 4;
1557 } else {
1558 plana = olddir;
1559 planb = (olddir + 3) % 4;
1560 planc = (olddir + 1) % 4;
1562 } else {
1563 if (isright) {
1564 plana = (olddir + 1) % 4;
1565 planb = olddir;
1566 planc = (olddir + 3) % 4;
1567 } else {
1568 plana = (olddir + 3) % 4;
1569 planb = olddir;
1570 planc = (olddir + 1) % 4;
1574 /* test for collision */
1575 set_worm_dir(w, plana);
1576 if (will_worm_collide(w)){
1578 /* plan b */
1579 set_worm_dir(w, planb);
1581 /* test for collision */
1582 if (will_worm_collide(w)) {
1584 /* plan c */
1585 set_worm_dir(w, planc);
1591 * prints out the score board with all the status information
1592 * about the game.
1594 static void score_board(void)
1596 int i;
1597 int y = 0;
1598 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1599 rb->lcd_fillrect(FIELD_RECT_WIDTH + 2, 0,
1600 LCD_WIDTH - FIELD_RECT_WIDTH - 2, LCD_HEIGHT);
1601 rb->lcd_set_drawmode(DRMODE_SOLID);
1602 for (i = 0; i < worm_count; i++) {
1603 int score = get_score(&worms[i]);
1604 int collision = check_collision(&worms[i]);
1605 const char *state_str;
1607 /* high score */
1608 if (worms[i].fetch_worm_direction != virtual_player){
1609 if (highscore < score) {
1610 highscore = score;
1614 /* worm state */
1615 if (collision == COLLISION_NONE) {
1616 if (worms[i].growing > 0)
1617 state_str = "Growing";
1618 else {
1619 state_str = worms[i].alive ? "Hungry" : "Wormed";
1621 } else {
1622 state_str = state_desc[collision];
1625 /* length */
1626 rb->lcd_putsxyf(FIELD_RECT_WIDTH + 3, y , "Len:%d", score);
1627 rb->lcd_putsxyf(FIELD_RECT_WIDTH + 3, y+8, state_str);
1629 if (!worms[i].alive){
1630 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
1631 rb->lcd_fillrect(FIELD_RECT_WIDTH + 2, y,
1632 LCD_WIDTH - FIELD_RECT_WIDTH - 2, 17);
1633 rb->lcd_set_drawmode(DRMODE_SOLID);
1635 y += 19;
1637 #ifdef DEBUG_WORMLET
1638 rb->lcd_putsxyf(FIELD_RECT_WIDTH + 3, LCD_HEIGHT - 8, "ticks %d", max_cycle);
1639 #else
1640 rb->lcd_putsxyf(FIELD_RECT_WIDTH + 3, LCD_HEIGHT - 8, "Hs: %d", highscore);
1641 #endif
1645 * Checks for collisions of the worm and its environment and
1646 * takes appropriate actions like growing the worm or killing it.
1647 * @return bool Returns true if the worm is dead. Returns
1648 * false if the worm is healthy, up and creeping.
1650 static bool process_collisions(struct worm *w)
1652 int index = -1;
1654 w->alive &= !field_collision(w);
1656 if (w->alive) {
1658 /* check if food was eaten */
1659 index = food_collision(w->x[w->head], w->y[w->head]);
1660 if (index != -1){
1661 int i;
1663 clear_food(index);
1664 make_food(index);
1665 draw_food(index);
1667 for (i = 0; i < arghs_per_food; i++) {
1668 argh_count++;
1669 if (argh_count > MAX_ARGH)
1670 argh_count = MAX_ARGH;
1671 make_argh(argh_count - 1);
1672 draw_argh(argh_count - 1);
1675 add_growing(w, worm_food);
1677 draw_worm(w);
1680 /* check if argh was eaten */
1681 else {
1682 index = argh_collision(w->x[w->head], w->y[w->head]);
1683 if (index != -1) {
1684 w->alive = false;
1686 else {
1687 if (worm_collision(w, w->x[w->head], w->y[w->head]) != NULL) {
1688 w->alive = false;
1693 return !w->alive;
1697 * The main loop of the game.
1698 * @return bool Returns true if the game ended
1699 * with a dead worm. Returns false if the user
1700 * aborted the game manually.
1702 static int run(void)
1704 int button = 0;
1705 int wormDead = false;
1706 bool paused = false;
1708 /* ticks are counted to compensate speed variations */
1709 long cycle_start = 0, cycle_end = 0;
1710 #ifdef DEBUG_WORMLET
1711 int ticks_to_max_cycle_reset = 20;
1712 max_cycle = 0;
1713 #endif
1715 /* initialize the board and so on */
1716 init_wormlet();
1718 cycle_start = *rb->current_tick;
1719 /* change the direction of the worm */
1720 while (!wormDead)
1722 int i;
1723 long cycle_duration=0;
1725 #ifdef HAS_BUTTON_HOLD
1726 if (rb->button_hold())
1727 paused = true;
1728 #endif
1730 switch (button) {
1731 case BTN_STARTPAUSE:
1732 paused = !paused;
1733 break;
1734 case BTN_STOPRESET:
1735 if (paused)
1736 return 1; /* restart game */
1737 else
1738 paused = true;
1739 break;
1740 #ifdef BTN_RC_QUIT
1741 case BTN_RC_QUIT:
1742 #endif
1743 case BTN_QUIT:
1744 return 2; /* back to menu */
1745 break;
1747 if (!paused)
1749 switch (button) {
1750 case BTN_DIR_UP:
1751 if (players == 1 && !use_remote) {
1752 player1_dir = NORTH;
1754 break;
1756 case BTN_DIR_DOWN:
1757 if (players == 1 && !use_remote) {
1758 player1_dir = SOUTH;
1760 break;
1762 case BTN_DIR_LEFT:
1763 if (players != 1 || use_remote) {
1764 player1_dir = (player1_dir + 3) % 4;
1765 } else {
1766 player1_dir = WEST;
1768 break;
1770 case BTN_DIR_RIGHT:
1771 if (players != 1 || use_remote) {
1772 player1_dir = (player1_dir + 1) % 4;
1773 } else {
1774 player1_dir = EAST;
1776 break;
1778 #ifdef MULTIPLAYER
1779 case BTN_PLAYER2_DIR1:
1780 player2_dir = (player2_dir + 3) % 4;
1781 break;
1783 case BTN_PLAYER2_DIR2:
1784 player2_dir = (player2_dir + 1) % 4;
1785 break;
1786 #endif
1788 #ifdef REMOTE
1789 case BTN_RC_UP:
1790 player3_dir = (player3_dir + 1) % 4;
1791 break;
1793 case BTN_RC_DOWN:
1794 player3_dir = (player3_dir + 3) % 4;
1795 break;
1796 #endif
1800 for (i = 0; i < worm_count; i++) {
1801 worms[i].fetch_worm_direction(&worms[i]);
1804 wormDead = true;
1805 for (i = 0; i < worm_count; i++){
1806 struct worm *w = &worms[i];
1807 move_worm(w);
1808 wormDead &= process_collisions(w);
1809 draw_worm(w);
1811 score_board();
1812 rb->lcd_update();
1813 if (button == BTN_STOPRESET) {
1814 wormDead = true;
1817 /* here the wormlet game cycle ends
1818 thus the current tick is stored
1819 as end time */
1820 cycle_end = *rb->current_tick;
1822 /* The duration of the game cycle */
1823 cycle_duration = cycle_end - cycle_start;
1824 cycle_duration = MAX(0, cycle_duration);
1825 cycle_duration = MIN(speed -1, cycle_duration);
1828 #ifdef DEBUG_WORMLET
1829 ticks_to_max_cycle_reset--;
1830 if (ticks_to_max_cycle_reset <= 0) {
1831 max_cycle = 0;
1834 if (max_cycle < cycle_duration) {
1835 max_cycle = cycle_duration;
1836 ticks_to_max_cycle_reset = 20;
1838 #endif
1840 /* adjust the number of ticks to wait for a button.
1841 This ensures that a complete game cycle including
1842 user input runs in constant time */
1843 button = rb->button_get_w_tmo(speed - cycle_duration);
1844 cycle_start = *rb->current_tick;
1847 rb->splash(HZ*2, "Game Over!");
1849 return 2; /* back to menu */
1852 #ifdef DEBUG_WORMLET
1855 * Just a test routine that checks that worm_food_collision works
1856 * in some typical situations.
1858 static void test_worm_food_collision(void)
1860 int collision_count = 0;
1861 int i;
1862 rb->lcd_clear_display();
1863 init_worm(&worms[0], 10, 10);
1864 add_growing(&worms[0], 10);
1865 set_worm_dir(&worms[0], EAST);
1866 for (i = 0; i < 10; i++) {
1867 move_worm(&worms[0]);
1868 draw_worm(&worms[0]);
1871 set_worm_dir(&worms[0], SOUTH);
1872 for (i = 0; i < 10; i++) {
1873 move_worm(&worms[0]);
1874 draw_worm(&worms[0]);
1877 foodx[0] = 15;
1878 foody[0] = 12;
1879 for (foody[0] = 20; foody[0] > 0; foody[0] --) {
1880 bool collision;
1881 draw_worm(&worms[0]);
1882 draw_food(0);
1883 collision = worm_food_collision(&worms[0], 0);
1884 if (collision) {
1885 collision_count++;
1887 rb->lcd_putsxyf(0, LCD_HEIGHT -8, "collisions: %d", collision_count);
1888 rb->lcd_update();
1890 if (collision_count != food_size) {
1891 rb->button_get(true);
1895 foody[0] = 15;
1896 for (foodx[0] = 30; foodx[0] > 0; foodx[0] --) {
1897 bool collision;
1898 draw_worm(&worms[0]);
1899 draw_food(0);
1900 collision = worm_food_collision(&worms[0], 0);
1901 if (collision) {
1902 collision_count ++;
1904 rb->lcd_putsxyf(0, LCD_HEIGHT -8, "collisions: %d", collision_count);
1905 rb->lcd_update();
1907 if (collision_count != food_size * 2) {
1908 rb->button_get(true);
1913 static bool expensive_worm_in_rect(struct worm *w, int rx, int ry, int rw, int rh)
1915 int x, y;
1916 bool retVal = false;
1917 for (x = rx; x < rx + rw; x++){
1918 for (y = ry; y < ry + rh; y++) {
1919 if (specific_worm_collision(w, x, y) != -1) {
1920 retVal = true;
1924 return retVal;
1927 static void test_worm_argh_collision(void)
1929 int i;
1930 int dir;
1931 int collision_count = 0;
1932 rb->lcd_clear_display();
1933 init_worm(&worms[0], 10, 10);
1934 add_growing(&worms[0], 40);
1935 for (dir = 0; dir < 4; dir++) {
1936 set_worm_dir(&worms[0], (EAST + dir) % 4);
1937 for (i = 0; i < 10; i++) {
1938 move_worm(&worms[0]);
1939 draw_worm(&worms[0]);
1943 arghx[0] = 12;
1944 for (arghy[0] = 0; arghy[0] < FIELD_RECT_HEIGHT - argh_size; arghy[0]++){
1945 bool collision;
1946 draw_argh(0);
1947 collision = worm_argh_collision(&worms[0], 0);
1948 if (collision) {
1949 collision_count ++;
1951 rb->lcd_putsxyf(0, LCD_HEIGHT -8, "collisions: %d", collision_count);
1952 rb->lcd_update();
1954 if (collision_count != argh_size * 2) {
1955 rb->button_get(true);
1958 arghy[0] = 12;
1959 for (arghx[0] = 0; arghx[0] < FIELD_RECT_HEIGHT - argh_size; arghx[0]++){
1960 bool collision;
1961 draw_argh(0);
1962 collision = worm_argh_collision(&worms[0], 0);
1963 if (collision) {
1964 collision_count ++;
1966 rb->lcd_putsxyf(0, LCD_HEIGHT -8, "collisions: %d", collision_count);
1967 rb->lcd_update();
1969 if (collision_count != argh_size * 4) {
1970 rb->button_get(true);
1974 static int testline_in_rect(void)
1976 int testfailed = -1;
1978 int rx = 10;
1979 int ry = 15;
1980 int rw = 20;
1981 int rh = 25;
1983 /* Test 1 */
1984 int x1 = 12;
1985 int y1 = 8;
1986 int x2 = 12;
1987 int y2 = 42;
1989 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
1990 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
1991 rb->lcd_drawrect(rx, ry, rw, rh);
1992 rb->lcd_drawline(x1, y1, x2, y2);
1993 rb->lcd_update();
1994 rb->lcd_putsxy(0, 0, "failed 1");
1995 rb->button_get(true);
1996 testfailed = 1;
1999 /* test 2 */
2000 y2 = 20;
2001 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2002 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2003 rb->lcd_drawrect(rx, ry, rw, rh);
2004 rb->lcd_drawline(x1, y1, x2, y2);
2005 rb->lcd_putsxy(0, 0, "failed 2");
2006 rb->lcd_update();
2007 rb->button_get(true);
2008 testfailed = 2;
2011 /* test 3 */
2012 y1 = 30;
2013 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2014 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2015 rb->lcd_drawrect(rx, ry, rw, rh);
2016 rb->lcd_drawline(x1, y1, x2, y2);
2017 rb->lcd_putsxy(0, 0, "failed 3");
2018 rb->lcd_update();
2019 rb->button_get(true);
2020 testfailed = 3;
2023 /* test 4 */
2024 y2 = 45;
2025 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2026 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2027 rb->lcd_drawrect(rx, ry, rw, rh);
2028 rb->lcd_drawline(x1, y1, x2, y2);
2029 rb->lcd_putsxy(0, 0, "failed 4");
2030 rb->lcd_update();
2031 rb->button_get(true);
2032 testfailed = 4;
2035 /* test 5 */
2036 y1 = 50;
2037 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
2038 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2039 rb->lcd_drawrect(rx, ry, rw, rh);
2040 rb->lcd_drawline(x1, y1, x2, y2);
2041 rb->lcd_putsxy(0, 0, "failed 5");
2042 rb->lcd_update();
2043 rb->button_get(true);
2044 testfailed = 5;
2047 /* test 6 */
2048 y1 = 5;
2049 y2 = 7;
2050 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
2051 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2052 rb->lcd_drawrect(rx, ry, rw, rh);
2053 rb->lcd_drawline(x1, y1, x2, y2);
2054 rb->lcd_putsxy(0, 0, "failed 6");
2055 rb->lcd_update();
2056 rb->button_get(true);
2057 testfailed = 6;
2060 /* test 7 */
2061 x1 = 8;
2062 y1 = 20;
2063 x2 = 35;
2064 y2 = 20;
2065 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2066 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2067 rb->lcd_drawrect(rx, ry, rw, rh);
2068 rb->lcd_drawline(x1, y1, x2, y2);
2069 rb->lcd_putsxy(0, 0, "failed 7");
2070 rb->lcd_update();
2071 rb->button_get(true);
2072 testfailed = 7;
2075 /* test 8 */
2076 x2 = 12;
2077 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2078 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2079 rb->lcd_drawrect(rx, ry, rw, rh);
2080 rb->lcd_drawline(x1, y1, x2, y2);
2081 rb->lcd_putsxy(0, 0, "failed 8");
2082 rb->lcd_update();
2083 rb->button_get(true);
2084 testfailed = 8;
2087 /* test 9 */
2088 x1 = 25;
2089 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2090 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2091 rb->lcd_drawrect(rx, ry, rw, rh);
2092 rb->lcd_drawline(x1, y1, x2, y2);
2093 rb->lcd_putsxy(0, 0, "failed 9");
2094 rb->lcd_update();
2095 rb->button_get(true);
2096 testfailed = 9;
2099 /* test 10 */
2100 x2 = 37;
2101 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2102 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2103 rb->lcd_drawrect(rx, ry, rw, rh);
2104 rb->lcd_drawline(x1, y1, x2, y2);
2105 rb->lcd_putsxy(0, 0, "failed 10");
2106 rb->lcd_update();
2107 rb->button_get(true);
2108 testfailed = 10;
2111 /* test 11 */
2112 x1 = 42;
2113 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
2114 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2115 rb->lcd_drawrect(rx, ry, rw, rh);
2116 rb->lcd_drawline(x1, y1, x2, y2);
2117 rb->lcd_putsxy(0, 0, "failed 11");
2118 rb->lcd_update();
2119 rb->button_get(true);
2120 testfailed = 11;
2123 /* test 12 */
2124 x1 = 5;
2125 x2 = 7;
2126 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
2127 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2128 rb->lcd_drawrect(rx, ry, rw, rh);
2129 rb->lcd_drawline(x1, y1, x2, y2);
2130 rb->lcd_putsxy(0, 0, "failed 12");
2131 rb->lcd_update();
2132 rb->button_get(true);
2133 testfailed = 12;
2136 /* test 13 */
2137 rx = 9;
2138 ry = 15;
2139 rw = food_size;
2140 rh = food_size;
2142 x1 = 10;
2143 y1 = 10;
2144 x2 = 10;
2145 y2 = 20;
2146 if (!(line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2147 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh))) {
2148 rb->lcd_drawrect(rx, ry, rw, rh);
2149 rb->lcd_drawline(x1, y1, x2, y2);
2150 rb->lcd_putsxy(0, 0, "failed 13");
2151 rb->lcd_update();
2152 rb->button_get(true);
2153 testfailed = 13;
2156 /* test 14 */
2157 rx = 9;
2158 ry = 15;
2159 rw = 4;
2160 rh = 4;
2162 x1 = 10;
2163 y1 = 10;
2164 x2 = 10;
2165 y2 = 19;
2166 if (!(line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2167 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh))) {
2168 rb->lcd_drawline(x1, y1, x2, y2);
2169 rb->lcd_invertrect(rx, ry, rw, rh);
2170 rb->lcd_putsxy(0, 0, "failed 14");
2171 rb->lcd_update();
2172 rb->button_get(true);
2173 testfailed = 14;
2176 rb->lcd_clear_display();
2178 return testfailed;
2182 * Just a test routine to test wether specific_worm_collision might work properly
2184 static int test_specific_worm_collision(void)
2186 int collisions = 0;
2187 int dir;
2188 int x = 0;
2189 int y = 0;
2190 rb->lcd_clear_display();
2191 init_worm(&worms[0], 10, 20);
2192 add_growing(&worms[0], 20 - INITIAL_WORM_LENGTH);
2194 for (dir = EAST; dir < EAST + 4; dir++) {
2195 int i;
2196 set_worm_dir(&worms[0], dir % 4);
2197 for (i = 0; i < 5; i++) {
2198 if (!(dir % 4 == NORTH && i == 9)) {
2199 move_worm(&worms[0]);
2200 draw_worm(&worms[0]);
2205 for (y = 15; y < 30; y ++){
2206 for (x = 5; x < 20; x++) {
2207 if (specific_worm_collision(&worms[0], x, y) != -1) {
2208 collisions ++;
2210 rb->lcd_invertpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y);
2211 rb->lcd_putsxyf(0, LCD_HEIGHT - 8, "collisions %d", collisions);
2212 rb->lcd_update();
2215 if (collisions != 21) {
2216 rb->button_get(true);
2218 return collisions;
2221 static void test_make_argh(void)
2223 int dir;
2224 int seed = 0;
2225 int hit = 0;
2226 int failures = 0;
2227 int last_failures = 0;
2228 int i, worm_idx;
2229 rb->lcd_clear_display();
2230 worm_count = 3;
2232 for (worm_idx = 0; worm_idx < worm_count; worm_idx++) {
2233 init_worm(&worms[worm_idx], 10 + worm_idx * 20, 20);
2234 add_growing(&worms[worm_idx], 40 - INITIAL_WORM_LENGTH);
2237 for (dir = EAST; dir < EAST + 4; dir++) {
2238 for (worm_idx = 0; worm_idx < worm_count; worm_idx++) {
2239 set_worm_dir(&worms[worm_idx], dir % 4);
2240 for (i = 0; i < 10; i++) {
2241 if (!(dir % 4 == NORTH && i == 9)) {
2242 move_worm(&worms[worm_idx]);
2243 draw_worm(&worms[worm_idx]);
2249 rb->lcd_update();
2251 for (seed = 0; hit < 20; seed += 2) {
2252 int x, y;
2253 rb->srand(seed);
2254 x = rb->rand() % (FIELD_RECT_WIDTH - argh_size);
2255 y = rb->rand() % (FIELD_RECT_HEIGHT - argh_size);
2257 for (worm_idx = 0; worm_idx < worm_count; worm_idx++){
2258 if (expensive_worm_in_rect(&worms[worm_idx], x, y, argh_size, argh_size)) {
2259 int tries = 0;
2260 rb->srand(seed);
2262 tries = make_argh(0);
2263 if ((x == arghx[0] && y == arghy[0]) || tries < 2) {
2264 failures ++;
2267 rb->lcd_putsxyf(0, LCD_HEIGHT - 8, "(%d;%d) fail%d try%d",
2268 x, y, failures, tries);
2269 rb->lcd_update();
2270 rb->lcd_invertrect(x + FIELD_RECT_X, y+ FIELD_RECT_Y,
2271 argh_size, argh_size);
2272 rb->lcd_update();
2273 draw_argh(0);
2274 rb->lcd_update();
2275 rb->lcd_invertrect(x + FIELD_RECT_X, y + FIELD_RECT_Y,
2276 argh_size, argh_size);
2277 rb->lcd_clearrect(arghx[0] + FIELD_RECT_X, arghy[0] + FIELD_RECT_Y,
2278 argh_size, argh_size);
2280 if (failures > last_failures) {
2281 rb->button_get(true);
2283 last_failures = failures;
2284 hit ++;
2290 static void test_worm_argh_collision_in_moves(void) {
2291 int hit_count = 0;
2292 int i;
2293 rb->lcd_clear_display();
2294 init_worm(&worms[0], 10, 20);
2296 arghx[0] = 20;
2297 arghy[0] = 18;
2298 draw_argh(0);
2300 set_worm_dir(&worms[0], EAST);
2301 for (i = 0; i < 20; i++) {
2302 move_worm(&worms[0]);
2303 draw_worm(&worms[0]);
2304 if (worm_argh_collision_in_moves(&worms[0], 0, 5)){
2305 hit_count ++;
2307 rb->lcd_putsxyf(0, LCD_HEIGHT - 8, "in 5 moves hits: %d", hit_count);
2308 rb->lcd_update();
2310 if (hit_count != argh_size + 5) {
2311 rb->button_get(true);
2314 #endif /* DEBUG_WORMLET */
2317 * Reverts default settings
2319 static void default_settings(void)
2321 arghs_per_food = ARGHS_PER_FOOD;
2322 argh_size = ARGH_SIZE;
2323 food_size = FOOD_SIZE;
2324 speed = SPEED;
2325 worm_food = WORM_PER_FOOD;
2326 players = 1;
2327 worm_count = MAX_WORMS;
2328 use_remote = false;
2329 return;
2333 * Launches the wormlet game
2335 static bool launch_wormlet(void)
2337 int game_result = 1;
2339 rb->lcd_clear_display();
2341 /* Turn off backlight timeout */
2342 backlight_ignore_timeout();
2344 /* start the game */
2345 while (game_result == 1)
2346 game_result = run();
2348 switch (game_result)
2350 case 2:
2351 /* Turn on backlight timeout (revert to settings) */
2352 backlight_use_settings();
2353 return false;
2354 break;
2356 return false;
2360 * Main entry point
2362 enum plugin_status plugin_start(const void* parameter)
2364 int result;
2365 int menu_quit = 0;
2366 int new_setting;
2368 (void)(parameter);
2370 default_settings();
2371 if (configfile_load(SETTINGS_FILENAME, config,
2372 sizeof(config)/sizeof(*config),
2373 SETTINGS_MIN_VERSION ) < 0)
2375 /* If the loading failed, save a new config file (as the disk is
2376 already spinning) */
2377 configfile_save(SETTINGS_FILENAME, config,
2378 sizeof(config)/sizeof(*config),
2379 SETTINGS_VERSION);
2382 #ifdef HAVE_LCD_COLOR
2383 rb->lcd_set_foreground(COLOR_FG);
2384 rb->lcd_set_background(COLOR_BG);
2385 #endif
2387 #if LCD_DEPTH > 1
2388 rb->lcd_set_backdrop(NULL);
2389 #endif
2391 #ifdef DEBUG_WORMLET
2392 testline_in_rect();
2393 test_worm_argh_collision_in_moves();
2394 test_make_argh();
2395 test_worm_food_collision();
2396 test_worm_argh_collision();
2397 test_specific_worm_collision();
2398 #endif
2400 /* Setup screen */
2402 static const struct opt_items noyes[2] = {
2403 { "No", -1 },
2404 { "Yes", -1 },
2407 static const struct opt_items remoteonly_option[1] = {
2408 { "Remote Control", -1 }
2411 static const struct opt_items key24_option[2] = {
2412 { "4 Key Control", -1 },
2413 { "2 Key Control", -1 }
2416 #ifdef REMOTE
2417 static const struct opt_items remote_option[2] = {
2418 { "Remote Control", -1 },
2419 { "No Rem. Control", -1 }
2421 #else
2422 static const struct opt_items key2_option[1] = {
2423 { "2 Key Control", -1 }
2425 #endif
2427 static const struct opt_items nokey_option[1] = {
2428 { "Out of Control", -1 }
2431 MENUITEM_STRINGLIST(menu, "Wormlet Menu", NULL, "Play Wormlet!",
2432 "Number of Worms", "Number of Players", "Control Style",
2433 "Worm Growth Per Food","Worm Speed","Arghs Per Food",
2434 "Argh Size","Food Size","Revert to Default Settings",
2435 "Playback Control", "Quit");
2437 rb->button_clear_queue();
2439 while (!menu_quit) {
2440 switch(rb->do_menu(&menu, &result, NULL, false))
2442 case 0:
2443 rb->lcd_setfont(FONT_SYSFIXED);
2444 launch_wormlet();
2445 break;
2446 case 1:
2447 rb->set_int("Number of Worms", "", UNIT_INT, &worm_count, NULL,
2448 1, 1, 3, NULL);
2449 if (worm_count < players) {
2450 worm_count = players;
2452 break;
2453 case 2:
2454 #ifdef MULTIPLAYER
2455 rb->set_int("Number of Players", "", UNIT_INT, &players, NULL,
2456 1, 0, 4, NULL);
2457 #else
2458 rb->set_int("Number of Players", "", UNIT_INT, &players, NULL,
2459 1, 0, 2, NULL);
2460 #endif
2461 if (players > worm_count) {
2462 worm_count = players;
2464 if (players > 2) {
2465 use_remote = true;
2467 break;
2468 case 3:
2469 switch(players) {
2470 case 0:
2471 rb->set_option("Control Style",&use_remote,INT,
2472 nokey_option, 1, NULL);
2473 break;
2474 case 1:
2475 rb->set_option("Control Style",&use_remote,INT,
2476 key24_option, 2, NULL);
2477 break;
2478 case 2:
2479 #ifdef REMOTE
2480 rb->set_option("Control Style",&use_remote,INT,
2481 remote_option, 2, NULL);
2482 #else
2483 rb->set_option("Control Style",&use_remote,INT,
2484 key2_option, 1, NULL);
2485 #endif
2486 break;
2487 case 3:
2488 rb->set_option("Control Style",&use_remote,INT,
2489 remoteonly_option, 1, NULL);
2490 break;
2492 break;
2493 case 4:
2494 rb->set_int("Worm Growth Per Food", "", UNIT_INT, &worm_food,
2495 NULL, 1, 0, 15, NULL);
2496 break;
2497 case 5:
2498 new_setting = 20 - speed;
2499 rb->set_int("Worm Speed", "", UNIT_INT, &new_setting,
2500 NULL, 1, 0, 20, NULL);
2501 speed = 20 - new_setting;
2502 break;
2503 case 6:
2504 rb->set_int("Arghs Per Food", "", UNIT_INT, &arghs_per_food,
2505 NULL, 1, 0, 8, NULL);
2506 break;
2507 case 7:
2508 rb->set_int("Argh Size", "", UNIT_INT, &argh_size,
2509 NULL, 1, 2, 10, NULL);
2510 break;
2511 case 8:
2512 rb->set_int("Food Size", "", UNIT_INT, &food_size,
2513 NULL, 1, 2, 10, NULL);
2514 break;
2515 case 9:
2516 new_setting = 0;
2517 rb->set_option("Reset Settings?", &new_setting, INT, noyes , 2, NULL);
2518 if (new_setting == 1)
2519 default_settings();
2520 break;
2521 case 10:
2522 playback_control(NULL);
2523 break;
2524 default:
2525 menu_quit=1;
2526 break;
2530 configfile_save(SETTINGS_FILENAME, config,
2531 sizeof(config)/sizeof(*config),
2532 SETTINGS_VERSION);
2534 return PLUGIN_OK;