1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (C) 2002 Philipp Pertermann
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
20 #include "configfile.h"
25 /* size of the field the worm lives in */
26 #define FIELD_RECT_X 1
27 #define FIELD_RECT_Y 1
28 #define FIELD_RECT_WIDTH (LCD_WIDTH - 45)
29 #define FIELD_RECT_HEIGHT (LCD_HEIGHT - 2)
31 /* when the game starts */
32 #define INITIAL_WORM_LENGTH 10
34 /* num of pixel the worm grows per eaten food */
35 #define WORM_PER_FOOD 7
37 /* num of worms creeping in the FIELD */
40 /* minimal distance between a worm and an argh
41 when a new argh is made */
42 #define MIN_ARGH_DIST 5
44 #if (CONFIG_KEYPAD == RECORDER_PAD)
45 #define BTN_DIR_UP BUTTON_UP
46 #define BTN_DIR_DOWN BUTTON_DOWN
47 #define BTN_DIR_LEFT BUTTON_LEFT
48 #define BTN_DIR_RIGHT BUTTON_RIGHT
49 #define BTN_PLAYER2_DIR1 BUTTON_F2
50 #define BTN_PLAYER2_DIR2 BUTTON_F3
51 #define BTN_STARTPAUSE BUTTON_PLAY
52 #define BTN_QUIT BUTTON_OFF
53 #define BTN_STOPRESET BUTTON_ON
54 #define BTN_TOGGLE_KEYS BUTTON_F1
56 #if BUTTON_REMOTE != 0
57 #define BTN_RC_UP BUTTON_RC_VOL_UP
58 #define BTN_RC_DOWN BUTTON_RC_VOL_DOWN
63 #define PLAYERS_TEXT "UP/DN"
64 #define WORMS_TEXT "L/R"
65 #define KEY_CONTROL_TEXT "F1"
67 #elif (CONFIG_KEYPAD == ARCHOS_AV300_PAD)
68 #define BTN_DIR_UP BUTTON_UP
69 #define BTN_DIR_DOWN BUTTON_DOWN
70 #define BTN_DIR_LEFT BUTTON_LEFT
71 #define BTN_DIR_RIGHT BUTTON_RIGHT
72 #define BTN_PLAYER2_DIR1 BUTTON_F2
73 #define BTN_PLAYER2_DIR2 BUTTON_F3
74 #define BTN_STARTPAUSE BUTTON_SELECT
75 #define BTN_QUIT BUTTON_OFF
76 #define BTN_STOPRESET BUTTON_ON
77 #define BTN_TOGGLE_KEYS BUTTON_F1
79 #define PLAYERS_TEXT "UP/DN"
80 #define WORMS_TEXT "L/R"
81 #define KEY_CONTROL_TEXT "F1"
83 #elif (CONFIG_KEYPAD == ONDIO_PAD)
84 #define BTN_DIR_UP BUTTON_UP
85 #define BTN_DIR_DOWN BUTTON_DOWN
86 #define BTN_DIR_LEFT BUTTON_LEFT
87 #define BTN_DIR_RIGHT BUTTON_RIGHT
88 #define BTN_STARTPAUSE (BUTTON_MENU|BUTTON_REL)
89 #define BTN_QUIT (BUTTON_OFF|BUTTON_REL)
90 #define BTN_STOPRESET (BUTTON_OFF|BUTTON_MENU)
92 #define PLAYERS_TEXT "UP/DN"
93 #define WORMS_TEXT "L/R"
95 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \
96 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
98 #define BTN_DIR_UP BUTTON_MENU
99 #define BTN_DIR_DOWN BUTTON_PLAY
100 #define BTN_DIR_LEFT BUTTON_LEFT
101 #define BTN_DIR_RIGHT BUTTON_RIGHT
102 #define BTN_STARTPAUSE (BUTTON_SELECT|BUTTON_REL)
103 #define BTN_QUIT (BUTTON_SELECT|BUTTON_MENU)
104 #define BTN_STOPRESET (BUTTON_SELECT|BUTTON_PLAY)
106 #define PLAYERS_TEXT "Menu/Play"
107 #define WORMS_TEXT "Left/Right"
109 #elif (CONFIG_KEYPAD == IRIVER_H300_PAD) || (CONFIG_KEYPAD == IRIVER_H100_PAD)
111 #define BTN_DIR_UP BUTTON_UP
112 #define BTN_DIR_DOWN BUTTON_DOWN
113 #define BTN_DIR_LEFT BUTTON_LEFT
114 #define BTN_DIR_RIGHT BUTTON_RIGHT
115 #define BTN_STARTPAUSE (BUTTON_SELECT|BUTTON_REL)
116 #define BTN_QUIT BUTTON_OFF
117 #define BTN_STOPRESET BUTTON_ON
119 #define BTN_RC_QUIT BUTTON_RC_STOP
121 #define PLAYERS_TEXT "Up/Down"
122 #define WORMS_TEXT "Left/Right"
124 #elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD)
126 #define BTN_DIR_UP BUTTON_UP
127 #define BTN_DIR_DOWN BUTTON_DOWN
128 #define BTN_DIR_LEFT BUTTON_LEFT
129 #define BTN_DIR_RIGHT BUTTON_RIGHT
130 #define BTN_STARTPAUSE BUTTON_PLAY
131 #define BTN_QUIT BUTTON_POWER
132 #define BTN_STOPRESET BUTTON_REC
134 #define PLAYERS_TEXT "Up/Down"
135 #define WORMS_TEXT "Left/Right"
137 #elif (CONFIG_KEYPAD == GIGABEAT_PAD)
139 #define BTN_DIR_UP BUTTON_UP
140 #define BTN_DIR_DOWN BUTTON_DOWN
141 #define BTN_DIR_LEFT BUTTON_LEFT
142 #define BTN_DIR_RIGHT BUTTON_RIGHT
143 #define BTN_STARTPAUSE BUTTON_SELECT
144 #define BTN_QUIT BUTTON_POWER
145 #define BTN_STOPRESET BUTTON_A
147 #define PLAYERS_TEXT "Up/Down"
148 #define WORMS_TEXT "Left/Right"
151 #elif (CONFIG_KEYPAD == SANSA_E200_PAD)
153 #define BTN_DIR_UP BUTTON_UP
154 #define BTN_DIR_DOWN BUTTON_DOWN
155 #define BTN_DIR_LEFT BUTTON_LEFT
156 #define BTN_DIR_RIGHT BUTTON_RIGHT
157 #define BTN_STARTPAUSE BUTTON_SELECT
158 #define BTN_QUIT BUTTON_POWER
159 #define BTN_STOPRESET BUTTON_REC
161 #define PLAYERS_TEXT "Up/Down"
162 #define WORMS_TEXT "Left/Right"
165 #elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
167 #define BTN_DIR_UP BUTTON_SCROLL_UP
168 #define BTN_DIR_DOWN BUTTON_SCROLL_DOWN
169 #define BTN_DIR_LEFT BUTTON_LEFT
170 #define BTN_DIR_RIGHT BUTTON_RIGHT
171 #define BTN_STARTPAUSE BUTTON_PLAY
172 #define BTN_QUIT BUTTON_POWER
173 #define BTN_STOPRESET BUTTON_REW
175 #define PLAYERS_TEXT "Up/Down"
176 #define WORMS_TEXT "Left/Right"
180 #if (LCD_WIDTH == 112) && (LCD_HEIGHT == 64)
184 #define MAX_WORM_SEGMENTS 128
185 #elif (LCD_WIDTH == 138) && (LCD_HEIGHT == 110)
189 #define MAX_WORM_SEGMENTS 128
190 #elif (LCD_WIDTH == 128) && (LCD_HEIGHT == 128)
194 #define MAX_WORM_SEGMENTS 128
195 #elif (LCD_WIDTH == 160) && (LCD_HEIGHT == 128)
199 #define MAX_WORM_SEGMENTS 256
200 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
204 #define MAX_WORM_SEGMENTS 256
205 #elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
209 #define MAX_WORM_SEGMENTS 512
210 #elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
214 #define MAX_WORM_SEGMENTS 512
215 #elif (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)
219 #define MAX_WORM_SEGMENTS 512
220 #elif (LCD_WIDTH == 240) && (LCD_HEIGHT == 320)
224 #define MAX_WORM_SEGMENTS 512
227 #ifdef HAVE_LCD_COLOR
228 #define COLOR_WORM LCD_RGBPACK(80, 40, 0)
229 #define COLOR_ARGH LCD_RGBPACK(175, 0, 0)
230 #define COLOR_FOOD LCD_RGBPACK(0, 150, 0)
231 #define COLOR_FG LCD_RGBPACK(0, 0, 0)
232 #define COLOR_BG LCD_RGBPACK(181, 199, 231)
236 * All the properties that a worm has.
239 /* The worm is stored in a ring of xy coordinates */
240 int x
[MAX_WORM_SEGMENTS
];
241 int y
[MAX_WORM_SEGMENTS
];
243 int head
; /* index of the head within the buffer */
244 int tail
; /* index of the tail within the buffer */
245 int growing
; /* number of cyles the worm still keeps growing */
246 bool alive
; /* the worms living state */
248 /* direction vector in which the worm moves */
249 int dirx
; /* only values -1 0 1 allowed */
250 int diry
; /* only values -1 0 1 allowed */
252 /* this method is used to fetch the direction the user
253 has selected. It can be one of the values
254 human_player1, human_player2, remote_player, virtual_player.
255 All these values are fuctions, that can change the direction
257 void (*fetch_worm_direction
)(struct worm
*w
);
260 /* stores the highscore - besides it was scored by a virtual player */
261 static int highscore
;
263 #define MAX_FOOD 5 /* maximal number of food items */
265 /* The arrays store the food coordinates */
266 static int foodx
[MAX_FOOD
];
267 static int foody
[MAX_FOOD
];
269 #define MAX_ARGH 100 /* maximal number of argh items */
270 #define ARGHS_PER_FOOD 2 /* number of arghs produced per eaten food */
272 /* The arrays store the argh coordinates */
273 static int arghx
[MAX_ARGH
];
274 static int arghy
[MAX_ARGH
];
276 /* the number of arghs that are currently in use */
277 static int argh_count
;
279 /* the number of arghs per food, settable by user */
280 static int arghs_per_food
= ARGHS_PER_FOOD
;
281 /* the size of the argh, settable by user */
282 static int argh_size
= ARGH_SIZE
;
283 /* the size of the food, settable by user */
284 static int food_size
= FOOD_SIZE
;
285 /* the speed of the worm, settable by user */
286 static int speed
= SPEED
;
287 /* the amount a worm grows by eating a food, settable by user */
288 static int worm_food
= WORM_PER_FOOD
;
290 /* End additional variables */
293 /* just a buffer used for debug output */
294 static char debugout
[15];
297 /* the number of active worms (dead or alive) */
298 static int worm_count
= MAX_WORMS
;
300 /* in multiplayer mode: en- / disables the remote worm control
301 in singleplayer mode: toggles 4 / 2 button worm control */
302 static bool use_remote
= false;
304 /* return values of check_collision */
305 #define COLLISION_NONE 0
306 #define COLLISION_WORM 1
307 #define COLLISION_FOOD 2
308 #define COLLISION_ARGH 3
309 #define COLLISION_FIELD 4
311 /* constants for use as directions.
312 Note that the values are ordered clockwise.
313 Thus increasing / decreasing the values
314 is equivalent to right / left turns. */
320 /* direction of human player 1 */
321 static int player1_dir
= EAST
;
322 /* direction of human player 2 */
323 static int player2_dir
= EAST
;
324 /* direction of human player 3 */
325 static int player3_dir
= EAST
;
327 /* the number of (human) players that currently
329 static int players
= 1;
331 /* the rockbox plugin api */
332 static struct plugin_api
* rb
;
334 #define SETTINGS_VERSION 1
335 #define SETTINGS_MIN_VERSION 1
336 #define SETTINGS_FILENAME "wormlet.cfg"
338 static struct configdata config
[] =
340 {TYPE_INT
, 0, 1024, &highscore
, "highscore", NULL
, NULL
},
341 {TYPE_INT
, 0, 15, &arghs_per_food
, "arghs per food", NULL
, NULL
},
342 {TYPE_INT
, 0, 15, &argh_size
, "argh size", NULL
, NULL
},
343 {TYPE_INT
, 0, 15, &food_size
, "food size", NULL
, NULL
},
344 {TYPE_INT
, 0, 3, &players
, "players", NULL
, NULL
},
345 {TYPE_INT
, 0, 3, &worm_count
, "worms", NULL
, NULL
},
346 {TYPE_INT
, 0, 20, &speed
, "speed", NULL
, NULL
},
347 {TYPE_INT
, 0, 15, &worm_food
, "Worm Growth Per Food", NULL
, NULL
}//,
348 //{TYPE_INT, 0, 3, &use_remote, "use remote", NULL, NULL}
352 static void set_debug_out(char *str
){
353 strcpy(debugout
, str
);
358 * Returns the direction id in which the worm
359 * currently is creeping.
360 * @param struct worm *w The worm that is to be investigated.
361 * w Must not be null.
362 * @return int A value 0 <= value < 4
363 * Note the predefined constants NORTH, SOUTH, EAST, WEST
365 static int get_worm_dir(struct worm
*w
) {
384 * Set the direction of the specified worm with a direction id.
385 * Increasing the value by 1 means to turn the worm direction
386 * to right by 90 degree.
387 * @param struct worm *w The worm that is to be altered. w Must not be null.
388 * @param int dir The new direction in which the worm is to creep.
389 * dir must be 0 <= dir < 4. Use predefined constants
390 * NORTH, SOUTH, EAST, WEST
392 static void set_worm_dir(struct worm
*w
, int dir
) {
414 * Returns the current length of the worm array. This
415 * is also a value for the number of bends that are in the worm.
416 * @return int a positive value with 0 <= value < MAX_WORM_SEGMENTS
418 static int get_worm_array_length(struct worm
*w
) {
419 /* initial simple calculation will be overwritten if wrong. */
420 int retVal
= w
->head
- w
->tail
;
422 /* if the worm 'crosses' the boundaries of the ringbuffer */
424 retVal
= w
->head
+ MAX_WORM_SEGMENTS
- w
->tail
;
431 * Returns the score the specified worm. The score is the length
433 * @param struct worm *w The worm that is to be investigated.
434 * w must not be null.
435 * @return int The length of the worm (>= 0).
437 static int get_score(struct worm
*w
) {
439 int length
= get_worm_array_length(w
);
441 for (i
= 0; i
< length
; i
++) {
443 /* The iteration iterates the length of the worm.
444 Here's the conversion to the true indices within the worm arrays. */
445 int linestart
= (w
->tail
+ i
) % MAX_WORM_SEGMENTS
;
446 int lineend
= (linestart
+ 1) % MAX_WORM_SEGMENTS
;
447 int startx
= w
->x
[linestart
];
448 int starty
= w
->y
[linestart
];
449 int endx
= w
->x
[lineend
];
450 int endy
= w
->y
[lineend
];
452 int minimum
, maximum
;
454 if (startx
== endx
) {
455 minimum
= MIN(starty
, endy
);
456 maximum
= MAX(starty
, endy
);
458 minimum
= MIN(startx
, endx
);
459 maximum
= MAX(startx
, endx
);
461 retval
+= abs(maximum
- minimum
);
467 * Determines wether the line specified by startx, starty, endx, endy intersects
468 * the rectangle specified by x, y, width, height. Note that the line must be exactly
469 * horizontal or vertical (startx == endx or starty == endy).
470 * @param int startx The x coordinate of the start point of the line.
471 * @param int starty The y coordinate of the start point of the line.
472 * @param int endx The x coordinate of the end point of the line.
473 * @param int endy The y coordinate of the end point of the line.
474 * @param int x The x coordinate of the top left corner of the rectangle.
475 * @param int y The y coordinate of the top left corner of the rectangle.
476 * @param int width The width of the rectangle.
477 * @param int height The height of the rectangle.
478 * @return bool Returns true if the specified line intersects with the recangle.
480 static bool line_in_rect(int startx
, int starty
, int endx
, int endy
, int x
, int y
, int width
, int height
) {
482 int simple
, simplemin
, simplemax
;
483 int compa
, compb
, compmin
, compmax
;
485 if (startx
== endx
) {
488 simplemax
= x
+ width
;
493 compmax
= y
+ height
;
497 simplemax
= y
+ height
;
506 compa
= MIN(compa
, compb
);
507 compb
= MAX(temp
, compb
);
509 if (simplemin
<= simple
&& simple
<= simplemax
) {
510 if ((compmin
<= compa
&& compa
<= compmax
) ||
511 (compmin
<= compb
&& compb
<= compmax
) ||
512 (compa
<= compmin
&& compb
>= compmax
)) {
520 * Tests wether the specified worm intersects with the rect.
521 * @param struct worm *w The worm to be investigated
522 * @param int x The x coordinate of the top left corner of the rect
523 * @param int y The y coordinate of the top left corner of the rect
524 * @param int widht The width of the rect
525 * @param int height The height of the rect
526 * @return bool Returns true if the worm intersects with the rect
528 static bool worm_in_rect(struct worm
*w
, int x
, int y
, int width
, int height
) {
532 /* get_worm_array_length is expensive -> buffer the value */
533 int wormLength
= get_worm_array_length(w
);
536 /* test each entry that is part of the worm */
537 for (i
= 0; i
< wormLength
&& retval
== false; i
++) {
539 /* The iteration iterates the length of the worm.
540 Here's the conversion to the true indices within the worm arrays. */
541 int linestart
= (w
->tail
+ i
) % MAX_WORM_SEGMENTS
;
542 int lineend
= (linestart
+ 1) % MAX_WORM_SEGMENTS
;
543 int startx
= w
->x
[linestart
];
544 int starty
= w
->y
[linestart
];
545 int endx
= w
->x
[lineend
];
546 int endy
= w
->y
[lineend
];
548 retval
= line_in_rect(startx
, starty
, endx
, endy
, x
, y
, width
, height
);
555 * Checks wether a specific food in the food arrays is at the
556 * specified coordinates.
557 * @param int foodIndex The index of the food in the food arrays
558 * @param int x the x coordinate.
559 * @param int y the y coordinate.
560 * @return Returns true if the coordinate hits the food specified by
563 static bool specific_food_collision(int foodIndex
, int x
, int y
) {
565 if (x
>= foodx
[foodIndex
] &&
566 x
< foodx
[foodIndex
] + food_size
&&
567 y
>= foody
[foodIndex
] &&
568 y
< foody
[foodIndex
] + food_size
) {
576 * Returns the index of the food that is at the
577 * given coordinates. If no food is at the coordinates
579 * @return int -1 <= value < MAX_FOOD
581 static int food_collision(int x
, int y
) {
584 for (i
= 0; i
< MAX_FOOD
; i
++) {
585 if (specific_food_collision(i
, x
, y
)) {
594 * Checks wether a specific argh in the argh arrays is at the
595 * specified coordinates.
596 * @param int arghIndex The index of the argh in the argh arrays
597 * @param int x the x coordinate.
598 * @param int y the y coordinate.
599 * @return Returns true if the coordinate hits the argh specified by
602 static bool specific_argh_collision(int arghIndex
, int x
, int y
) {
604 if ( x
>= arghx
[arghIndex
] &&
605 y
>= arghy
[arghIndex
] &&
606 x
< arghx
[arghIndex
] + argh_size
&&
607 y
< arghy
[arghIndex
] + argh_size
)
616 * Returns the index of the argh that is at the
617 * given coordinates. If no argh is at the coordinates
619 * @param int x The x coordinate.
620 * @param int y The y coordinate.
621 * @return int -1 <= value < argh_count <= MAX_ARGH
623 static int argh_collision(int x
, int y
) {
627 /* search for the argh that has the specified coords */
628 for (i
= 0; i
< argh_count
; i
++) {
629 if (specific_argh_collision(i
, x
, y
)) {
638 * Checks wether the worm collides with the food at the specfied food-arrays.
639 * @param int foodIndex The index of the food in the arrays. Ensure the value is
640 * 0 <= foodIndex <= MAX_FOOD
641 * @return Returns true if the worm collides with the specified food.
643 static bool worm_food_collision(struct worm
*w
, int foodIndex
)
647 retVal
= worm_in_rect(w
, foodx
[foodIndex
], foody
[foodIndex
],
648 food_size
- 1, food_size
- 1);
654 * Returns true if the worm hits the argh within the next moves (unless
655 * the worm changes it's direction).
656 * @param struct worm *w - The worm to investigate
657 * @param int argh_idx - The index of the argh
658 * @param int moves - The number of moves that are considered.
659 * @return Returns false if the specified argh is not hit within the next
662 static bool worm_argh_collision_in_moves(struct worm
*w
, int argh_idx
, int moves
){
668 x2
= w
->x
[w
->head
] + moves
* w
->dirx
;
669 y2
= w
->y
[w
->head
] + moves
* w
->diry
;
671 retVal
= line_in_rect(x1
, y1
, x2
, y2
, arghx
[argh_idx
], arghy
[argh_idx
],
672 argh_size
, argh_size
);
677 * Checks wether the worm collides with the argh at the specfied argh-arrays.
678 * @param int arghIndex The index of the argh in the arrays.
679 * Ensure the value is 0 <= arghIndex < argh_count <= MAX_ARGH
680 * @return Returns true if the worm collides with the specified argh.
682 static bool worm_argh_collision(struct worm
*w
, int arghIndex
)
686 retVal
= worm_in_rect(w
, arghx
[arghIndex
], arghy
[arghIndex
],
687 argh_size
- 1, argh_size
- 1);
693 * Find new coordinates for the food stored in foodx[index], foody[index]
694 * that don't collide with any other food or argh
696 * Ensure that 0 <= index < MAX_FOOD.
698 static void make_food(int index
) {
702 bool collisionDetected
= false;
706 /* make coordinates for a new food so that
707 the entire food lies within the FIELD */
708 x
= rb
->rand() % (FIELD_RECT_WIDTH
- food_size
);
709 y
= rb
->rand() % (FIELD_RECT_HEIGHT
- food_size
);
711 /* Ensure that the new food doesn't collide with any
712 existing foods or arghs.
713 If one or more corners of the new food hit any existing
714 argh or food a collision is detected.
717 food_collision(x
, y
) >= 0 ||
718 food_collision(x
, y
+ food_size
- 1) >= 0 ||
719 food_collision(x
+ food_size
- 1, y
) >= 0 ||
720 food_collision(x
+ food_size
- 1, y
+ food_size
- 1) >= 0 ||
721 argh_collision(x
, y
) >= 0 ||
722 argh_collision(x
, y
+ food_size
- 1) >= 0 ||
723 argh_collision(x
+ food_size
- 1, y
) >= 0 ||
724 argh_collision(x
+ food_size
- 1, y
+ food_size
- 1) >= 0;
726 /* use coordinates for further testing */
730 /* now test wether we accidently hit the worm with food ;) */
732 for (i
= 0; i
< worm_count
&& !collisionDetected
; i
++) {
733 collisionDetected
|= worm_food_collision(&worms
[i
], index
);
736 while (collisionDetected
);
741 * Clears a food from the lcd buffer.
742 * @param int index The index of the food arrays under which
743 * the coordinates of the desired food can be found. Ensure
744 * that the value is 0 <= index <= MAX_FOOD.
746 static void clear_food(int index
)
748 /* remove the old food from the screen */
749 rb
->lcd_set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
750 rb
->lcd_fillrect(foodx
[index
] + FIELD_RECT_X
,
751 foody
[index
] + FIELD_RECT_Y
,
752 food_size
, food_size
);
753 rb
->lcd_set_drawmode(DRMODE_SOLID
);
757 * Draws a food in the lcd buffer.
758 * @param int index The index of the food arrays under which
759 * the coordinates of the desired food can be found. Ensure
760 * that the value is 0 <= index <= MAX_FOOD.
762 static void draw_food(int index
)
764 /* draw the food object */
765 #ifdef HAVE_LCD_COLOR
766 rb
->lcd_set_foreground(COLOR_FOOD
);
768 rb
->lcd_fillrect(foodx
[index
] + FIELD_RECT_X
,
769 foody
[index
] + FIELD_RECT_Y
,
770 food_size
, food_size
);
771 rb
->lcd_set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
772 rb
->lcd_fillrect(foodx
[index
] + FIELD_RECT_X
+ 1,
773 foody
[index
] + FIELD_RECT_Y
+ 1,
774 food_size
- 2, food_size
- 2);
775 rb
->lcd_set_drawmode(DRMODE_SOLID
);
776 #ifdef HAVE_LCD_COLOR
777 rb
->lcd_set_foreground(COLOR_FG
);
782 * Find new coordinates for the argh stored in arghx[index], arghy[index]
783 * that don't collide with any other food or argh.
785 * Ensure that 0 <= index < argh_count < MAX_ARGH.
787 static void make_argh(int index
)
791 bool collisionDetected
= false;
795 /* make coordinates for a new argh so that
796 the entire food lies within the FIELD */
797 x
= rb
->rand() % (FIELD_RECT_WIDTH
- argh_size
);
798 y
= rb
->rand() % (FIELD_RECT_HEIGHT
- argh_size
);
800 /* Ensure that the new argh doesn't intersect with any
801 existing foods or arghs.
802 If one or more corners of the new argh hit any existing
803 argh or food an intersection is detected.
806 food_collision(x
, y
) >= 0 ||
807 food_collision(x
, y
+ argh_size
- 1) >= 0 ||
808 food_collision(x
+ argh_size
- 1, y
) >= 0 ||
809 food_collision(x
+ argh_size
- 1, y
+ argh_size
- 1) >= 0 ||
810 argh_collision(x
, y
) >= 0 ||
811 argh_collision(x
, y
+ argh_size
- 1) >= 0 ||
812 argh_collision(x
+ argh_size
- 1, y
) >= 0 ||
813 argh_collision(x
+ argh_size
- 1, y
+ argh_size
- 1) >= 0;
815 /* use the candidate coordinates to make a real argh */
819 /* now test wether we accidently hit the worm with argh ;) */
820 for (i
= 0; i
< worm_count
&& !collisionDetected
; i
++) {
821 collisionDetected
|= worm_argh_collision(&worms
[i
], index
);
822 collisionDetected
|= worm_argh_collision_in_moves(&worms
[i
], index
,
826 while (collisionDetected
);
831 * Draws an argh in the lcd buffer.
832 * @param int index The index of the argh arrays under which
833 * the coordinates of the desired argh can be found. Ensure
834 * that the value is 0 <= index < argh_count <= MAX_ARGH.
836 static void draw_argh(int index
)
838 /* draw the new argh */
839 #ifdef HAVE_LCD_COLOR
840 rb
->lcd_set_foreground(COLOR_ARGH
);
842 rb
->lcd_fillrect(arghx
[index
] + FIELD_RECT_X
,
843 arghy
[index
] + FIELD_RECT_Y
,
844 argh_size
, argh_size
);
845 #ifdef HAVE_LCD_COLOR
846 rb
->lcd_set_foreground(COLOR_FG
);
850 static void virtual_player(struct worm
*w
);
852 * Initialzes the specified worm with INITIAL_WORM_LENGTH
853 * and the tail at the specified position. The worm will
854 * be initialized alive and creeping EAST.
855 * @param struct worm *w The worm that is to be initialized
856 * @param int x The x coordinate at which the tail of the worm starts.
857 * x must be 0 <= x < FIELD_RECT_WIDTH.
858 * @param int y The y coordinate at which the tail of the worm starts
859 * y must be 0 <= y < FIELD_RECT_WIDTH.
861 static void init_worm(struct worm
*w
, int x
, int y
){
862 /* initialize the worm size */
866 w
->x
[w
->head
] = x
+ 1;
872 /* set the initial direction the worm creeps to */
876 w
->growing
= INITIAL_WORM_LENGTH
- 1;
878 w
->fetch_worm_direction
= virtual_player
;
882 * Writes the direction that was stored for
883 * human player 1 into the specified worm. This function
884 * may be used to be stored in worm.fetch_worm_direction.
886 * the direction is read from player1_dir.
887 * @param struct worm *w - The worm of which the direction
890 static void human_player1(struct worm
*w
) {
891 set_worm_dir(w
, player1_dir
);
895 * Writes the direction that was stored for
896 * human player 2 into the specified worm. This function
897 * may be used to be stored in worm.fetch_worm_direction.
899 * the direction is read from player2_dir.
900 * @param struct worm *w - The worm of which the direction
903 static void human_player2(struct worm
*w
) {
904 set_worm_dir(w
, player2_dir
);
908 * Writes the direction that was stored for
909 * human player using a remote control
910 * into the specified worm. This function
911 * may be used to be stored in worm.fetch_worm_direction.
913 * the direction is read from player3_dir.
914 * @param struct worm *w - The worm of which the direction
917 static void remote_player(struct worm
*w
) {
918 set_worm_dir(w
, player3_dir
);
922 * Initializes the worm-, food- and argh-arrays, draws a frame,
923 * makes some food and argh and display all that stuff.
925 static void init_wormlet(void)
929 for (i
= 0; i
< worm_count
; i
++) {
930 /* Initialize all the worm coordinates to center. */
931 int x
= (int)(FIELD_RECT_WIDTH
/ 2);
932 int y
= (int)((FIELD_RECT_HEIGHT
- 20)/ 2) + i
* 10;
934 init_worm(&worms
[i
], x
, y
);
942 worms
[0].fetch_worm_direction
= human_player1
;
947 worms
[1].fetch_worm_direction
= remote_player
;
949 worms
[1].fetch_worm_direction
= human_player2
;
954 worms
[2].fetch_worm_direction
= human_player2
;
957 /* Needed when the game is restarted using BTN_STOPRESET */
958 rb
->lcd_clear_display();
960 /* make and display some food and argh */
961 argh_count
= MAX_FOOD
;
962 for (i
= 0; i
< MAX_FOOD
; i
++) {
969 /* draw the game field */
970 rb
->lcd_set_drawmode(DRMODE_COMPLEMENT
);
971 rb
->lcd_fillrect(0, 0, FIELD_RECT_WIDTH
+ 2, FIELD_RECT_HEIGHT
+ 2);
972 rb
->lcd_fillrect(1, 1, FIELD_RECT_WIDTH
, FIELD_RECT_HEIGHT
);
973 rb
->lcd_set_drawmode(DRMODE_SOLID
);
975 /* make everything visible */
981 * Move the worm one step further if it is alive.
982 * The direction in which the worm moves is taken from dirx and diry.
983 * move_worm decreases growing if > 0. While the worm is growing the tail
985 * @param struct worm *w The worm to move. w must not be NULL.
987 static void move_worm(struct worm
*w
)
990 /* determine the head point and its precessor */
991 int headx
= w
->x
[w
->head
];
992 int heady
= w
->y
[w
->head
];
993 int prehead
= (w
->head
+ MAX_WORM_SEGMENTS
- 1) % MAX_WORM_SEGMENTS
;
994 int preheadx
= w
->x
[prehead
];
995 int preheady
= w
->y
[prehead
];
997 /* determine the old direction */
1000 if (headx
== preheadx
) {
1002 olddiry
= (heady
> preheady
) ? 1 : -1;
1005 olddirx
= (headx
> preheadx
) ? 1 : -1;
1009 a change of direction means a new segment
1011 if (olddirx
!= w
->dirx
||
1012 olddiry
!= w
->diry
) {
1013 w
->head
= (w
->head
+ 1) % MAX_WORM_SEGMENTS
;
1016 /* new head position */
1017 w
->x
[w
->head
] = headx
+ w
->dirx
;
1018 w
->y
[w
->head
] = heady
+ w
->diry
;
1021 /* while the worm is growing no tail procession is necessary */
1022 if (w
->growing
> 0) {
1023 /* update the worms grow state */
1027 /* if the worm isn't growing the tail has to be dragged */
1029 /* index of the end of the tail segment */
1030 int tail_segment_end
= (w
->tail
+ 1) % MAX_WORM_SEGMENTS
;
1032 /* drag the end of the tail */
1033 /* only one coordinate has to be altered. Here it is
1034 determined which one */
1035 int dir
= 0; /* specifies wether the coord has to be in- or decreased */
1036 if (w
->x
[w
->tail
] == w
->x
[tail_segment_end
]) {
1037 dir
= (w
->y
[w
->tail
] - w
->y
[tail_segment_end
] < 0) ? 1 : -1;
1038 w
->y
[w
->tail
] += dir
;
1040 dir
= (w
->x
[w
->tail
] - w
->x
[tail_segment_end
] < 0) ? 1 : -1;
1041 w
->x
[w
->tail
] += dir
;
1044 /* when the tail has been dragged so far that it meets
1045 the next segment start the tail segment is obsolete and
1047 if (w
->x
[w
->tail
] == w
->x
[tail_segment_end
] &&
1048 w
->y
[w
->tail
] == w
->y
[tail_segment_end
]){
1050 /* drop the last tail point */
1051 w
->tail
= tail_segment_end
;
1058 * Draws the head and clears the tail of the worm in
1059 * the display buffer. lcd_update() is NOT called thus
1060 * the caller has to take care that the buffer is displayed.
1062 static void draw_worm(struct worm
*w
)
1064 #ifdef HAVE_LCD_COLOR
1065 rb
->lcd_set_foreground(COLOR_WORM
);
1067 /* draw the new head */
1068 int x
= w
->x
[w
->head
];
1069 int y
= w
->y
[w
->head
];
1070 if (x
>= 0 && x
< FIELD_RECT_WIDTH
&& y
>= 0 && y
< FIELD_RECT_HEIGHT
) {
1071 rb
->lcd_drawpixel(x
+ FIELD_RECT_X
, y
+ FIELD_RECT_Y
);
1074 rb
->lcd_set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
1076 /* clear the space behind the worm */
1079 if (x
>= 0 && x
< FIELD_RECT_WIDTH
&& y
>= 0 && y
< FIELD_RECT_HEIGHT
) {
1080 rb
->lcd_drawpixel(x
+ FIELD_RECT_X
, y
+ FIELD_RECT_Y
);
1082 rb
->lcd_set_drawmode(DRMODE_SOLID
);
1083 #ifdef HAVE_LCD_COLOR
1084 rb
->lcd_set_foreground(COLOR_FG
);
1089 * Checks wether the coordinate is part of the worm. Returns
1090 * true if any part of the worm was hit - including the head.
1091 * @param x int The x coordinate
1092 * @param y int The y coordinate
1093 * @return int The index of the worm arrays that contain x, y.
1094 * Returns -1 if the coordinates are not part of the worm.
1096 static int specific_worm_collision(struct worm
*w
, int x
, int y
)
1100 /* get_worm_array_length is expensive -> buffer the value */
1101 int wormLength
= get_worm_array_length(w
);
1104 /* test each entry that is part of the worm */
1105 for (i
= 0; i
< wormLength
&& retVal
== -1; i
++) {
1107 /* The iteration iterates the length of the worm.
1108 Here's the conversion to the true indices within the worm arrays. */
1109 int linestart
= (w
->tail
+ i
) % MAX_WORM_SEGMENTS
;
1110 int lineend
= (linestart
+ 1) % MAX_WORM_SEGMENTS
;
1111 bool samex
= (w
->x
[linestart
] == x
) && (w
->x
[lineend
] == x
);
1112 bool samey
= (w
->y
[linestart
] == y
) && (w
->y
[lineend
] == y
);
1113 if (samex
|| samey
){
1114 int test
, min
, max
, tmp
;
1117 min
= w
->x
[linestart
];
1118 max
= w
->x
[lineend
];
1121 min
= w
->y
[linestart
];
1122 max
= w
->y
[lineend
];
1127 min
= MIN(min
, max
);
1128 max
= MAX(tmp
, max
);
1130 if (min
<= test
&& test
<= max
) {
1139 * Increases the length of the specified worm by marking
1140 * that it may grow by len pixels. Note that the worm has
1141 * to move to make the growing happen.
1142 * @param worm *w The worm that is to be altered.
1143 * @param int len A positive value specifying the amount of
1144 * pixels the worm may grow.
1146 static void add_growing(struct worm
*w
, int len
) {
1151 * Determins the worm that is at the coordinates x, y. The parameter
1152 * w is a switch parameter that changes the functionality of worm_collision.
1153 * If w is specified and x,y hits the head of w NULL is returned.
1154 * This is a useful way to determine wether the head of w hits
1155 * any worm but including itself but excluding its own head.
1156 * (It hits always its own head ;))
1157 * If w is set to NULL worm_collision returns any worm including all heads
1158 * that is at position of x,y.
1159 * @param struct worm *w The worm of which the head should be excluded in
1160 * the test. w may be set to NULL.
1161 * @param int x The x coordinate that is checked
1162 * @param int y The y coordinate that is checkec
1163 * @return struct worm* The worm that has been hit by x,y. If no worm
1164 * was at the position NULL is returned.
1166 static struct worm
* worm_collision(struct worm
*w
, int x
, int y
){
1167 struct worm
*retVal
= NULL
;
1169 for (i
= 0; (i
< worm_count
) && (retVal
== NULL
); i
++) {
1170 int collision_at
= specific_worm_collision(&worms
[i
], x
, y
);
1171 if (collision_at
!= -1) {
1172 if (!(w
== &worms
[i
] && collision_at
== w
->head
)){
1181 * Returns true if the head of the worm just has
1182 * crossed the field boundaries.
1183 * @return bool true if the worm just has wrapped.
1185 static bool field_collision(struct worm
*w
)
1187 bool retVal
= false;
1188 if ((w
->x
[w
->head
] >= FIELD_RECT_WIDTH
) ||
1189 (w
->y
[w
->head
] >= FIELD_RECT_HEIGHT
) ||
1190 (w
->x
[w
->head
] < 0) ||
1191 (w
->y
[w
->head
] < 0))
1200 * Returns true if the specified coordinates are within the
1201 * field specified by the FIELD_RECT_XXX constants.
1202 * @param int x The x coordinate of the point that is investigated
1203 * @param int y The y coordinate of the point that is investigated
1204 * @return bool Returns false if x,y specifies a point outside the
1207 static bool is_in_field_rect(int x
, int y
) {
1208 bool retVal
= false;
1209 retVal
= (x
>= 0 && x
< FIELD_RECT_WIDTH
&&
1210 y
>= 0 && y
< FIELD_RECT_HEIGHT
);
1215 * Checks and returns wether the head of the w
1216 * is colliding with something currently.
1217 * @return int One of the values:
1224 static int check_collision(struct worm
*w
)
1226 int retVal
= COLLISION_NONE
;
1228 if (worm_collision(w
, w
->x
[w
->head
], w
->y
[w
->head
]) != NULL
)
1229 retVal
= COLLISION_WORM
;
1231 if (food_collision(w
->x
[w
->head
], w
->y
[w
->head
]) >= 0)
1232 retVal
= COLLISION_FOOD
;
1234 if (argh_collision(w
->x
[w
->head
], w
->y
[w
->head
]) >= 0)
1235 retVal
= COLLISION_ARGH
;
1237 if (field_collision(w
))
1238 retVal
= COLLISION_FIELD
;
1244 * Returns the index of the food that is closest to the point
1245 * specified by x, y. This index may be used in the foodx and
1247 * @param int x The x coordinate of the point
1248 * @param int y The y coordinate of the point
1249 * @return int A value usable as index in foodx and foody.
1251 static int get_nearest_food(int x
, int y
){
1252 int nearestfood
= 0;
1253 int olddistance
= FIELD_RECT_WIDTH
+ FIELD_RECT_HEIGHT
;
1257 for (foodindex
= 0; foodindex
< MAX_FOOD
; foodindex
++) {
1259 deltax
= foodx
[foodindex
] - x
;
1260 deltay
= foody
[foodindex
] - y
;
1261 deltax
= deltax
> 0 ? deltax
: deltax
* (-1);
1262 deltay
= deltay
> 0 ? deltay
: deltay
* (-1);
1263 distance
= deltax
+ deltay
;
1265 if (distance
< olddistance
) {
1266 olddistance
= distance
;
1267 nearestfood
= foodindex
;
1274 * Returns wether the specified position is next to the worm
1275 * and in the direction the worm looks. Use this method to
1276 * test wether this position would be hit with the next move of
1277 * the worm unless the worm changes its direction.
1278 * @param struct worm *w - The worm to be investigated
1279 * @param int x - The x coordinate of the position to test.
1280 * @param int y - The y coordinate of the position to test.
1281 * @return Returns true if the worm will hit the position unless
1282 * it change its direction before the next move.
1284 static bool is_in_front_of_worm(struct worm
*w
, int x
, int y
) {
1285 bool infront
= false;
1286 int deltax
= x
- w
->x
[w
->head
];
1287 int deltay
= y
- w
->y
[w
->head
];
1290 infront
= (w
->diry
* deltay
) > 0;
1292 infront
= (w
->dirx
* deltax
) > 0;
1298 * Returns true if the worm will collide with the next move unless
1299 * it changes its direction.
1300 * @param struct worm *w - The worm to be investigated.
1301 * @return Returns true if the worm will collide with the next move
1302 * unless it changes its direction.
1304 static bool will_worm_collide(struct worm
*w
) {
1305 int x
= w
->x
[w
->head
] + w
->dirx
;
1306 int y
= w
->y
[w
->head
] + w
->diry
;
1307 bool retVal
= !is_in_field_rect(x
, y
);
1309 retVal
= (argh_collision(x
, y
) != -1);
1313 retVal
= (worm_collision(w
, x
, y
) != NULL
);
1320 * may be used to be stored in worm.fetch_worm_direction for
1321 * worms that are not controlled by humans but by artificial stupidity.
1322 * A direction is searched that doesn't lead to collision but to the nearest
1323 * food - but not very intelligent. The direction is written to the specified
1325 * @param struct worm *w - The worm of which the direction
1328 static void virtual_player(struct worm
*w
) {
1330 int plana
, planb
, planc
;
1331 /* find the next lunch */
1332 int nearestfood
= get_nearest_food(w
->x
[w
->head
], w
->y
[w
->head
]);
1334 /* determine in which direction it is */
1336 /* in front of me? */
1337 bool infront
= is_in_front_of_worm(w
, foodx
[nearestfood
], foody
[nearestfood
]);
1339 /* left right of me? */
1340 int olddir
= get_worm_dir(w
);
1341 set_worm_dir(w
, (olddir
+ 1) % 4);
1342 isright
= is_in_front_of_worm(w
, foodx
[nearestfood
], foody
[nearestfood
]);
1343 set_worm_dir(w
, olddir
);
1345 /* detect situation, set strategy */
1349 planb
= (olddir
+ 1) % 4;
1350 planc
= (olddir
+ 3) % 4;
1353 planb
= (olddir
+ 3) % 4;
1354 planc
= (olddir
+ 1) % 4;
1358 plana
= (olddir
+ 1) % 4;
1360 planc
= (olddir
+ 3) % 4;
1362 plana
= (olddir
+ 3) % 4;
1364 planc
= (olddir
+ 1) % 4;
1368 /* test for collision */
1369 set_worm_dir(w
, plana
);
1370 if (will_worm_collide(w
)){
1373 set_worm_dir(w
, planb
);
1375 /* test for collision */
1376 if (will_worm_collide(w
)) {
1379 set_worm_dir(w
, planc
);
1385 * prints out the score board with all the status information
1388 static void score_board(void)
1394 rb
->lcd_set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
1395 rb
->lcd_fillrect(FIELD_RECT_WIDTH
+ 2, 0, LCD_WIDTH
- FIELD_RECT_WIDTH
- 2, LCD_HEIGHT
);
1396 rb
->lcd_set_drawmode(DRMODE_SOLID
);
1397 for (i
= 0; i
< worm_count
; i
++) {
1398 int score
= get_score(&worms
[i
]);
1401 if (worms
[i
].fetch_worm_direction
!= virtual_player
){
1402 if (highscore
< score
) {
1408 rb
->snprintf(buf
, sizeof (buf
),"Len:%d", score
);
1411 switch (check_collision(&worms
[i
])) {
1412 case COLLISION_NONE
:
1413 if (worms
[i
].growing
> 0)
1423 case COLLISION_WORM
:
1427 case COLLISION_FOOD
:
1431 case COLLISION_ARGH
:
1435 case COLLISION_FIELD
:
1439 rb
->lcd_putsxy(FIELD_RECT_WIDTH
+ 3, y
, buf
);
1440 rb
->lcd_putsxy(FIELD_RECT_WIDTH
+ 3, y
+8, buf2
);
1442 if (!worms
[i
].alive
){
1443 rb
->lcd_set_drawmode(DRMODE_COMPLEMENT
);
1444 rb
->lcd_fillrect(FIELD_RECT_WIDTH
+ 2, y
,
1445 LCD_WIDTH
- FIELD_RECT_WIDTH
- 2, 17);
1446 rb
->lcd_set_drawmode(DRMODE_SOLID
);
1450 rb
->snprintf(buf
, sizeof(buf
), "Hs: %d", highscore
);
1451 #ifndef DEBUG_WORMLET
1452 rb
->lcd_putsxy(FIELD_RECT_WIDTH
+ 3, LCD_HEIGHT
- 8, buf
);
1454 rb
->lcd_putsxy(FIELD_RECT_WIDTH
+ 3, LCD_HEIGHT
- 8, debugout
);
1459 * Checks for collisions of the worm and its environment and
1460 * takes appropriate actions like growing the worm or killing it.
1461 * @return bool Returns true if the worm is dead. Returns
1462 * false if the worm is healthy, up and creeping.
1464 static bool process_collisions(struct worm
*w
)
1468 w
->alive
&= !field_collision(w
);
1472 /* check if food was eaten */
1473 index
= food_collision(w
->x
[w
->head
], w
->y
[w
->head
]);
1481 for (i
= 0; i
< arghs_per_food
; i
++) {
1483 if (argh_count
> MAX_ARGH
)
1484 argh_count
= MAX_ARGH
;
1485 make_argh(argh_count
- 1);
1486 draw_argh(argh_count
- 1);
1489 add_growing(w
, worm_food
);
1494 /* check if argh was eaten */
1496 index
= argh_collision(w
->x
[w
->head
], w
->y
[w
->head
]);
1501 if (worm_collision(w
, w
->x
[w
->head
], w
->y
[w
->head
]) != NULL
) {
1511 * The main loop of the game.
1512 * @return bool Returns true if the game ended
1513 * with a dead worm. Returns false if the user
1514 * aborted the game manually.
1516 static int run(void)
1519 int wormDead
= false;
1520 bool paused
= false;
1522 /* ticks are counted to compensate speed variations */
1523 long cycle_start
= 0, cycle_end
= 0;
1524 #ifdef DEBUG_WORMLET
1525 int ticks_to_max_cycle_reset
= 20;
1530 /* initialize the board and so on */
1533 cycle_start
= *rb
->current_tick
;
1534 /* change the direction of the worm */
1538 long cycle_duration
=0;
1540 #ifdef HAS_BUTTON_HOLD
1541 if (rb
->button_hold())
1546 case BTN_STARTPAUSE
:
1551 return 1; /* restart game */
1559 return 2; /* back to menu */
1566 if (players
== 1 && !use_remote
) {
1567 player1_dir
= NORTH
;
1572 if (players
== 1 && !use_remote
) {
1573 player1_dir
= SOUTH
;
1578 if (players
!= 1 || use_remote
) {
1579 player1_dir
= (player1_dir
+ 3) % 4;
1586 if (players
!= 1 || use_remote
) {
1587 player1_dir
= (player1_dir
+ 1) % 4;
1594 case BTN_PLAYER2_DIR1
:
1595 player2_dir
= (player2_dir
+ 3) % 4;
1598 case BTN_PLAYER2_DIR2
:
1599 player2_dir
= (player2_dir
+ 1) % 4;
1605 player3_dir
= (player3_dir
+ 1) % 4;
1609 player3_dir
= (player3_dir
+ 3) % 4;
1615 for (i
= 0; i
< worm_count
; i
++) {
1616 worms
[i
].fetch_worm_direction(&worms
[i
]);
1620 for (i
= 0; i
< worm_count
; i
++){
1621 struct worm
*w
= &worms
[i
];
1623 wormDead
&= process_collisions(w
);
1628 if (button
== BTN_STOPRESET
) {
1632 /* here the wormlet game cycle ends
1633 thus the current tick is stored
1635 cycle_end
= *rb
->current_tick
;
1637 /* The duration of the game cycle */
1638 cycle_duration
= cycle_end
- cycle_start
;
1639 cycle_duration
= MAX(0, cycle_duration
);
1640 cycle_duration
= MIN(speed
-1, cycle_duration
);
1643 #ifdef DEBUG_WORMLET
1644 ticks_to_max_cycle_reset
--;
1645 if (ticks_to_max_cycle_reset
<= 0) {
1649 if (max_cycle
< cycle_duration
) {
1650 max_cycle
= cycle_duration
;
1651 ticks_to_max_cycle_reset
= 20;
1653 rb
->snprintf(buf
, sizeof buf
, "ticks %d", max_cycle
);
1657 /* adjust the number of ticks to wait for a button.
1658 This ensures that a complete game cycle including
1659 user input runs in constant time */
1660 button
= rb
->button_get_w_tmo(speed
- cycle_duration
);
1661 cycle_start
= *rb
->current_tick
;
1664 rb
->splash(HZ
*2, "Game Over!");
1666 return 2; /* back to menu */
1669 #ifdef DEBUG_WORMLET
1672 * Just a test routine that checks that worm_food_collision works
1673 * in some typical situations.
1675 static void test_worm_food_collision(void) {
1676 int collision_count
= 0;
1678 rb
->lcd_clear_display();
1679 init_worm(&worms
[0], 10, 10);
1680 add_growing(&worms
[0], 10);
1681 set_worm_dir(&worms
[0], EAST
);
1682 for (i
= 0; i
< 10; i
++) {
1683 move_worm(&worms
[0]);
1684 draw_worm(&worms
[0]);
1687 set_worm_dir(&worms
[0], SOUTH
);
1688 for (i
= 0; i
< 10; i
++) {
1689 move_worm(&worms
[0]);
1690 draw_worm(&worms
[0]);
1695 for (foody
[0] = 20; foody
[0] > 0; foody
[0] --) {
1698 draw_worm(&worms
[0]);
1700 collision
= worm_food_collision(&worms
[0], 0);
1704 rb
->snprintf(buf
, sizeof buf
, "collisions: %d", collision_count
);
1705 rb
->lcd_putsxy(0, LCD_HEIGHT
-8, buf
);
1708 if (collision_count
!= food_size
) {
1709 rb
->button_get(true);
1714 for (foodx
[0] = 30; foodx
[0] > 0; foodx
[0] --) {
1717 draw_worm(&worms
[0]);
1719 collision
= worm_food_collision(&worms
[0], 0);
1723 rb
->snprintf(buf
, sizeof buf
, "collisions: %d", collision_count
);
1724 rb
->lcd_putsxy(0, LCD_HEIGHT
-8, buf
);
1727 if (collision_count
!= food_size
* 2) {
1728 rb
->button_get(true);
1733 static bool expensive_worm_in_rect(struct worm
*w
, int rx
, int ry
, int rw
, int rh
){
1735 bool retVal
= false;
1736 for (x
= rx
; x
< rx
+ rw
; x
++){
1737 for (y
= ry
; y
< ry
+ rh
; y
++) {
1738 if (specific_worm_collision(w
, x
, y
) != -1) {
1746 static void test_worm_argh_collision(void) {
1749 int collision_count
= 0;
1750 rb
->lcd_clear_display();
1751 init_worm(&worms
[0], 10, 10);
1752 add_growing(&worms
[0], 40);
1753 for (dir
= 0; dir
< 4; dir
++) {
1754 set_worm_dir(&worms
[0], (EAST
+ dir
) % 4);
1755 for (i
= 0; i
< 10; i
++) {
1756 move_worm(&worms
[0]);
1757 draw_worm(&worms
[0]);
1762 for (arghy
[0] = 0; arghy
[0] < FIELD_RECT_HEIGHT
- argh_size
; arghy
[0]++){
1766 collision
= worm_argh_collision(&worms
[0], 0);
1770 rb
->snprintf(buf
, sizeof buf
, "collisions: %d", collision_count
);
1771 rb
->lcd_putsxy(0, LCD_HEIGHT
-8, buf
);
1774 if (collision_count
!= argh_size
* 2) {
1775 rb
->button_get(true);
1779 for (arghx
[0] = 0; arghx
[0] < FIELD_RECT_HEIGHT
- argh_size
; arghx
[0]++){
1783 collision
= worm_argh_collision(&worms
[0], 0);
1787 rb
->snprintf(buf
, sizeof buf
, "collisions: %d", collision_count
);
1788 rb
->lcd_putsxy(0, LCD_HEIGHT
-8, buf
);
1791 if (collision_count
!= argh_size
* 4) {
1792 rb
->button_get(true);
1796 static int testline_in_rect(void) {
1797 int testfailed
= -1;
1810 if (!line_in_rect(x1
, y1
, x2
, y2
, rx
, ry
, rw
, rh
) &&
1811 !line_in_rect(x2
, y2
, x1
, y1
, rx
, ry
, rw
, rh
)) {
1812 rb
->lcd_drawrect(rx
, ry
, rw
, rh
);
1813 rb
->lcd_drawline(x1
, y1
, x2
, y2
);
1815 rb
->lcd_putsxy(0, 0, "failed 1");
1816 rb
->button_get(true);
1822 if (!line_in_rect(x1
, y1
, x2
, y2
, rx
, ry
, rw
, rh
) &&
1823 !line_in_rect(x2
, y2
, x1
, y1
, rx
, ry
, rw
, rh
)) {
1824 rb
->lcd_drawrect(rx
, ry
, rw
, rh
);
1825 rb
->lcd_drawline(x1
, y1
, x2
, y2
);
1826 rb
->lcd_putsxy(0, 0, "failed 2");
1828 rb
->button_get(true);
1834 if (!line_in_rect(x1
, y1
, x2
, y2
, rx
, ry
, rw
, rh
) &&
1835 !line_in_rect(x2
, y2
, x1
, y1
, rx
, ry
, rw
, rh
)) {
1836 rb
->lcd_drawrect(rx
, ry
, rw
, rh
);
1837 rb
->lcd_drawline(x1
, y1
, x2
, y2
);
1838 rb
->lcd_putsxy(0, 0, "failed 3");
1840 rb
->button_get(true);
1846 if (!line_in_rect(x1
, y1
, x2
, y2
, rx
, ry
, rw
, rh
) &&
1847 !line_in_rect(x2
, y2
, x1
, y1
, rx
, ry
, rw
, rh
)) {
1848 rb
->lcd_drawrect(rx
, ry
, rw
, rh
);
1849 rb
->lcd_drawline(x1
, y1
, x2
, y2
);
1850 rb
->lcd_putsxy(0, 0, "failed 4");
1852 rb
->button_get(true);
1858 if (line_in_rect(x1
, y1
, x2
, y2
, rx
, ry
, rw
, rh
) ||
1859 line_in_rect(x2
, y2
, x1
, y1
, rx
, ry
, rw
, rh
)) {
1860 rb
->lcd_drawrect(rx
, ry
, rw
, rh
);
1861 rb
->lcd_drawline(x1
, y1
, x2
, y2
);
1862 rb
->lcd_putsxy(0, 0, "failed 5");
1864 rb
->button_get(true);
1871 if (line_in_rect(x1
, y1
, x2
, y2
, rx
, ry
, rw
, rh
) ||
1872 line_in_rect(x2
, y2
, x1
, y1
, rx
, ry
, rw
, rh
)) {
1873 rb
->lcd_drawrect(rx
, ry
, rw
, rh
);
1874 rb
->lcd_drawline(x1
, y1
, x2
, y2
);
1875 rb
->lcd_putsxy(0, 0, "failed 6");
1877 rb
->button_get(true);
1886 if (!line_in_rect(x1
, y1
, x2
, y2
, rx
, ry
, rw
, rh
) &&
1887 !line_in_rect(x2
, y2
, x1
, y1
, rx
, ry
, rw
, rh
)) {
1888 rb
->lcd_drawrect(rx
, ry
, rw
, rh
);
1889 rb
->lcd_drawline(x1
, y1
, x2
, y2
);
1890 rb
->lcd_putsxy(0, 0, "failed 7");
1892 rb
->button_get(true);
1898 if (!line_in_rect(x1
, y1
, x2
, y2
, rx
, ry
, rw
, rh
) &&
1899 !line_in_rect(x2
, y2
, x1
, y1
, rx
, ry
, rw
, rh
)) {
1900 rb
->lcd_drawrect(rx
, ry
, rw
, rh
);
1901 rb
->lcd_drawline(x1
, y1
, x2
, y2
);
1902 rb
->lcd_putsxy(0, 0, "failed 8");
1904 rb
->button_get(true);
1910 if (!line_in_rect(x1
, y1
, x2
, y2
, rx
, ry
, rw
, rh
) &&
1911 !line_in_rect(x2
, y2
, x1
, y1
, rx
, ry
, rw
, rh
)) {
1912 rb
->lcd_drawrect(rx
, ry
, rw
, rh
);
1913 rb
->lcd_drawline(x1
, y1
, x2
, y2
);
1914 rb
->lcd_putsxy(0, 0, "failed 9");
1916 rb
->button_get(true);
1922 if (!line_in_rect(x1
, y1
, x2
, y2
, rx
, ry
, rw
, rh
) &&
1923 !line_in_rect(x2
, y2
, x1
, y1
, rx
, ry
, rw
, rh
)) {
1924 rb
->lcd_drawrect(rx
, ry
, rw
, rh
);
1925 rb
->lcd_drawline(x1
, y1
, x2
, y2
);
1926 rb
->lcd_putsxy(0, 0, "failed 10");
1928 rb
->button_get(true);
1934 if (line_in_rect(x1
, y1
, x2
, y2
, rx
, ry
, rw
, rh
) ||
1935 line_in_rect(x2
, y2
, x1
, y1
, rx
, ry
, rw
, rh
)) {
1936 rb
->lcd_drawrect(rx
, ry
, rw
, rh
);
1937 rb
->lcd_drawline(x1
, y1
, x2
, y2
);
1938 rb
->lcd_putsxy(0, 0, "failed 11");
1940 rb
->button_get(true);
1947 if (line_in_rect(x1
, y1
, x2
, y2
, rx
, ry
, rw
, rh
) ||
1948 line_in_rect(x2
, y2
, x1
, y1
, rx
, ry
, rw
, rh
)) {
1949 rb
->lcd_drawrect(rx
, ry
, rw
, rh
);
1950 rb
->lcd_drawline(x1
, y1
, x2
, y2
);
1951 rb
->lcd_putsxy(0, 0, "failed 12");
1953 rb
->button_get(true);
1967 if (!(line_in_rect(x1
, y1
, x2
, y2
, rx
, ry
, rw
, rh
) &&
1968 line_in_rect(x2
, y2
, x1
, y1
, rx
, ry
, rw
, rh
))) {
1969 rb
->lcd_drawrect(rx
, ry
, rw
, rh
);
1970 rb
->lcd_drawline(x1
, y1
, x2
, y2
);
1971 rb
->lcd_putsxy(0, 0, "failed 13");
1973 rb
->button_get(true);
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_drawline(x1
, y1
, x2
, y2
);
1990 rb
->lcd_invertrect(rx
, ry
, rw
, rh
);
1991 rb
->lcd_putsxy(0, 0, "failed 14");
1993 rb
->button_get(true);
1997 rb
->lcd_clear_display();
2003 * Just a test routine to test wether specific_worm_collision might work properly
2005 static int test_specific_worm_collision(void) {
2011 rb
->lcd_clear_display();
2012 init_worm(&worms
[0], 10, 20);
2013 add_growing(&worms
[0], 20 - INITIAL_WORM_LENGTH
);
2015 for (dir
= EAST
; dir
< EAST
+ 4; dir
++) {
2017 set_worm_dir(&worms
[0], dir
% 4);
2018 for (i
= 0; i
< 5; i
++) {
2019 if (!(dir
% 4 == NORTH
&& i
== 9)) {
2020 move_worm(&worms
[0]);
2021 draw_worm(&worms
[0]);
2026 for (y
= 15; y
< 30; y
++){
2027 for (x
= 5; x
< 20; x
++) {
2028 if (specific_worm_collision(&worms
[0], x
, y
) != -1) {
2031 rb
->lcd_invertpixel(x
+ FIELD_RECT_X
, y
+ FIELD_RECT_Y
);
2032 rb
->snprintf(buf
, sizeof buf
, "collisions %d", collisions
);
2033 rb
->lcd_putsxy(0, LCD_HEIGHT
- 8, buf
);
2037 if (collisions
!= 21) {
2038 rb
->button_get(true);
2043 static void test_make_argh(void){
2048 int last_failures
= 0;
2050 rb
->lcd_clear_display();
2053 for (worm_idx
= 0; worm_idx
< worm_count
; worm_idx
++) {
2054 init_worm(&worms
[worm_idx
], 10 + worm_idx
* 20, 20);
2055 add_growing(&worms
[worm_idx
], 40 - INITIAL_WORM_LENGTH
);
2058 for (dir
= EAST
; dir
< EAST
+ 4; dir
++) {
2059 for (worm_idx
= 0; worm_idx
< worm_count
; worm_idx
++) {
2060 set_worm_dir(&worms
[worm_idx
], dir
% 4);
2061 for (i
= 0; i
< 10; i
++) {
2062 if (!(dir
% 4 == NORTH
&& i
== 9)) {
2063 move_worm(&worms
[worm_idx
]);
2064 draw_worm(&worms
[worm_idx
]);
2072 for (seed
= 0; hit
< 20; seed
+= 2) {
2076 x
= rb
->rand() % (FIELD_RECT_WIDTH
- argh_size
);
2077 y
= rb
->rand() % (FIELD_RECT_HEIGHT
- argh_size
);
2079 for (worm_idx
= 0; worm_idx
< worm_count
; worm_idx
++){
2080 if (expensive_worm_in_rect(&worms
[worm_idx
], x
, y
, argh_size
, argh_size
)) {
2084 tries
= make_argh(0);
2085 if ((x
== arghx
[0] && y
== arghy
[0]) || tries
< 2) {
2089 rb
->snprintf(buf
, sizeof buf
, "(%d;%d) fail%d try%d", x
, y
, failures
, tries
);
2090 rb
->lcd_putsxy(0, LCD_HEIGHT
- 8, buf
);
2092 rb
->lcd_invertrect(x
+ FIELD_RECT_X
, y
+ FIELD_RECT_Y
, argh_size
, argh_size
);
2096 rb
->lcd_invertrect(x
+ FIELD_RECT_X
, y
+ FIELD_RECT_Y
, argh_size
, argh_size
);
2097 rb
->lcd_clearrect(arghx
[0] + FIELD_RECT_X
, arghy
[0] + FIELD_RECT_Y
, argh_size
, argh_size
);
2099 if (failures
> last_failures
) {
2100 rb
->button_get(true);
2102 last_failures
= failures
;
2109 static void test_worm_argh_collision_in_moves(void) {
2112 rb
->lcd_clear_display();
2113 init_worm(&worms
[0], 10, 20);
2119 set_worm_dir(&worms
[0], EAST
);
2120 for (i
= 0; i
< 20; i
++) {
2122 move_worm(&worms
[0]);
2123 draw_worm(&worms
[0]);
2124 if (worm_argh_collision_in_moves(&worms
[0], 0, 5)){
2127 rb
->snprintf(buf
, sizeof buf
, "in 5 moves hits: %d", hit_count
);
2128 rb
->lcd_putsxy(0, LCD_HEIGHT
- 8, buf
);
2131 if (hit_count
!= argh_size
+ 5) {
2132 rb
->button_get(true);
2135 #endif /* DEBUG_WORMLET */
2137 extern bool use_old_rect
;
2140 * These are additional functions required to set various wormlet settings
2141 * and to enable saving of settings and high score
2145 Sets the total number of worms, both human and machine
2147 bool set_worm_num_worms(void)
2150 static const struct opt_items num_worms_option
[3] = {
2156 ret
= rb
->set_option("Number Of Worms", &worm_count
,INT
, num_worms_option
, 3, NULL
);
2157 if (worm_count
< players
) {
2164 Sets the number of human players
2166 bool set_worm_num_players(void)
2169 static const struct opt_items num_players_option
[4] = {
2176 ret
= rb
->set_option("Number of Players", &players
, INT
,num_players_option
, 4, NULL
);
2177 if (players
> worm_count
) {
2178 worm_count
= players
;
2187 Sets the size of each argh block created
2189 bool set_worm_argh_size(void)
2191 static const struct opt_items argh_size_option
[11] = {
2205 return rb
->set_option("Argh Size", &argh_size
,INT
,argh_size_option
, 11, NULL
);
2209 Sets the amount a worm grows per food
2211 bool set_worm_food(void)
2213 static const struct opt_items worm_food_option
[11] = {
2226 return rb
->set_option("Worm Growth Per Food", &worm_food
,INT
,worm_food_option
, 11, NULL
);
2230 Sets the number of arghs created per food
2232 bool set_argh_per_food(void)
2234 static const struct opt_items argh_food_option
[5] = {
2241 return rb
->set_option("Arghs Per Food", &arghs_per_food
,INT
,argh_food_option
, 5, NULL
);
2245 Sets the number of ticks per move
2247 bool set_worm_speed(void)
2250 static const struct opt_items speed_option
[19] = {
2274 ret
= rb
->set_option("Worm Speed", &speed
,INT
,speed_option
, 19, NULL
);
2280 Sets the control style, which depends on the number of players,
2281 remote control existing, etc
2282 bool set_bool_options(char* string, bool* variable,
2283 char* yes_str, char* no_str, void (*function)(bool))
2285 bool set_worm_control_style(void)
2287 static const struct opt_items key1_option
[2] = {
2288 { "Remote Control", -1 },
2289 { "No Rem. Control", -1 },
2292 static const struct opt_items key2_option
[2] = {
2293 { "2 Key Control", -1 },
2294 { "4 Key COntrol", -1 },
2297 static const struct opt_items key3_option
[1] = {
2298 { "Out of Control", -1 },
2302 rb
->set_option("Control Style",&use_remote
,INT
, key1_option
, 2, NULL
);
2305 rb
->set_option("Control Style",&use_remote
,INT
, key2_option
, 2, NULL
);
2308 rb
->set_option("Control Style",&use_remote
,INT
, key3_option
, 1, NULL
);
2314 void default_settings(void)
2316 arghs_per_food
= ARGHS_PER_FOOD
;
2317 argh_size
= ARGH_SIZE
;
2318 food_size
= FOOD_SIZE
;
2320 worm_food
= WORM_PER_FOOD
;
2322 worm_count
= MAX_WORMS
;
2328 Launches the wormlet game
2330 bool launch_wormlet(void)
2332 int game_result
= 1;
2334 rb
->lcd_clear_display();
2336 /* Turn off backlight timeout */
2337 backlight_force_on(rb
); /* backlight control in lib/helper.c */
2339 /* start the game */
2340 while (game_result
== 1)
2341 game_result
= run();
2343 switch (game_result
)
2346 /* Turn on backlight timeout (revert to settings) */
2347 backlight_use_settings(rb
); /* backlight control in lib/helper.c */
2354 /* End of settings/changes etc */
2359 enum plugin_status
plugin_start(struct plugin_api
* api
, void* parameter
)
2369 configfile_init(rb
);
2370 if (configfile_load(SETTINGS_FILENAME
, config
,
2371 sizeof(config
)/sizeof(*config
),
2372 SETTINGS_MIN_VERSION
) < 0)
2374 /* If the loading failed, save a new config file (as the disk is
2375 already spinning) */
2376 configfile_save(SETTINGS_FILENAME
, config
,
2377 sizeof(config
)/sizeof(*config
),
2381 #ifdef HAVE_LCD_COLOR
2382 rb
->lcd_set_foreground(COLOR_FG
);
2383 rb
->lcd_set_background(COLOR_BG
);
2387 rb
->lcd_set_backdrop(NULL
);
2390 #ifdef DEBUG_WORMLET
2392 test_worm_argh_collision_in_moves();
2394 test_worm_food_collision();
2395 test_worm_argh_collision();
2396 test_specific_worm_collision();
2401 static const struct opt_items noyes
[2] = {
2406 static const struct opt_items num_worms_option
[3] = {
2413 static const struct opt_items num_players_option
[4] = {
2415 static const struct opt_items num_players_option
[2] = {
2425 static const struct opt_items argh_size_option
[9] = {
2437 static const struct opt_items food_size_option
[9] = {
2449 static const struct opt_items worm_food_option
[16] = {
2468 static const struct opt_items argh_food_option
[9] = {
2480 static const struct opt_items speed_option
[21] = {
2504 static const struct opt_items remoteonly_option
[1] = {
2505 { "Remote Control", -1 }
2508 static const struct opt_items key24_option
[2] = {
2509 { "4 Key Control", -1 },
2510 { "2 Key Control", -1 }
2514 static const struct opt_items remote_option
[2] = {
2515 { "Remote Control", -1 },
2516 { "No Rem. Control", -1 }
2519 static const struct opt_items key2_option
[1] = {
2520 { "2 Key Control", -1 }
2524 static const struct opt_items nokey_option
[1] = {
2525 { "Out of Control", -1 }
2528 MENUITEM_STRINGLIST(menu
, "Wormlet Menu", NULL
, "Play Wormlet!",
2529 "Number of Worms", "Number of Players", "Control Style",
2530 "Worm Growth Per Food","Worm Speed","Arghs Per Food",
2531 "Argh Size","Food Size","Revert to Default Settings",
2534 rb
->button_clear_queue();
2536 while (!menu_quit
) {
2537 switch(rb
->do_menu(&menu
, &result
))
2540 rb
->lcd_setfont(FONT_SYSFIXED
);
2544 new_setting
= worm_count
- 1;
2545 rb
->set_option("Number of Worms", &new_setting
, INT
, num_worms_option
, 3, NULL
);
2546 if (new_setting
!= worm_count
)
2547 worm_count
= new_setting
+ 1;
2548 if (worm_count
< players
) {
2549 worm_count
= players
;
2553 new_setting
= players
;
2555 rb
->set_option("Number of Players", &new_setting
, INT
, num_players_option
, 4, NULL
);
2557 rb
->set_option("Number of Players", &new_setting
, INT
, num_players_option
, 2, NULL
);
2559 if (new_setting
!= players
)
2560 players
= new_setting
;
2561 if (players
> worm_count
) {
2562 worm_count
= players
;
2569 new_setting
= use_remote
;
2572 rb
->set_option("Control Style",&new_setting
,INT
, nokey_option
, 1, NULL
);
2575 rb
->set_option("Control Style",&new_setting
,INT
, key24_option
, 2, NULL
);
2579 rb
->set_option("Control Style",&new_setting
,INT
, remote_option
, 2, NULL
);
2581 rb
->set_option("Control Style",&new_setting
,INT
, key2_option
, 1, NULL
);
2585 rb
->set_option("Control Style",&new_setting
,INT
, remoteonly_option
, 1, NULL
);
2588 if (new_setting
!= use_remote
)
2589 use_remote
= new_setting
;
2592 new_setting
= worm_food
;
2593 rb
->set_option("Worm Growth Per Food", &new_setting
,INT
,worm_food_option
, 16, NULL
);
2594 if (new_setting
!= worm_food
)
2595 worm_food
= new_setting
;
2598 new_setting
= speed
;
2599 new_setting
= 20 - new_setting
;
2600 rb
->set_option("Worm Speed", &new_setting
,INT
,speed_option
, 21, NULL
);
2601 new_setting
= 20 - new_setting
;
2602 if (new_setting
!= speed
)
2603 speed
= new_setting
;
2606 new_setting
= arghs_per_food
;
2607 rb
->set_option("Arghs Per Food", &new_setting
,INT
,argh_food_option
, 9, NULL
);
2608 if (new_setting
!= arghs_per_food
)
2609 arghs_per_food
= new_setting
;
2612 new_setting
= argh_size
-2;
2613 rb
->set_option("Argh Size", &new_setting
,INT
,argh_size_option
, 9, NULL
);
2614 if (new_setting
!= argh_size
)
2615 argh_size
= new_setting
+2;
2618 new_setting
= food_size
-2;
2619 rb
->set_option("Food Size", &new_setting
,INT
,food_size_option
, 9, NULL
);
2620 if (new_setting
!= food_size
)
2621 food_size
= new_setting
+2;
2625 rb
->set_option("Reset Settings?", &new_setting
,INT
, noyes
, 2, NULL
);
2626 if (new_setting
== 1)
2635 configfile_save(SETTINGS_FILENAME
, config
,
2636 sizeof(config
)/sizeof(*config
),