A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 Philipp Pertermann
11 *
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.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22#include "lib/configfile.h"
23#include "lib/helper.h"
24#include "lib/playback_control.h"
25
26#ifdef DEBUG_WORMLET
27static long max_cycle;
28#endif
29
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)
35
36/* when the game starts */
37#define INITIAL_WORM_LENGTH 10
38
39/* num of pixel the worm grows per eaten food */
40#define WORM_PER_FOOD 7
41
42/* num of worms creeping in the FIELD */
43#define MAX_WORMS 3
44
45/* minimal distance between a worm and an argh
46 when a new argh is made */
47#define MIN_ARGH_DIST 5
48
49#if (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \
50 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
51
52#define BTN_DIR_UP BUTTON_MENU
53#define BTN_DIR_DOWN BUTTON_PLAY
54#define BTN_DIR_LEFT BUTTON_LEFT
55#define BTN_DIR_RIGHT BUTTON_RIGHT
56#define BTN_STARTPAUSE (BUTTON_SELECT|BUTTON_REL)
57#define BTN_QUIT (BUTTON_SELECT|BUTTON_REPEAT)
58#define BTN_STOPRESET (BUTTON_SELECT|BUTTON_PLAY)
59
60#elif (CONFIG_KEYPAD == IRIVER_H300_PAD) || (CONFIG_KEYPAD == IRIVER_H100_PAD)
61
62#define BTN_DIR_UP BUTTON_UP
63#define BTN_DIR_DOWN BUTTON_DOWN
64#define BTN_DIR_LEFT BUTTON_LEFT
65#define BTN_DIR_RIGHT BUTTON_RIGHT
66#define BTN_STARTPAUSE (BUTTON_SELECT|BUTTON_REL)
67#define BTN_QUIT BUTTON_OFF
68#define BTN_STOPRESET BUTTON_ON
69
70#define BTN_RC_QUIT BUTTON_RC_STOP
71
72#elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD)
73
74#define BTN_DIR_UP BUTTON_UP
75#define BTN_DIR_DOWN BUTTON_DOWN
76#define BTN_DIR_LEFT BUTTON_LEFT
77#define BTN_DIR_RIGHT BUTTON_RIGHT
78#define BTN_STARTPAUSE BUTTON_PLAY
79#define BTN_QUIT BUTTON_POWER
80#define BTN_STOPRESET BUTTON_REC
81
82#elif (CONFIG_KEYPAD == GIGABEAT_PAD)
83
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_SELECT
89#define BTN_QUIT BUTTON_POWER
90#define BTN_STOPRESET BUTTON_A
91
92#elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \
93(CONFIG_KEYPAD == SANSA_C200_PAD)
94
95#define BTN_DIR_UP BUTTON_UP
96#define BTN_DIR_DOWN BUTTON_DOWN
97#define BTN_DIR_LEFT BUTTON_LEFT
98#define BTN_DIR_RIGHT BUTTON_RIGHT
99#define BTN_STARTPAUSE BUTTON_SELECT
100#define BTN_QUIT BUTTON_POWER
101#define BTN_STOPRESET BUTTON_REC
102
103#elif (CONFIG_KEYPAD == SANSA_CLIP_PAD)
104
105#define BTN_DIR_UP BUTTON_UP
106#define BTN_DIR_DOWN BUTTON_DOWN
107#define BTN_DIR_LEFT BUTTON_LEFT
108#define BTN_DIR_RIGHT BUTTON_RIGHT
109#define BTN_STARTPAUSE BUTTON_SELECT
110#define BTN_QUIT BUTTON_POWER
111#define BTN_STOPRESET BUTTON_HOME
112
113#elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
114
115#define BTN_DIR_UP BUTTON_UP
116#define BTN_DIR_DOWN BUTTON_DOWN
117#define BTN_DIR_LEFT BUTTON_LEFT
118#define BTN_DIR_RIGHT BUTTON_RIGHT
119#define BTN_STARTPAUSE BUTTON_SELECT
120#define BTN_QUIT (BUTTON_HOME|BUTTON_REPEAT)
121#define BTN_STOPRESET (BUTTON_SELECT | BUTTON_UP)
122
123#elif (CONFIG_KEYPAD == SANSA_M200_PAD)
124
125#define BTN_DIR_UP BUTTON_UP
126#define BTN_DIR_DOWN BUTTON_DOWN
127#define BTN_DIR_LEFT BUTTON_LEFT
128#define BTN_DIR_RIGHT BUTTON_RIGHT
129#define BTN_STARTPAUSE (BUTTON_SELECT | BUTTON_REL)
130#define BTN_QUIT BUTTON_POWER
131#define BTN_STOPRESET (BUTTON_SELECT | BUTTON_UP)
132
133#elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
134
135#define BTN_DIR_UP BUTTON_SCROLL_UP
136#define BTN_DIR_DOWN BUTTON_SCROLL_DOWN
137#define BTN_DIR_LEFT BUTTON_LEFT
138#define BTN_DIR_RIGHT BUTTON_RIGHT
139#define BTN_STARTPAUSE BUTTON_PLAY
140#define BTN_QUIT BUTTON_POWER
141#define BTN_STOPRESET BUTTON_REW
142
143#elif (CONFIG_KEYPAD == GIGABEAT_S_PAD) || \
144 (CONFIG_KEYPAD == SAMSUNG_YPR0_PAD)
145
146#define BTN_DIR_UP BUTTON_UP
147#define BTN_DIR_DOWN BUTTON_DOWN
148#define BTN_DIR_LEFT BUTTON_LEFT
149#define BTN_DIR_RIGHT BUTTON_RIGHT
150#define BTN_STARTPAUSE BUTTON_SELECT
151#define BTN_QUIT BUTTON_BACK
152#define BTN_STOPRESET BUTTON_MENU
153
154#elif (CONFIG_KEYPAD == MROBE100_PAD)
155
156#define BTN_DIR_UP BUTTON_UP
157#define BTN_DIR_DOWN BUTTON_DOWN
158#define BTN_DIR_LEFT BUTTON_LEFT
159#define BTN_DIR_RIGHT BUTTON_RIGHT
160#define BTN_STARTPAUSE BUTTON_SELECT
161#define BTN_QUIT BUTTON_POWER
162#define BTN_STOPRESET BUTTON_DISPLAY
163
164#elif CONFIG_KEYPAD == IAUDIO_M3_PAD
165
166#define BTN_DIR_UP BUTTON_RC_VOL_UP
167#define BTN_DIR_DOWN BUTTON_RC_VOL_DOWN
168#define BTN_DIR_LEFT BUTTON_RC_REW
169#define BTN_DIR_RIGHT BUTTON_RC_FF
170#define BTN_STARTPAUSE BUTTON_RC_PLAY
171#define BTN_QUIT BUTTON_RC_REC
172#define BTN_STOPRESET BUTTON_RC_MODE
173
174#elif (CONFIG_KEYPAD == COWON_D2_PAD)
175
176#define BTN_QUIT BUTTON_POWER
177
178#elif CONFIG_KEYPAD == CREATIVEZVM_PAD
179
180#define BTN_DIR_UP BUTTON_UP
181#define BTN_DIR_DOWN BUTTON_DOWN
182#define BTN_DIR_LEFT BUTTON_LEFT
183#define BTN_DIR_RIGHT BUTTON_RIGHT
184#define BTN_STARTPAUSE BUTTON_PLAY
185#define BTN_QUIT BUTTON_BACK
186#define BTN_STOPRESET BUTTON_MENU
187
188#elif CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD
189
190#define BTN_DIR_UP BUTTON_UP
191#define BTN_DIR_DOWN BUTTON_DOWN
192#define BTN_DIR_LEFT BUTTON_BACK
193#define BTN_DIR_RIGHT BUTTON_MENU
194#define BTN_STARTPAUSE (BUTTON_PLAY|BUTTON_REL)
195#define BTN_QUIT BUTTON_POWER
196#define BTN_STOPRESET (BUTTON_PLAY|BUTTON_REPEAT)
197
198#elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
199
200#define BTN_DIR_UP BUTTON_UP
201#define BTN_DIR_DOWN BUTTON_DOWN
202#define BTN_DIR_LEFT BUTTON_LEFT
203#define BTN_DIR_RIGHT BUTTON_RIGHT
204#define BTN_STARTPAUSE BUTTON_MENU
205#define BTN_QUIT BUTTON_POWER
206#define BTN_STOPRESET BUTTON_VIEW
207
208#elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD
209
210#define BTN_DIR_UP BUTTON_UP
211#define BTN_DIR_DOWN BUTTON_DOWN
212#define BTN_DIR_LEFT BUTTON_PREV
213#define BTN_DIR_RIGHT BUTTON_NEXT
214#define BTN_STARTPAUSE BUTTON_MENU
215#define BTN_QUIT BUTTON_POWER
216#define BTN_STOPRESET BUTTON_RIGHT
217
218#elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
219
220#define BTN_DIR_UP BUTTON_UP
221#define BTN_DIR_DOWN BUTTON_DOWN
222#define BTN_DIR_LEFT BUTTON_PREV
223#define BTN_DIR_RIGHT BUTTON_RIGHT
224#define BTN_STARTPAUSE BUTTON_MENU
225#define BTN_QUIT BUTTON_POWER
226#define BTN_STOPRESET BUTTON_RIGHT
227
228#elif (CONFIG_KEYPAD == ONDAVX747_PAD) || \
229(CONFIG_KEYPAD == ONDAVX777_PAD) || \
230CONFIG_KEYPAD == MROBE500_PAD
231
232#define BTN_QUIT BUTTON_POWER
233
234#elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \
235 (CONFIG_KEYPAD == SAMSUNG_YH92X_PAD)
236
237#define BTN_DIR_UP BUTTON_UP
238#define BTN_DIR_DOWN BUTTON_DOWN
239#define BTN_DIR_LEFT BUTTON_LEFT
240#define BTN_DIR_RIGHT BUTTON_RIGHT
241#define BTN_STARTPAUSE BUTTON_PLAY
242#define BTN_QUIT BUTTON_REW
243#define BTN_STOPRESET BUTTON_FFWD
244
245#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
246
247#define BTN_DIR_UP BUTTON_UP
248#define BTN_DIR_DOWN BUTTON_DOWN
249#define BTN_DIR_LEFT BUTTON_PREV
250#define BTN_DIR_RIGHT BUTTON_NEXT
251#define BTN_STARTPAUSE BUTTON_PLAY
252#define BTN_QUIT BUTTON_REC
253#define BTN_STOPRESET BUTTON_CANCEL
254
255#elif CONFIG_KEYPAD == MPIO_HD200_PAD
256
257#define BTN_DIR_UP BUTTON_REW
258#define BTN_DIR_DOWN BUTTON_FF
259#define BTN_DIR_LEFT BUTTON_VOL_DOWN
260#define BTN_DIR_RIGHT BUTTON_VOL_UP
261#define BTN_STARTPAUSE BUTTON_FUNC
262#define BTN_QUIT (BUTTON_REC|BUTTON_PLAY)
263#define BTN_STOPRESET (BUTTON_FUNC|BUTTON_REPEAT)
264
265#elif CONFIG_KEYPAD == MPIO_HD300_PAD
266
267#define BTN_DIR_UP BUTTON_UP
268#define BTN_DIR_DOWN BUTTON_DOWN
269#define BTN_DIR_LEFT BUTTON_REW
270#define BTN_DIR_RIGHT BUTTON_FF
271#define BTN_STARTPAUSE BUTTON_PLAY
272#define BTN_QUIT (BUTTON_MENU | BUTTON_REPEAT)
273#define BTN_STOPRESET (BUTTON_PLAY | BUTTON_REPEAT)
274
275#elif CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD
276
277#define BTN_DIR_UP BUTTON_UP
278#define BTN_DIR_DOWN BUTTON_DOWN
279#define BTN_DIR_LEFT BUTTON_LEFT
280#define BTN_DIR_RIGHT BUTTON_RIGHT
281#define BTN_STARTPAUSE BUTTON_PLAYPAUSE
282#define BTN_QUIT BUTTON_POWER
283#define BTN_STOPRESET BUTTON_BACK
284
285#elif CONFIG_KEYPAD == SANSA_CONNECT_PAD
286
287#define BTN_DIR_UP BUTTON_UP
288#define BTN_DIR_DOWN BUTTON_DOWN
289#define BTN_DIR_LEFT BUTTON_LEFT
290#define BTN_DIR_RIGHT BUTTON_RIGHT
291#define BTN_STARTPAUSE BUTTON_SELECT
292#define BTN_QUIT BUTTON_POWER
293#define BTN_STOPRESET BUTTON_VOL_DOWN
294
295#elif CONFIG_KEYPAD == HM60X_PAD
296
297#define BTN_DIR_UP BUTTON_UP
298#define BTN_DIR_DOWN BUTTON_DOWN
299#define BTN_DIR_LEFT BUTTON_LEFT
300#define BTN_DIR_RIGHT BUTTON_RIGHT
301#define BTN_STARTPAUSE BUTTON_SELECT
302#define BTN_QUIT BUTTON_POWER
303#define BTN_STOPRESET (BUTTON_POWER|BUTTON_SELECT)
304
305#elif CONFIG_KEYPAD == HM801_PAD
306
307#define BTN_DIR_UP BUTTON_UP
308#define BTN_DIR_DOWN BUTTON_DOWN
309#define BTN_DIR_LEFT BUTTON_LEFT
310#define BTN_DIR_RIGHT BUTTON_RIGHT
311#define BTN_STARTPAUSE BUTTON_SELECT
312#define BTN_QUIT BUTTON_POWER
313#define BTN_STOPRESET BUTTON_PLAY
314
315#elif CONFIG_KEYPAD == SONY_NWZ_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_PLAY
321#define BTN_QUIT BUTTON_BACK
322#define BTN_STOPRESET BUTTON_POWER
323
324#elif CONFIG_KEYPAD == CREATIVE_ZEN_PAD
325#define BTN_DIR_UP BUTTON_UP
326#define BTN_DIR_DOWN BUTTON_DOWN
327#define BTN_DIR_LEFT BUTTON_LEFT
328#define BTN_DIR_RIGHT BUTTON_RIGHT
329#define BTN_STARTPAUSE BUTTON_PLAYPAUSE
330#define BTN_QUIT BUTTON_BACK
331#define BTN_STOPRESET BUTTON_SHORTCUT
332
333#elif CONFIG_KEYPAD == DX50_PAD
334#define BTN_DIR_UP BUTTON_VOL_UP
335#define BTN_DIR_DOWN BUTTON_VOL_DOWN
336#define BTN_DIR_LEFT BUTTON_LEFT
337#define BTN_DIR_RIGHT BUTTON_RIGHT
338#define BTN_STARTPAUSE BUTTON_PLAY
339#define BTN_QUIT BUTTON_POWER
340#define BTN_STOPRESET (BUTTON_PLAY|BUTTON_REPEAT)
341
342#elif CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD
343#define BTN_QUIT BUTTON_POWER
344#define BTN_STARTPAUSE BUTTON_MENU
345#define BTN_STOPRESET (BUTTON_MENU|BUTTON_REPEAT)
346
347#elif CONFIG_KEYPAD == AGPTEK_ROCKER_PAD
348#define BTN_DIR_UP BUTTON_UP
349#define BTN_DIR_DOWN BUTTON_DOWN
350#define BTN_DIR_LEFT BUTTON_LEFT
351#define BTN_DIR_RIGHT BUTTON_RIGHT
352#define BTN_STARTPAUSE BUTTON_SELECT
353#define BTN_QUIT BUTTON_POWER
354#define BTN_STOPRESET (BUTTON_SELECT|BUTTON_REPEAT)
355
356#elif CONFIG_KEYPAD == XDUOO_X3_PAD
357#define BTN_DIR_UP BUTTON_HOME
358#define BTN_DIR_DOWN BUTTON_OPTION
359#define BTN_DIR_LEFT BUTTON_PREV
360#define BTN_DIR_RIGHT BUTTON_NEXT
361#define BTN_STARTPAUSE BUTTON_PLAY
362#define BTN_QUIT BUTTON_POWER
363#define BTN_STOPRESET (BUTTON_HOME | BUTTON_PWRALT)
364
365#elif CONFIG_KEYPAD == XDUOO_X3II_PAD || CONFIG_KEYPAD == XDUOO_X20_PAD
366#define BTN_DIR_UP BUTTON_HOME
367#define BTN_DIR_DOWN BUTTON_OPTION
368#define BTN_DIR_LEFT BUTTON_PREV
369#define BTN_DIR_RIGHT BUTTON_NEXT
370#define BTN_STARTPAUSE BUTTON_PLAY
371#define BTN_QUIT BUTTON_POWER
372#define BTN_STOPRESET (BUTTON_HOME | BUTTON_POWER)
373
374#elif CONFIG_KEYPAD == FIIO_M3K_LINUX_PAD
375#define BTN_DIR_UP BUTTON_HOME
376#define BTN_DIR_DOWN BUTTON_OPTION
377#define BTN_DIR_LEFT BUTTON_PREV
378#define BTN_DIR_RIGHT BUTTON_NEXT
379#define BTN_STARTPAUSE BUTTON_PLAY
380#define BTN_QUIT BUTTON_POWER
381#define BTN_STOPRESET (BUTTON_HOME | BUTTON_POWER)
382
383#elif CONFIG_KEYPAD == IHIFI_770_PAD || CONFIG_KEYPAD == IHIFI_800_PAD
384#define BTN_DIR_UP BUTTON_PREV
385#define BTN_DIR_DOWN BUTTON_NEXT
386#define BTN_DIR_LEFT BUTTON_HOME
387#define BTN_DIR_RIGHT BUTTON_VOL_DOWN
388#define BTN_STARTPAUSE BUTTON_PLAY
389#define BTN_QUIT BUTTON_POWER
390#define BTN_STOPRESET BUTTON_VOL_UP
391
392#elif CONFIG_KEYPAD == EROSQ_PAD
393#define BTN_DIR_UP BUTTON_PREV
394#define BTN_DIR_DOWN BUTTON_NEXT
395#define BTN_DIR_LEFT BUTTON_SCROLL_BACK
396#define BTN_DIR_RIGHT BUTTON_SCROLL_FWD
397#define BTN_STARTPAUSE BUTTON_PLAY
398#define BTN_QUIT BUTTON_POWER
399#define BTN_STOPRESET BUTTON_BACK
400
401#elif CONFIG_KEYPAD == FIIO_M3K_PAD
402#define BTN_DIR_UP BUTTON_UP
403#define BTN_DIR_DOWN BUTTON_DOWN
404#define BTN_DIR_LEFT BUTTON_LEFT
405#define BTN_DIR_RIGHT BUTTON_RIGHT
406#define BTN_STARTPAUSE BUTTON_PLAY
407#define BTN_QUIT BUTTON_POWER
408#define BTN_STOPRESET BUTTON_BACK
409
410#elif CONFIG_KEYPAD == SHANLING_Q1_PAD
411/* use touchscreen */
412
413#elif CONFIG_KEYPAD == SDL_PAD
414/* use touchscreen */
415#elif CONFIG_KEYPAD == MA_PAD
416#define BTN_DIR_UP BUTTON_UP
417#define BTN_DIR_DOWN BUTTON_DOWN
418#define BTN_DIR_LEFT BUTTON_LEFT
419#define BTN_DIR_RIGHT BUTTON_RIGHT
420#define BTN_STARTPAUSE BUTTON_PLAY
421#define BTN_QUIT BUTTON_BACK
422#define BTN_STOPRESET BUTTON_MENU
423
424#elif CONFIG_KEYPAD == RG_NANO_PAD
425
426#define BTN_DIR_UP BUTTON_UP
427#define BTN_DIR_DOWN BUTTON_DOWN
428#define BTN_DIR_LEFT BUTTON_LEFT
429#define BTN_DIR_RIGHT BUTTON_RIGHT
430#define BTN_STARTPAUSE BUTTON_A
431#define BTN_QUIT BUTTON_START
432#define BTN_STOPRESET BUTTON_B
433
434#else
435#error No keymap defined!
436#endif
437
438#ifdef HAVE_TOUCHSCREEN
439#ifndef BTN_DIR_UP
440#define BTN_DIR_UP BUTTON_TOPMIDDLE
441#endif
442#ifndef BTN_DIR_DOWN
443#define BTN_DIR_DOWN BUTTON_BOTTOMMIDDLE
444#endif
445#ifndef BTN_DIR_LEFT
446#define BTN_DIR_LEFT BUTTON_MIDLEFT
447#endif
448#ifndef BTN_DIR_RIGHT
449#define BTN_DIR_RIGHT BUTTON_MIDRIGHT
450#endif
451#ifndef BTN_STARTPAUSE
452#define BTN_STARTPAUSE BUTTON_CENTER
453#endif
454#ifndef BTN_QUIT
455#define BTN_QUIT BUTTON_TOPLEFT
456#endif
457#ifndef BTN_STOPRESET
458#define BTN_STOPRESET BUTTON_TOPRIGHT
459#endif
460#endif
461
462#if (LCD_WIDTH == 96) && (LCD_HEIGHT == 96)
463#define FOOD_SIZE 3
464#define ARGH_SIZE 4
465#define SPEED 14
466#define MAX_WORM_SEGMENTS 128
467#elif (LCD_WIDTH == 112) && (LCD_HEIGHT == 64)
468#define FOOD_SIZE 3
469#define ARGH_SIZE 4
470#define SPEED 14
471#define MAX_WORM_SEGMENTS 128
472#elif (LCD_WIDTH == 128) && (LCD_HEIGHT == 64)
473#define FOOD_SIZE 3
474#define ARGH_SIZE 4
475#define SPEED 14
476#define MAX_WORM_SEGMENTS 128
477#elif (LCD_WIDTH == 132) && (LCD_HEIGHT == 80)
478#define FOOD_SIZE 3
479#define ARGH_SIZE 4
480#define SPEED 14
481#define MAX_WORM_SEGMENTS 128
482#elif (LCD_WIDTH == 128) && (LCD_HEIGHT == 96)
483#define FOOD_SIZE 3
484#define ARGH_SIZE 4
485#define SPEED 12
486#define MAX_WORM_SEGMENTS 128
487#elif (LCD_WIDTH == 138) && (LCD_HEIGHT == 110)
488#define FOOD_SIZE 4
489#define ARGH_SIZE 5
490#define SPEED 10
491#define MAX_WORM_SEGMENTS 128
492#elif (LCD_WIDTH == 128) && (LCD_HEIGHT == 128)
493#define FOOD_SIZE 4
494#define ARGH_SIZE 5
495#define SPEED 9
496#define MAX_WORM_SEGMENTS 128
497#elif ((LCD_WIDTH == 160) && (LCD_HEIGHT == 128)) || \
498 ((LCD_WIDTH == 128) && (LCD_HEIGHT == 160))
499#define FOOD_SIZE 4
500#define ARGH_SIZE 5
501#define SPEED 8
502#define MAX_WORM_SEGMENTS 256
503#elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)
504#define FOOD_SIZE 4
505#define ARGH_SIZE 5
506#define SPEED 6
507#define MAX_WORM_SEGMENTS 256
508#elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)
509#define FOOD_SIZE 5
510#define ARGH_SIZE 6
511#define SPEED 4
512#define MAX_WORM_SEGMENTS 512
513#elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)
514#define FOOD_SIZE 5
515#define ARGH_SIZE 6
516#define SPEED 4
517#define MAX_WORM_SEGMENTS 512
518#elif ((LCD_WIDTH == 240) && (LCD_HEIGHT == 240)) || \
519 ((LCD_WIDTH == 320) && (LCD_HEIGHT == 240)) || \
520 ((LCD_WIDTH == 240) && ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400))) || \
521 ((LCD_WIDTH == 360) && (LCD_HEIGHT == 400))
522#define FOOD_SIZE 7
523#define ARGH_SIZE 8
524#define SPEED 4
525#define MAX_WORM_SEGMENTS 512
526#elif ((LCD_WIDTH == 640) && (LCD_HEIGHT == 480)) || \
527 ((LCD_WIDTH == 480) && (LCD_HEIGHT == 640))
528#define FOOD_SIZE 14
529#define ARGH_SIZE 16
530#define SPEED 4
531#define MAX_WORM_SEGMENTS 512
532#endif
533
534#ifdef HAVE_LCD_COLOR
535#define COLOR_WORM LCD_RGBPACK(80, 40, 0)
536#define COLOR_ARGH LCD_RGBPACK(175, 0, 0)
537#define COLOR_FOOD LCD_RGBPACK(0, 150, 0)
538#define COLOR_FG LCD_RGBPACK(0, 0, 0)
539#define COLOR_BG LCD_RGBPACK(181, 199, 231)
540#endif
541
542#define CHECK_SQUARE_COLLISION(x1,y1,s1,x2,y2,s2) (x1+s1>x2)&&(x2+s2>x1)&&(y1+s1>y2)&&(y2+s2>y1)
543
544/**
545 * All the properties that a worm has.
546 */
547static struct worm {
548 /* The worm is stored in a ring of xy coordinates */
549 int x[MAX_WORM_SEGMENTS];
550 int y[MAX_WORM_SEGMENTS];
551
552 int head; /* index of the head within the buffer */
553 int tail; /* index of the tail within the buffer */
554 int growing; /* number of cyles the worm still keeps growing */
555 bool alive; /* the worms living state */
556
557 /* direction vector in which the worm moves */
558 int dirx; /* only values -1 0 1 allowed */
559 int diry; /* only values -1 0 1 allowed */
560
561 /* this method is used to fetch the direction the user
562 has selected. It can be one of the values
563 human_player1, human_player2, remote_player, virtual_player.
564 All these values are fuctions, that can change the direction
565 of the worm */
566 void (*fetch_worm_direction)(struct worm *w);
567} worms[MAX_WORMS];
568
569/* stores the highscore - besides it was scored by a virtual player */
570static int highscore;
571
572#define MAX_FOOD 5 /* maximal number of food items */
573
574/* The arrays store the food coordinates */
575static int foodx[MAX_FOOD];
576static int foody[MAX_FOOD];
577
578#define MAX_ARGH 100 /* maximal number of argh items */
579#define ARGHS_PER_FOOD 2 /* number of arghs produced per eaten food */
580
581/* The arrays store the argh coordinates */
582static int arghx[MAX_ARGH];
583static int arghy[MAX_ARGH];
584
585/* the number of arghs that are currently in use */
586static int argh_count;
587
588/* the number of arghs per food, settable by user */
589static int arghs_per_food = ARGHS_PER_FOOD;
590/* the size of the argh, settable by user */
591static int argh_size = ARGH_SIZE;
592/* the size of the food, settable by user */
593static int food_size = FOOD_SIZE;
594/* the speed of the worm, settable by user */
595static int speed = SPEED;
596/* the amount a worm grows by eating a food, settable by user */
597static int worm_food = WORM_PER_FOOD;
598
599/* End additional variables */
600
601/* the number of active worms (dead or alive) */
602static int worm_count = MAX_WORMS;
603
604/* in multiplayer mode: en- / disables the remote worm control
605 in singleplayer mode: toggles 4 / 2 button worm control */
606static bool use_remote = false;
607
608/* return values of check_collision */
609#define COLLISION_NONE 0
610#define COLLISION_WORM 1
611#define COLLISION_FOOD 2
612#define COLLISION_ARGH 3
613#define COLLISION_FIELD 4
614
615static const char *const state_desc[] = {
616 [COLLISION_NONE] = NULL,
617 [COLLISION_WORM] = "Wormed",
618 [COLLISION_FOOD] = "Growing",
619 [COLLISION_ARGH] = "Argh",
620 [COLLISION_FIELD] = "Crashed",
621};
622
623/* constants for use as directions.
624 Note that the values are ordered clockwise.
625 Thus increasing / decreasing the values
626 is equivalent to right / left turns. */
627#define WEST 0
628#define NORTH 1
629#define EAST 2
630#define SOUTH 3
631
632/* direction of human player 1 */
633static int player1_dir = EAST;
634/* direction of human player 2 */
635static int player2_dir = EAST;
636/* direction of human player 3 */
637static int player3_dir = EAST;
638
639/* the number of (human) players that currently
640 control a worm */
641static int players = 1;
642
643#define SETTINGS_VERSION 1
644#define SETTINGS_MIN_VERSION 1
645#define SETTINGS_FILENAME "wormlet.cfg"
646
647static struct configdata config[] =
648{
649 {TYPE_INT, 0, 1024, { .int_p = &highscore }, "highscore", NULL},
650 {TYPE_INT, 0, 15, { .int_p = &arghs_per_food }, "arghs per food", NULL},
651 {TYPE_INT, 0, 15, { .int_p = &argh_size }, "argh size", NULL},
652 {TYPE_INT, 0, 15, { .int_p = &food_size }, "food size", NULL},
653 {TYPE_INT, 0, 3, { .int_p = &players }, "players", NULL},
654 {TYPE_INT, 0, 3, { .int_p = &worm_count }, "worms", NULL},
655 {TYPE_INT, 0, 20, { .int_p = &speed }, "speed", NULL},
656 {TYPE_INT, 0, 15, { .int_p = &worm_food }, "Worm Growth Per Food", NULL}
657};
658
659/**
660 * Returns the direction id in which the worm
661 * currently is creeping.
662 * @param struct worm *w The worm that is to be investigated.
663 * w Must not be null.
664 * @return int A value 0 <= value < 4
665 * Note the predefined constants NORTH, SOUTH, EAST, WEST
666 */
667static int get_worm_dir(struct worm *w)
668{
669 int retVal ;
670 if (w->dirx == 0) {
671 if (w->diry == 1) {
672 retVal = SOUTH;
673 } else {
674 retVal = NORTH;
675 }
676 } else {
677 if (w->dirx == 1) {
678 retVal = EAST;
679 } else {
680 retVal = WEST;
681 }
682 }
683 return retVal;
684}
685
686/**
687 * Set the direction of the specified worm with a direction id.
688 * Increasing the value by 1 means to turn the worm direction
689 * to right by 90 degree.
690 * @param struct worm *w The worm that is to be altered. w Must not be null.
691 * @param int dir The new direction in which the worm is to creep.
692 * dir must be 0 <= dir < 4. Use predefined constants
693 * NORTH, SOUTH, EAST, WEST
694 */
695static void set_worm_dir(struct worm *w, int dir)
696{
697 switch (dir) {
698 case WEST:
699 w->dirx = -1;
700 w->diry = 0;
701 break;
702 case NORTH:
703 w->dirx = 0;
704 w->diry = - 1;
705 break;
706 case EAST:
707 w->dirx = 1;
708 w->diry = 0;
709 break;
710 case SOUTH:
711 w->dirx = 0;
712 w->diry = 1;
713 break;
714 }
715}
716
717/**
718 * Returns the current length of the worm array. This
719 * is also a value for the number of bends that are in the worm.
720 * @return int a positive value with 0 <= value < MAX_WORM_SEGMENTS
721 */
722static int get_worm_array_length(struct worm *w)
723{
724 /* initial simple calculation will be overwritten if wrong. */
725 int retVal = w->head - w->tail;
726
727 /* if the worm 'crosses' the boundaries of the ringbuffer */
728 if (retVal < 0) {
729 retVal = w->head + MAX_WORM_SEGMENTS - w->tail;
730 }
731
732 return retVal;
733}
734
735/**
736 * Returns the score the specified worm. The score is the length
737 * of the worm.
738 * @param struct worm *w The worm that is to be investigated.
739 * w must not be null.
740 * @return int The length of the worm (>= 0).
741 */
742static int get_score(struct worm *w)
743{
744 int retval = 0;
745 int length = get_worm_array_length(w);
746 int i;
747 for (i = 0; i < length; i++) {
748
749 /* The iteration iterates the length of the worm.
750 Here's the conversion to the true indices within the worm arrays. */
751 int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS;
752 int lineend = (linestart + 1) % MAX_WORM_SEGMENTS;
753 int startx = w->x[linestart];
754 int starty = w->y[linestart];
755 int endx = w->x[lineend];
756 int endy = w->y[lineend];
757
758 int minimum, maximum;
759
760 if (startx == endx) {
761 minimum = MIN(starty, endy);
762 maximum = MAX(starty, endy);
763 } else {
764 minimum = MIN(startx, endx);
765 maximum = MAX(startx, endx);
766 }
767 retval += abs(maximum - minimum);
768 }
769 return retval;
770}
771
772/**
773 * Determines wether the line specified by startx, starty, endx, endy intersects
774 * the rectangle specified by x, y, width, height. Note that the line must be exactly
775 * horizontal or vertical (startx == endx or starty == endy).
776 * @param int startx The x coordinate of the start point of the line.
777 * @param int starty The y coordinate of the start point of the line.
778 * @param int endx The x coordinate of the end point of the line.
779 * @param int endy The y coordinate of the end point of the line.
780 * @param int x The x coordinate of the top left corner of the rectangle.
781 * @param int y The y coordinate of the top left corner of the rectangle.
782 * @param int width The width of the rectangle.
783 * @param int height The height of the rectangle.
784 * @return bool Returns true if the specified line intersects with the recangle.
785 */
786static bool line_in_rect(int startx, int starty, int endx, int endy,
787 int x, int y, int width, int height)
788{
789 bool retval = false;
790 int simple, simplemin, simplemax;
791 int compa, compb, compmin, compmax;
792 int temp;
793 if (startx == endx) {
794 simple = startx;
795 simplemin = x;
796 simplemax = x + width;
797
798 compa = starty;
799 compb = endy;
800 compmin = y;
801 compmax = y + height;
802 } else {
803 simple = starty;
804 simplemin = y;
805 simplemax = y + height;
806
807 compa = startx;
808 compb = endx;
809 compmin = x;
810 compmax = x + width;
811 };
812
813 temp = compa;
814 compa = MIN(compa, compb);
815 compb = MAX(temp, compb);
816
817 if (simplemin <= simple && simple <= simplemax) {
818 if ((compmin <= compa && compa <= compmax) ||
819 (compmin <= compb && compb <= compmax) ||
820 (compa <= compmin && compb >= compmax)) {
821 retval = true;
822 }
823 }
824 return retval;
825}
826
827/**
828 * Tests wether the specified worm intersects with the rect.
829 * @param struct worm *w The worm to be investigated
830 * @param int x The x coordinate of the top left corner of the rect
831 * @param int y The y coordinate of the top left corner of the rect
832 * @param int widht The width of the rect
833 * @param int height The height of the rect
834 * @return bool Returns true if the worm intersects with the rect
835 */
836static bool worm_in_rect(struct worm *w, int x, int y, int width, int height)
837{
838 bool retval = false;
839
840
841 /* get_worm_array_length is expensive -> buffer the value */
842 int wormLength = get_worm_array_length(w);
843 int i;
844
845 /* test each entry that is part of the worm */
846 for (i = 0; i < wormLength && retval == false; i++) {
847
848 /* The iteration iterates the length of the worm.
849 Here's the conversion to the true indices within the worm arrays. */
850 int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS;
851 int lineend = (linestart + 1) % MAX_WORM_SEGMENTS;
852 int startx = w->x[linestart];
853 int starty = w->y[linestart];
854 int endx = w->x[lineend];
855 int endy = w->y[lineend];
856
857 retval = line_in_rect(startx, starty, endx, endy, x, y, width, height);
858 }
859
860 return retval;
861}
862
863/**
864 * Checks wether a specific food in the food arrays is at the
865 * specified coordinates.
866 * @param int foodIndex The index of the food in the food arrays
867 * @param int x the x coordinate.
868 * @param int y the y coordinate.
869 * @return Returns true if the coordinate hits the food specified by
870 * foodIndex.
871 */
872static bool specific_food_collision(int foodIndex, int x, int y)
873{
874 bool retVal = false;
875 if (x >= foodx[foodIndex] &&
876 x < foodx[foodIndex] + food_size &&
877 y >= foody[foodIndex] &&
878 y < foody[foodIndex] + food_size) {
879
880 retVal = true;
881 }
882 return retVal;
883}
884
885/**
886 * Returns the index of the food that is at the
887 * given coordinates. If no food is at the coordinates
888 * -1 is returned.
889 * @return int -1 <= value < MAX_FOOD
890 */
891static int food_collision(int x, int y)
892{
893 int i = 0;
894 int retVal = -1;
895 for (i = 0; i < MAX_FOOD; i++) {
896 if (specific_food_collision(i, x, y)) {
897 retVal = i;
898 break;
899 }
900 }
901 return retVal;
902}
903
904/**
905 * Checks wether a specific argh in the argh arrays is at the
906 * specified coordinates.
907 * @param int arghIndex The index of the argh in the argh arrays
908 * @param int x the x coordinate.
909 * @param int y the y coordinate.
910 * @return Returns true if the coordinate hits the argh specified by
911 * arghIndex.
912 */
913static bool specific_argh_collision(int arghIndex, int x, int y)
914{
915 if ( x >= arghx[arghIndex] &&
916 y >= arghy[arghIndex] &&
917 x < arghx[arghIndex] + argh_size &&
918 y < arghy[arghIndex] + argh_size )
919 {
920 return true;
921 }
922
923 return false;
924}
925
926/**
927 * Returns the index of the argh that is at the
928 * given coordinates. If no argh is at the coordinates
929 * -1 is returned.
930 * @param int x The x coordinate.
931 * @param int y The y coordinate.
932 * @return int -1 <= value < argh_count <= MAX_ARGH
933 */
934static int argh_collision(int x, int y)
935{
936 int i = 0;
937 int retVal = -1;
938
939 /* search for the argh that has the specified coords */
940 for (i = 0; i < argh_count; i++) {
941 if (specific_argh_collision(i, x, y)) {
942 retVal = i;
943 break;
944 }
945 }
946 return retVal;
947}
948
949/**
950 * Checks wether the worm collides with the food at the specfied food-arrays.
951 * @param int foodIndex The index of the food in the arrays. Ensure the value is
952 * 0 <= foodIndex <= MAX_FOOD
953 * @return Returns true if the worm collides with the specified food.
954 */
955static bool worm_food_collision(struct worm *w, int foodIndex)
956{
957 bool retVal = false;
958
959 retVal = worm_in_rect(w, foodx[foodIndex], foody[foodIndex],
960 food_size - 1, food_size - 1);
961
962 return retVal;
963}
964
965/**
966 * Returns true if the worm hits the argh within the next moves (unless
967 * the worm changes it's direction).
968 * @param struct worm *w - The worm to investigate
969 * @param int argh_idx - The index of the argh
970 * @param int moves - The number of moves that are considered.
971 * @return Returns false if the specified argh is not hit within the next
972 * moves.
973 */
974static bool worm_argh_collision_in_moves(struct worm *w, int argh_idx, int moves)
975{
976 bool retVal = false;
977 int x1, y1, x2, y2;
978 x1 = w->x[w->head];
979 y1 = w->y[w->head];
980
981 x2 = w->x[w->head] + moves * w->dirx;
982 y2 = w->y[w->head] + moves * w->diry;
983
984 retVal = line_in_rect(x1, y1, x2, y2, arghx[argh_idx], arghy[argh_idx],
985 argh_size, argh_size);
986 return retVal;
987}
988
989/**
990 * Checks wether the worm collides with the argh at the specfied argh-arrays.
991 * @param int arghIndex The index of the argh in the arrays.
992 * Ensure the value is 0 <= arghIndex < argh_count <= MAX_ARGH
993 * @return Returns true if the worm collides with the specified argh.
994 */
995static bool worm_argh_collision(struct worm *w, int arghIndex)
996{
997 bool retVal = false;
998
999 retVal = worm_in_rect(w, arghx[arghIndex], arghy[arghIndex],
1000 argh_size - 1, argh_size - 1);
1001
1002 return retVal;
1003}
1004
1005/**
1006 * Find new coordinates for the food stored in foodx[index], foody[index]
1007 * that don't collide with any other food or argh
1008 * @param int index
1009 * Ensure that 0 <= index < MAX_FOOD.
1010 */
1011static void make_food(int index)
1012{
1013 int x = 0;
1014 int y = 0;
1015 bool collisionDetected = false;
1016 int i;
1017
1018 do {
1019 /* make coordinates for a new food so that
1020 the entire food lies within the FIELD */
1021 x = rb->rand() % (FIELD_RECT_WIDTH - food_size);
1022 y = rb->rand() % (FIELD_RECT_HEIGHT - food_size);
1023 collisionDetected = false;
1024 /* Ensure that the new food doesn't collide with any
1025 existing foods or arghs.
1026 If the new food hit any existing
1027 argh or food a collision is detected.
1028 */
1029
1030 for (i=0; i<MAX_FOOD && !collisionDetected; i++) {
1031 collisionDetected = CHECK_SQUARE_COLLISION(x,y,food_size,foodx[i],foody[i],food_size);
1032 }
1033 for (i=0; i<argh_count && !collisionDetected; i++) {
1034 collisionDetected = CHECK_SQUARE_COLLISION(x,y,food_size,arghx[i],arghy[i],argh_size);
1035 }
1036
1037 /* use coordinates for further testing */
1038 foodx[index] = x;
1039 foody[index] = y;
1040
1041 /* now test wether we accidently hit the worm with food ;) */
1042 i = 0;
1043 for (i = 0; i < worm_count && !collisionDetected; i++) {
1044
1045 collisionDetected = worm_food_collision(&worms[i], index);
1046
1047 }
1048 }
1049 while (collisionDetected);
1050 return;
1051}
1052
1053/**
1054 * Clears a food from the lcd buffer.
1055 * @param int index The index of the food arrays under which
1056 * the coordinates of the desired food can be found. Ensure
1057 * that the value is 0 <= index <= MAX_FOOD.
1058 */
1059static void clear_food(int index)
1060{
1061 /* remove the old food from the screen */
1062 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1063 rb->lcd_fillrect(foodx[index] + FIELD_RECT_X,
1064 foody[index] + FIELD_RECT_Y,
1065 food_size, food_size);
1066 rb->lcd_set_drawmode(DRMODE_SOLID);
1067}
1068
1069/**
1070 * Draws a food in the lcd buffer.
1071 * @param int index The index of the food arrays under which
1072 * the coordinates of the desired food can be found. Ensure
1073 * that the value is 0 <= index <= MAX_FOOD.
1074 */
1075static void draw_food(int index)
1076{
1077 /* draw the food object */
1078#ifdef HAVE_LCD_COLOR
1079 rb->lcd_set_foreground(COLOR_FOOD);
1080#endif
1081 rb->lcd_fillrect(foodx[index] + FIELD_RECT_X,
1082 foody[index] + FIELD_RECT_Y,
1083 food_size, food_size);
1084 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1085 rb->lcd_fillrect(foodx[index] + FIELD_RECT_X + 1,
1086 foody[index] + FIELD_RECT_Y + 1,
1087 food_size - 2, food_size - 2);
1088 rb->lcd_set_drawmode(DRMODE_SOLID);
1089#ifdef HAVE_LCD_COLOR
1090 rb->lcd_set_foreground(COLOR_FG);
1091#endif
1092}
1093
1094/**
1095 * Find new coordinates for the argh stored in arghx[index], arghy[index]
1096 * that don't collide with any other food or argh.
1097 * @param int index
1098 * Ensure that 0 <= index < argh_count < MAX_ARGH.
1099 */
1100static void make_argh(int index)
1101{
1102 int x = -1;
1103 int y = -1;
1104 bool collisionDetected = false;
1105 int i;
1106
1107 do {
1108 /* make coordinates for a new argh so that
1109 the entire food lies within the FIELD */
1110 x = rb->rand() % (FIELD_RECT_WIDTH - argh_size);
1111 y = rb->rand() % (FIELD_RECT_HEIGHT - argh_size);
1112 collisionDetected = false;
1113 /* Ensure that the new argh doesn't intersect with any
1114 existing foods or arghs.
1115 If the new argh hit any existing
1116 argh or food an intersection is detected.
1117 */
1118
1119 for (i=0; i<MAX_FOOD && !collisionDetected; i++) {
1120 collisionDetected = CHECK_SQUARE_COLLISION(x,y,argh_size,foodx[i],foody[i],food_size);
1121 }
1122 for (i=0; i<argh_count && !collisionDetected; i++) {
1123 collisionDetected = CHECK_SQUARE_COLLISION(x,y,argh_size,arghx[i],arghy[i],argh_size);
1124 }
1125
1126 /* use the candidate coordinates to make a real argh */
1127 arghx[index] = x;
1128 arghy[index] = y;
1129
1130 /* now test wether we accidently hit the worm with argh ;) */
1131 for (i = 0; i < worm_count && !collisionDetected; i++) {
1132 collisionDetected |= worm_argh_collision(&worms[i], index);
1133 collisionDetected |= worm_argh_collision_in_moves(&worms[i], index,
1134 MIN_ARGH_DIST);
1135 }
1136 }
1137 while (collisionDetected);
1138 return;
1139}
1140
1141/**
1142 * Draws an argh in the lcd buffer.
1143 * @param int index The index of the argh arrays under which
1144 * the coordinates of the desired argh can be found. Ensure
1145 * that the value is 0 <= index < argh_count <= MAX_ARGH.
1146 */
1147static void draw_argh(int index)
1148{
1149 /* draw the new argh */
1150#ifdef HAVE_LCD_COLOR
1151 rb->lcd_set_foreground(COLOR_ARGH);
1152#endif
1153 rb->lcd_fillrect(arghx[index] + FIELD_RECT_X,
1154 arghy[index] + FIELD_RECT_Y,
1155 argh_size, argh_size);
1156#ifdef HAVE_LCD_COLOR
1157 rb->lcd_set_foreground(COLOR_FG);
1158#endif
1159}
1160
1161static void virtual_player(struct worm *w);
1162/**
1163 * Initialzes the specified worm with INITIAL_WORM_LENGTH
1164 * and the tail at the specified position. The worm will
1165 * be initialized alive and creeping EAST.
1166 * @param struct worm *w The worm that is to be initialized
1167 * @param int x The x coordinate at which the tail of the worm starts.
1168 * x must be 0 <= x < FIELD_RECT_WIDTH.
1169 * @param int y The y coordinate at which the tail of the worm starts
1170 * y must be 0 <= y < FIELD_RECT_WIDTH.
1171 */
1172static void init_worm(struct worm *w, int x, int y)
1173{
1174 /* initialize the worm size */
1175 w->head = 1;
1176 w->tail = 0;
1177
1178 w->x[w->head] = x + 1;
1179 w->y[w->head] = y;
1180
1181 w->x[w->tail] = x;
1182 w->y[w->tail] = y;
1183
1184 /* set the initial direction the worm creeps to */
1185 w->dirx = 1;
1186 w->diry = 0;
1187
1188 w->growing = INITIAL_WORM_LENGTH - 1;
1189 w->alive = true;
1190 w->fetch_worm_direction = virtual_player;
1191}
1192
1193/**
1194 * Writes the direction that was stored for
1195 * human player 1 into the specified worm. This function
1196 * may be used to be stored in worm.fetch_worm_direction.
1197 * The value of
1198 * the direction is read from player1_dir.
1199 * @param struct worm *w - The worm of which the direction
1200 * is altered.
1201 */
1202static void human_player1(struct worm *w) {
1203 set_worm_dir(w, player1_dir);
1204}
1205
1206/**
1207 * Writes the direction that was stored for
1208 * human player 2 into the specified worm. This function
1209 * may be used to be stored in worm.fetch_worm_direction.
1210 * The value of
1211 * the direction is read from player2_dir.
1212 * @param struct worm *w - The worm of which the direction
1213 * is altered.
1214 */
1215static void human_player2(struct worm *w) {
1216 set_worm_dir(w, player2_dir);
1217}
1218
1219/**
1220 * Writes the direction that was stored for
1221 * human player using a remote control
1222 * into the specified worm. This function
1223 * may be used to be stored in worm.fetch_worm_direction.
1224 * The value of
1225 * the direction is read from player3_dir.
1226 * @param struct worm *w - The worm of which the direction
1227 * is altered.
1228 */
1229static void remote_player(struct worm *w) {
1230 set_worm_dir(w, player3_dir);
1231}
1232
1233/**
1234 * Initializes the worm-, food- and argh-arrays, draws a frame,
1235 * makes some food and argh and display all that stuff.
1236 */
1237static void init_wormlet(void)
1238{
1239 int i;
1240
1241 for (i = 0; i< worm_count; i++) {
1242 /* Initialize all the worm coordinates to center. */
1243 int x = (int)(FIELD_RECT_WIDTH / 2);
1244 int y = (int)((FIELD_RECT_HEIGHT - 20)/ 2) + i * 10;
1245
1246 init_worm(&worms[i], x, y);
1247 }
1248
1249 player1_dir = EAST;
1250 player2_dir = EAST;
1251 player3_dir = EAST;
1252
1253 if (players > 0) {
1254 worms[0].fetch_worm_direction = human_player1;
1255 }
1256
1257 if (players > 1) {
1258 if (use_remote) {
1259 worms[1].fetch_worm_direction = remote_player;
1260 } else {
1261 worms[1].fetch_worm_direction = human_player2;
1262 }
1263 }
1264
1265 if (players > 2) {
1266 worms[2].fetch_worm_direction = human_player2;
1267 }
1268
1269 /* Needed when the game is restarted using BTN_STOPRESET */
1270 rb->lcd_clear_display();
1271
1272 /* make and display some food and argh */
1273 argh_count = MAX_FOOD;
1274 for (i = 0; i < MAX_FOOD; i++) {
1275 make_food(i);
1276 draw_food(i);
1277 make_argh(i);
1278 draw_argh(i);
1279 }
1280
1281 /* draw the game field */
1282 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
1283 rb->lcd_fillrect(0, 0, FIELD_RECT_WIDTH + 2, FIELD_RECT_HEIGHT + 2);
1284 rb->lcd_fillrect(1, 1, FIELD_RECT_WIDTH, FIELD_RECT_HEIGHT);
1285 rb->lcd_set_drawmode(DRMODE_SOLID);
1286
1287 /* make everything visible */
1288 rb->lcd_update();
1289}
1290
1291
1292/**
1293 * Move the worm one step further if it is alive.
1294 * The direction in which the worm moves is taken from dirx and diry.
1295 * move_worm decreases growing if > 0. While the worm is growing the tail
1296 * is left untouched.
1297 * @param struct worm *w The worm to move. w must not be NULL.
1298 */
1299static void move_worm(struct worm *w)
1300{
1301 if (w->alive) {
1302 /* determine the head point and its precessor */
1303 int headx = w->x[w->head];
1304 int heady = w->y[w->head];
1305 int prehead = (w->head + MAX_WORM_SEGMENTS - 1) % MAX_WORM_SEGMENTS;
1306 int preheadx = w->x[prehead];
1307 int preheady = w->y[prehead];
1308
1309 /* determine the old direction */
1310 int olddirx;
1311 int olddiry;
1312 if (headx == preheadx) {
1313 olddirx = 0;
1314 olddiry = (heady > preheady) ? 1 : -1;
1315 } else {
1316 olddiry = 0;
1317 olddirx = (headx > preheadx) ? 1 : -1;
1318 }
1319
1320 /* olddir == dir?
1321 a change of direction means a new segment
1322 has been opened */
1323 if (olddirx != w->dirx ||
1324 olddiry != w->diry) {
1325 w->head = (w->head + 1) % MAX_WORM_SEGMENTS;
1326 }
1327
1328 /* new head position */
1329 w->x[w->head] = headx + w->dirx;
1330 w->y[w->head] = heady + w->diry;
1331
1332
1333 /* while the worm is growing no tail procession is necessary */
1334 if (w->growing > 0) {
1335 /* update the worms grow state */
1336 w->growing--;
1337 }
1338
1339 /* if the worm isn't growing the tail has to be dragged */
1340 else {
1341 /* index of the end of the tail segment */
1342 int tail_segment_end = (w->tail + 1) % MAX_WORM_SEGMENTS;
1343
1344 /* drag the end of the tail */
1345 /* only one coordinate has to be altered. Here it is
1346 determined which one */
1347 int dir = 0; /* specifies wether the coord has to be in- or decreased */
1348 if (w->x[w->tail] == w->x[tail_segment_end]) {
1349 dir = (w->y[w->tail] - w->y[tail_segment_end] < 0) ? 1 : -1;
1350 w->y[w->tail] += dir;
1351 } else {
1352 dir = (w->x[w->tail] - w->x[tail_segment_end] < 0) ? 1 : -1;
1353 w->x[w->tail] += dir;
1354 }
1355
1356 /* when the tail has been dragged so far that it meets
1357 the next segment start the tail segment is obsolete and
1358 must be freed */
1359 if (w->x[w->tail] == w->x[tail_segment_end] &&
1360 w->y[w->tail] == w->y[tail_segment_end]){
1361
1362 /* drop the last tail point */
1363 w->tail = tail_segment_end;
1364 }
1365 }
1366 }
1367}
1368
1369/**
1370 * Draws the head and clears the tail of the worm in
1371 * the display buffer. lcd_update() is NOT called thus
1372 * the caller has to take care that the buffer is displayed.
1373 */
1374static void draw_worm(struct worm *w)
1375{
1376 /* draw the new head */
1377 int x = w->x[w->head];
1378 int y = w->y[w->head];
1379#ifdef HAVE_LCD_COLOR
1380 rb->lcd_set_foreground(COLOR_WORM);
1381#endif
1382 if (x >= 0 && x < FIELD_RECT_WIDTH && y >= 0 && y < FIELD_RECT_HEIGHT) {
1383 rb->lcd_drawpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y);
1384 }
1385
1386 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1387
1388 /* clear the space behind the worm */
1389 x = w->x[w->tail] ;
1390 y = w->y[w->tail] ;
1391 if (x >= 0 && x < FIELD_RECT_WIDTH && y >= 0 && y < FIELD_RECT_HEIGHT) {
1392 rb->lcd_drawpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y);
1393 }
1394 rb->lcd_set_drawmode(DRMODE_SOLID);
1395#ifdef HAVE_LCD_COLOR
1396 rb->lcd_set_foreground(COLOR_FG);
1397#endif
1398}
1399
1400/**
1401 * Checks wether the coordinate is part of the worm. Returns
1402 * true if any part of the worm was hit - including the head.
1403 * @param x int The x coordinate
1404 * @param y int The y coordinate
1405 * @return int The index of the worm arrays that contain x, y.
1406 * Returns -1 if the coordinates are not part of the worm.
1407 */
1408static int specific_worm_collision(struct worm *w, int x, int y)
1409{
1410 int retVal = -1;
1411
1412 /* get_worm_array_length is expensive -> buffer the value */
1413 int wormLength = get_worm_array_length(w);
1414 int i;
1415
1416 /* test each entry that is part of the worm */
1417 for (i = 0; i < wormLength && retVal == -1; i++) {
1418
1419 /* The iteration iterates the length of the worm.
1420 Here's the conversion to the true indices within the worm arrays. */
1421 int linestart = (w->tail + i ) % MAX_WORM_SEGMENTS;
1422 int lineend = (linestart + 1) % MAX_WORM_SEGMENTS;
1423 bool samex = (w->x[linestart] == x) && (w->x[lineend] == x);
1424 bool samey = (w->y[linestart] == y) && (w->y[lineend] == y);
1425 if (samex || samey){
1426 int test, min, max, tmp;
1427
1428 if (samey) {
1429 min = w->x[linestart];
1430 max = w->x[lineend];
1431 test = x;
1432 } else {
1433 min = w->y[linestart];
1434 max = w->y[lineend];
1435 test = y;
1436 }
1437
1438 tmp = min;
1439 min = MIN(min, max);
1440 max = MAX(tmp, max);
1441
1442 if (min <= test && test <= max) {
1443 retVal = lineend;
1444 }
1445 }
1446 }
1447 return retVal;
1448}
1449
1450/**
1451 * Increases the length of the specified worm by marking
1452 * that it may grow by len pixels. Note that the worm has
1453 * to move to make the growing happen.
1454 * @param worm *w The worm that is to be altered.
1455 * @param int len A positive value specifying the amount of
1456 * pixels the worm may grow.
1457 */
1458static void add_growing(struct worm *w, int len) {
1459 w->growing += len;
1460}
1461
1462/**
1463 * Determins the worm that is at the coordinates x, y. The parameter
1464 * w is a switch parameter that changes the functionality of worm_collision.
1465 * If w is specified and x,y hits the head of w NULL is returned.
1466 * This is a useful way to determine wether the head of w hits
1467 * any worm but including itself but excluding its own head.
1468 * (It hits always its own head ;))
1469 * If w is set to NULL worm_collision returns any worm including all heads
1470 * that is at position of x,y.
1471 * @param struct worm *w The worm of which the head should be excluded in
1472 * the test. w may be set to NULL.
1473 * @param int x The x coordinate that is checked
1474 * @param int y The y coordinate that is checkec
1475 * @return struct worm* The worm that has been hit by x,y. If no worm
1476 * was at the position NULL is returned.
1477 */
1478static struct worm* worm_collision(struct worm *w, int x, int y)
1479{
1480 struct worm *retVal = NULL;
1481 int i;
1482 for (i = 0; (i < worm_count) && (retVal == NULL); i++) {
1483 int collision_at = specific_worm_collision(&worms[i], x, y);
1484 if (collision_at != -1) {
1485 if (!(w == &worms[i] && collision_at == w->head)){
1486 retVal = &worms[i];
1487 }
1488 }
1489 }
1490 return retVal;
1491}
1492
1493/**
1494 * Returns true if the head of the worm just has
1495 * crossed the field boundaries.
1496 * @return bool true if the worm just has wrapped.
1497 */
1498static bool field_collision(struct worm *w)
1499{
1500 bool retVal = false;
1501 if ((w->x[w->head] >= FIELD_RECT_WIDTH) ||
1502 (w->y[w->head] >= FIELD_RECT_HEIGHT) ||
1503 (w->x[w->head] < 0) ||
1504 (w->y[w->head] < 0))
1505 {
1506 retVal = true;
1507 }
1508 return retVal;
1509}
1510
1511
1512/**
1513 * Returns true if the specified coordinates are within the
1514 * field specified by the FIELD_RECT_XXX constants.
1515 * @param int x The x coordinate of the point that is investigated
1516 * @param int y The y coordinate of the point that is investigated
1517 * @return bool Returns false if x,y specifies a point outside the
1518 * field of worms.
1519 */
1520static bool is_in_field_rect(int x, int y)
1521{
1522 bool retVal = false;
1523 retVal = (x >= 0 && x < FIELD_RECT_WIDTH &&
1524 y >= 0 && y < FIELD_RECT_HEIGHT);
1525 return retVal;
1526}
1527
1528/**
1529 * Checks and returns wether the head of the w
1530 * is colliding with something currently.
1531 * @return int One of the values:
1532 * COLLISION_NONE
1533 * COLLISION_w
1534 * COLLISION_FOOD
1535 * COLLISION_ARGH
1536 * COLLISION_FIELD
1537 */
1538static int check_collision(struct worm *w)
1539{
1540 int retVal = COLLISION_NONE;
1541
1542 if (worm_collision(w, w->x[w->head], w->y[w->head]) != NULL)
1543 retVal = COLLISION_WORM;
1544
1545 if (food_collision(w->x[w->head], w->y[w->head]) >= 0)
1546 retVal = COLLISION_FOOD;
1547
1548 if (argh_collision(w->x[w->head], w->y[w->head]) >= 0)
1549 retVal = COLLISION_ARGH;
1550
1551 if (field_collision(w))
1552 retVal = COLLISION_FIELD;
1553
1554 return retVal;
1555}
1556
1557/**
1558 * Returns the index of the food that is closest to the point
1559 * specified by x, y. This index may be used in the foodx and
1560 * foody arrays.
1561 * @param int x The x coordinate of the point
1562 * @param int y The y coordinate of the point
1563 * @return int A value usable as index in foodx and foody.
1564 */
1565static int get_nearest_food(int x, int y)
1566{
1567 int nearestfood = 0;
1568 int olddistance = FIELD_RECT_WIDTH + FIELD_RECT_HEIGHT;
1569 int deltax = 0;
1570 int deltay = 0;
1571 int foodindex;
1572 for (foodindex = 0; foodindex < MAX_FOOD; foodindex++) {
1573 int distance;
1574 deltax = foodx[foodindex] - x;
1575 deltay = foody[foodindex] - y;
1576 deltax = deltax > 0 ? deltax : deltax * (-1);
1577 deltay = deltay > 0 ? deltay : deltay * (-1);
1578 distance = deltax + deltay;
1579
1580 if (distance < olddistance) {
1581 olddistance = distance;
1582 nearestfood = foodindex;
1583 }
1584 }
1585 return nearestfood;
1586}
1587
1588/**
1589 * Returns wether the specified position is next to the worm
1590 * and in the direction the worm looks. Use this method to
1591 * test wether this position would be hit with the next move of
1592 * the worm unless the worm changes its direction.
1593 * @param struct worm *w - The worm to be investigated
1594 * @param int x - The x coordinate of the position to test.
1595 * @param int y - The y coordinate of the position to test.
1596 * @return Returns true if the worm will hit the position unless
1597 * it change its direction before the next move.
1598 */
1599static bool is_in_front_of_worm(struct worm *w, int x, int y)
1600{
1601 bool infront = false;
1602 int deltax = x - w->x[w->head];
1603 int deltay = y - w->y[w->head];
1604
1605 if (w->dirx == 0) {
1606 infront = (w->diry * deltay) > 0;
1607 } else {
1608 infront = (w->dirx * deltax) > 0;
1609 }
1610 return infront;
1611}
1612
1613/**
1614 * Returns true if the worm will collide with the next move unless
1615 * it changes its direction.
1616 * @param struct worm *w - The worm to be investigated.
1617 * @return Returns true if the worm will collide with the next move
1618 * unless it changes its direction.
1619 */
1620static bool will_worm_collide(struct worm *w)
1621{
1622 int x = w->x[w->head] + w->dirx;
1623 int y = w->y[w->head] + w->diry;
1624 bool retVal = !is_in_field_rect(x, y);
1625 if (!retVal) {
1626 retVal = (argh_collision(x, y) != -1);
1627 }
1628
1629 if (!retVal) {
1630 retVal = (worm_collision(w, x, y) != NULL);
1631 }
1632 return retVal;
1633}
1634
1635/**
1636 * This function
1637 * may be used to be stored in worm.fetch_worm_direction for
1638 * worms that are not controlled by humans but by artificial stupidity.
1639 * A direction is searched that doesn't lead to collision but to the nearest
1640 * food - but not very intelligent. The direction is written to the specified
1641 * worm.
1642 * @param struct worm *w - The worm of which the direction
1643 * is altered.
1644 */
1645static void virtual_player(struct worm *w)
1646{
1647 bool isright;
1648 int plana, planb, planc;
1649 /* find the next lunch */
1650 int nearestfood = get_nearest_food(w->x[w->head], w->y[w->head]);
1651
1652 /* determine in which direction it is */
1653
1654 /* in front of me? */
1655 bool infront = is_in_front_of_worm(w, foodx[nearestfood], foody[nearestfood]);
1656
1657 /* left right of me? */
1658 int olddir = get_worm_dir(w);
1659 set_worm_dir(w, (olddir + 1) % 4);
1660 isright = is_in_front_of_worm(w, foodx[nearestfood], foody[nearestfood]);
1661 set_worm_dir(w, olddir);
1662
1663 /* detect situation, set strategy */
1664 if (infront) {
1665 if (isright) {
1666 plana = olddir;
1667 planb = (olddir + 1) % 4;
1668 planc = (olddir + 3) % 4;
1669 } else {
1670 plana = olddir;
1671 planb = (olddir + 3) % 4;
1672 planc = (olddir + 1) % 4;
1673 }
1674 } else {
1675 if (isright) {
1676 plana = (olddir + 1) % 4;
1677 planb = olddir;
1678 planc = (olddir + 3) % 4;
1679 } else {
1680 plana = (olddir + 3) % 4;
1681 planb = olddir;
1682 planc = (olddir + 1) % 4;
1683 }
1684 }
1685
1686 /* test for collision */
1687 set_worm_dir(w, plana);
1688 if (will_worm_collide(w)){
1689
1690 /* plan b */
1691 set_worm_dir(w, planb);
1692
1693 /* test for collision */
1694 if (will_worm_collide(w)) {
1695
1696 /* plan c */
1697 set_worm_dir(w, planc);
1698 }
1699 }
1700}
1701
1702/**
1703 * prints out the score board with all the status information
1704 * about the game.
1705 */
1706static void score_board(void)
1707{
1708 int i;
1709 int y = 0;
1710 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1711 rb->lcd_fillrect(FIELD_RECT_WIDTH + 2, 0,
1712 LCD_WIDTH - FIELD_RECT_WIDTH - 2, LCD_HEIGHT);
1713 rb->lcd_set_drawmode(DRMODE_SOLID);
1714 for (i = 0; i < worm_count; i++) {
1715 int score = get_score(&worms[i]);
1716 int collision = check_collision(&worms[i]);
1717 const char *state_str;
1718
1719 /* high score */
1720 if (worms[i].fetch_worm_direction != virtual_player){
1721 if (highscore < score) {
1722 highscore = score;
1723 }
1724 }
1725
1726 /* worm state */
1727 if (collision == COLLISION_NONE) {
1728 if (worms[i].growing > 0)
1729 state_str = "Growing";
1730 else {
1731 state_str = worms[i].alive ? "Hungry" : "Wormed";
1732 }
1733 } else {
1734 state_str = state_desc[collision];
1735 }
1736
1737 /* length */
1738 rb->lcd_putsxyf(FIELD_RECT_WIDTH + 3, y , "Len:%d", score);
1739 rb->lcd_putsxyf(FIELD_RECT_WIDTH + 3, y+8, state_str);
1740
1741 if (!worms[i].alive){
1742 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
1743 rb->lcd_fillrect(FIELD_RECT_WIDTH + 2, y,
1744 LCD_WIDTH - FIELD_RECT_WIDTH - 2, 17);
1745 rb->lcd_set_drawmode(DRMODE_SOLID);
1746 }
1747 y += 19;
1748 }
1749#ifdef DEBUG_WORMLET
1750 rb->lcd_putsxyf(FIELD_RECT_WIDTH + 3, LCD_HEIGHT - 8, "ticks %d", max_cycle);
1751#else
1752 rb->lcd_putsxyf(FIELD_RECT_WIDTH + 3, LCD_HEIGHT - 8, "Hs: %d", highscore);
1753#endif
1754}
1755
1756/**
1757 * Checks for collisions of the worm and its environment and
1758 * takes appropriate actions like growing the worm or killing it.
1759 * @return bool Returns true if the worm is dead. Returns
1760 * false if the worm is healthy, up and creeping.
1761 */
1762static bool process_collisions(struct worm *w)
1763{
1764 int index = -1;
1765
1766 w->alive &= !field_collision(w);
1767
1768 if (w->alive) {
1769
1770 /* check if food was eaten */
1771 index = food_collision(w->x[w->head], w->y[w->head]);
1772 if (index != -1){
1773 int i;
1774
1775 clear_food(index);
1776 make_food(index);
1777 draw_food(index);
1778
1779 for (i = 0; i < arghs_per_food; i++) {
1780 argh_count++;
1781 if (argh_count > MAX_ARGH)
1782 argh_count = MAX_ARGH;
1783 make_argh(argh_count - 1);
1784 draw_argh(argh_count - 1);
1785 }
1786
1787 add_growing(w, worm_food);
1788
1789 draw_worm(w);
1790 }
1791
1792 /* check if argh was eaten */
1793 else {
1794 index = argh_collision(w->x[w->head], w->y[w->head]);
1795 if (index != -1) {
1796 w->alive = false;
1797 }
1798 else {
1799 if (worm_collision(w, w->x[w->head], w->y[w->head]) != NULL) {
1800 w->alive = false;
1801 }
1802 }
1803 }
1804 }
1805 return !w->alive;
1806}
1807
1808/**
1809 * The main loop of the game.
1810 * @return bool Returns true if the game ended
1811 * with a dead worm. Returns false if the user
1812 * aborted the game manually.
1813 */
1814static int run(void)
1815{
1816 int button = 0;
1817 int wormDead = false;
1818 bool paused = false;
1819
1820 /* ticks are counted to compensate speed variations */
1821 long cycle_start = 0, cycle_end = 0;
1822#ifdef DEBUG_WORMLET
1823 int ticks_to_max_cycle_reset = 20;
1824 max_cycle = 0;
1825#endif
1826
1827 /* initialize the board and so on */
1828 init_wormlet();
1829
1830 cycle_start = *rb->current_tick;
1831 /* change the direction of the worm */
1832 while (!wormDead)
1833 {
1834 int i;
1835 long cycle_duration=0;
1836
1837#ifdef HAS_BUTTON_HOLD
1838 if (rb->button_hold())
1839 paused = true;
1840#endif
1841
1842 switch (button) {
1843 case BTN_STARTPAUSE:
1844 paused = !paused;
1845 break;
1846 case BTN_STOPRESET:
1847 if (paused)
1848 return 1; /* restart game */
1849 else
1850 paused = true;
1851 break;
1852#ifdef BTN_RC_QUIT
1853 case BTN_RC_QUIT:
1854#endif
1855 case BTN_QUIT:
1856 return 2; /* back to menu */
1857 break;
1858 }
1859 if (!paused)
1860 {
1861 switch (button) {
1862 case BTN_DIR_UP:
1863 if (players == 1 && !use_remote) {
1864 player1_dir = NORTH;
1865 }
1866 break;
1867
1868 case BTN_DIR_DOWN:
1869 if (players == 1 && !use_remote) {
1870 player1_dir = SOUTH;
1871 }
1872 break;
1873
1874 case BTN_DIR_LEFT:
1875 if (players != 1 || use_remote) {
1876 player1_dir = (player1_dir + 3) % 4;
1877 } else {
1878 player1_dir = WEST;
1879 }
1880 break;
1881
1882 case BTN_DIR_RIGHT:
1883 if (players != 1 || use_remote) {
1884 player1_dir = (player1_dir + 1) % 4;
1885 } else {
1886 player1_dir = EAST;
1887 }
1888 break;
1889
1890#ifdef MULTIPLAYER
1891 case BTN_PLAYER2_DIR1:
1892 player2_dir = (player2_dir + 3) % 4;
1893 break;
1894
1895 case BTN_PLAYER2_DIR2:
1896 player2_dir = (player2_dir + 1) % 4;
1897 break;
1898#endif
1899
1900#ifdef REMOTE
1901 case BTN_RC_UP:
1902 player3_dir = (player3_dir + 1) % 4;
1903 break;
1904
1905 case BTN_RC_DOWN:
1906 player3_dir = (player3_dir + 3) % 4;
1907 break;
1908#endif
1909 }
1910
1911
1912 for (i = 0; i < worm_count; i++) {
1913 worms[i].fetch_worm_direction(&worms[i]);
1914 }
1915
1916 wormDead = true;
1917 for (i = 0; i < worm_count; i++){
1918 struct worm *w = &worms[i];
1919 move_worm(w);
1920 wormDead &= process_collisions(w);
1921 draw_worm(w);
1922 }
1923 score_board();
1924 rb->lcd_update();
1925 if (button == BTN_STOPRESET) {
1926 wormDead = true;
1927 }
1928
1929 /* here the wormlet game cycle ends
1930 thus the current tick is stored
1931 as end time */
1932 cycle_end = *rb->current_tick;
1933
1934 /* The duration of the game cycle */
1935 cycle_duration = cycle_end - cycle_start;
1936 cycle_duration = MAX(0, cycle_duration);
1937 cycle_duration = MIN(speed -1, cycle_duration);
1938
1939
1940#ifdef DEBUG_WORMLET
1941 ticks_to_max_cycle_reset--;
1942 if (ticks_to_max_cycle_reset <= 0) {
1943 max_cycle = 0;
1944 }
1945
1946 if (max_cycle < cycle_duration) {
1947 max_cycle = cycle_duration;
1948 ticks_to_max_cycle_reset = 20;
1949 }
1950#endif
1951 }
1952 /* adjust the number of ticks to wait for a button.
1953 This ensures that a complete game cycle including
1954 user input runs in constant time */
1955 button = rb->button_get_w_tmo(speed - cycle_duration);
1956 cycle_start = *rb->current_tick;
1957 }
1958
1959 rb->splash(HZ*2, "Game Over!");
1960
1961 return 2; /* back to menu */
1962}
1963
1964#ifdef DEBUG_WORMLET
1965
1966/**
1967 * Just a test routine that checks that worm_food_collision works
1968 * in some typical situations.
1969 */
1970static void test_worm_food_collision(void)
1971{
1972 int collision_count = 0;
1973 int i;
1974 rb->lcd_clear_display();
1975 init_worm(&worms[0], 10, 10);
1976 add_growing(&worms[0], 10);
1977 set_worm_dir(&worms[0], EAST);
1978 for (i = 0; i < 10; i++) {
1979 move_worm(&worms[0]);
1980 draw_worm(&worms[0]);
1981 }
1982
1983 set_worm_dir(&worms[0], SOUTH);
1984 for (i = 0; i < 10; i++) {
1985 move_worm(&worms[0]);
1986 draw_worm(&worms[0]);
1987 }
1988
1989 foodx[0] = 15;
1990 foody[0] = 12;
1991 for (foody[0] = 20; foody[0] > 0; foody[0] --) {
1992 bool collision;
1993 draw_worm(&worms[0]);
1994 draw_food(0);
1995 collision = worm_food_collision(&worms[0], 0);
1996 if (collision) {
1997 collision_count++;
1998 }
1999 rb->lcd_putsxyf(0, LCD_HEIGHT -8, "collisions: %d", collision_count);
2000 rb->lcd_update();
2001 }
2002 if (collision_count != food_size) {
2003 rb->button_get(true);
2004 }
2005
2006
2007 foody[0] = 15;
2008 for (foodx[0] = 30; foodx[0] > 0; foodx[0] --) {
2009 bool collision;
2010 draw_worm(&worms[0]);
2011 draw_food(0);
2012 collision = worm_food_collision(&worms[0], 0);
2013 if (collision) {
2014 collision_count ++;
2015 }
2016 rb->lcd_putsxyf(0, LCD_HEIGHT -8, "collisions: %d", collision_count);
2017 rb->lcd_update();
2018 }
2019 if (collision_count != food_size * 2) {
2020 rb->button_get(true);
2021 }
2022
2023}
2024
2025static bool expensive_worm_in_rect(struct worm *w, int rx, int ry, int rw, int rh)
2026{
2027 int x, y;
2028 bool retVal = false;
2029 for (x = rx; x < rx + rw; x++){
2030 for (y = ry; y < ry + rh; y++) {
2031 if (specific_worm_collision(w, x, y) != -1) {
2032 retVal = true;
2033 }
2034 }
2035 }
2036 return retVal;
2037}
2038
2039static void test_worm_argh_collision(void)
2040{
2041 int i;
2042 int dir;
2043 int collision_count = 0;
2044 rb->lcd_clear_display();
2045 init_worm(&worms[0], 10, 10);
2046 add_growing(&worms[0], 40);
2047 for (dir = 0; dir < 4; dir++) {
2048 set_worm_dir(&worms[0], (EAST + dir) % 4);
2049 for (i = 0; i < 10; i++) {
2050 move_worm(&worms[0]);
2051 draw_worm(&worms[0]);
2052 }
2053 }
2054
2055 arghx[0] = 12;
2056 for (arghy[0] = 0; arghy[0] < FIELD_RECT_HEIGHT - argh_size; arghy[0]++){
2057 bool collision;
2058 draw_argh(0);
2059 collision = worm_argh_collision(&worms[0], 0);
2060 if (collision) {
2061 collision_count ++;
2062 }
2063 rb->lcd_putsxyf(0, LCD_HEIGHT -8, "collisions: %d", collision_count);
2064 rb->lcd_update();
2065 }
2066 if (collision_count != argh_size * 2) {
2067 rb->button_get(true);
2068 }
2069
2070 arghy[0] = 12;
2071 for (arghx[0] = 0; arghx[0] < FIELD_RECT_HEIGHT - argh_size; arghx[0]++){
2072 bool collision;
2073 draw_argh(0);
2074 collision = worm_argh_collision(&worms[0], 0);
2075 if (collision) {
2076 collision_count ++;
2077 }
2078 rb->lcd_putsxyf(0, LCD_HEIGHT -8, "collisions: %d", collision_count);
2079 rb->lcd_update();
2080 }
2081 if (collision_count != argh_size * 4) {
2082 rb->button_get(true);
2083 }
2084}
2085
2086static int testline_in_rect(void)
2087{
2088 int testfailed = -1;
2089
2090 int rx = 10;
2091 int ry = 15;
2092 int rw = 20;
2093 int rh = 25;
2094
2095 /* Test 1 */
2096 int x1 = 12;
2097 int y1 = 8;
2098 int x2 = 12;
2099 int y2 = 42;
2100
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_update();
2106 rb->lcd_putsxy(0, 0, "failed 1");
2107 rb->button_get(true);
2108 testfailed = 1;
2109 }
2110
2111 /* test 2 */
2112 y2 = 20;
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 2");
2118 rb->lcd_update();
2119 rb->button_get(true);
2120 testfailed = 2;
2121 }
2122
2123 /* test 3 */
2124 y1 = 30;
2125 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2126 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2127 rb->lcd_drawrect(rx, ry, rw, rh);
2128 rb->lcd_drawline(x1, y1, x2, y2);
2129 rb->lcd_putsxy(0, 0, "failed 3");
2130 rb->lcd_update();
2131 rb->button_get(true);
2132 testfailed = 3;
2133 }
2134
2135 /* test 4 */
2136 y2 = 45;
2137 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2138 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2139 rb->lcd_drawrect(rx, ry, rw, rh);
2140 rb->lcd_drawline(x1, y1, x2, y2);
2141 rb->lcd_putsxy(0, 0, "failed 4");
2142 rb->lcd_update();
2143 rb->button_get(true);
2144 testfailed = 4;
2145 }
2146
2147 /* test 5 */
2148 y1 = 50;
2149 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
2150 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2151 rb->lcd_drawrect(rx, ry, rw, rh);
2152 rb->lcd_drawline(x1, y1, x2, y2);
2153 rb->lcd_putsxy(0, 0, "failed 5");
2154 rb->lcd_update();
2155 rb->button_get(true);
2156 testfailed = 5;
2157 }
2158
2159 /* test 6 */
2160 y1 = 5;
2161 y2 = 7;
2162 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
2163 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2164 rb->lcd_drawrect(rx, ry, rw, rh);
2165 rb->lcd_drawline(x1, y1, x2, y2);
2166 rb->lcd_putsxy(0, 0, "failed 6");
2167 rb->lcd_update();
2168 rb->button_get(true);
2169 testfailed = 6;
2170 }
2171
2172 /* test 7 */
2173 x1 = 8;
2174 y1 = 20;
2175 x2 = 35;
2176 y2 = 20;
2177 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2178 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2179 rb->lcd_drawrect(rx, ry, rw, rh);
2180 rb->lcd_drawline(x1, y1, x2, y2);
2181 rb->lcd_putsxy(0, 0, "failed 7");
2182 rb->lcd_update();
2183 rb->button_get(true);
2184 testfailed = 7;
2185 }
2186
2187 /* test 8 */
2188 x2 = 12;
2189 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2190 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2191 rb->lcd_drawrect(rx, ry, rw, rh);
2192 rb->lcd_drawline(x1, y1, x2, y2);
2193 rb->lcd_putsxy(0, 0, "failed 8");
2194 rb->lcd_update();
2195 rb->button_get(true);
2196 testfailed = 8;
2197 }
2198
2199 /* test 9 */
2200 x1 = 25;
2201 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2202 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2203 rb->lcd_drawrect(rx, ry, rw, rh);
2204 rb->lcd_drawline(x1, y1, x2, y2);
2205 rb->lcd_putsxy(0, 0, "failed 9");
2206 rb->lcd_update();
2207 rb->button_get(true);
2208 testfailed = 9;
2209 }
2210
2211 /* test 10 */
2212 x2 = 37;
2213 if (!line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2214 !line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2215 rb->lcd_drawrect(rx, ry, rw, rh);
2216 rb->lcd_drawline(x1, y1, x2, y2);
2217 rb->lcd_putsxy(0, 0, "failed 10");
2218 rb->lcd_update();
2219 rb->button_get(true);
2220 testfailed = 10;
2221 }
2222
2223 /* test 11 */
2224 x1 = 42;
2225 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
2226 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2227 rb->lcd_drawrect(rx, ry, rw, rh);
2228 rb->lcd_drawline(x1, y1, x2, y2);
2229 rb->lcd_putsxy(0, 0, "failed 11");
2230 rb->lcd_update();
2231 rb->button_get(true);
2232 testfailed = 11;
2233 }
2234
2235 /* test 12 */
2236 x1 = 5;
2237 x2 = 7;
2238 if (line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) ||
2239 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh)) {
2240 rb->lcd_drawrect(rx, ry, rw, rh);
2241 rb->lcd_drawline(x1, y1, x2, y2);
2242 rb->lcd_putsxy(0, 0, "failed 12");
2243 rb->lcd_update();
2244 rb->button_get(true);
2245 testfailed = 12;
2246 }
2247
2248 /* test 13 */
2249 rx = 9;
2250 ry = 15;
2251 rw = food_size;
2252 rh = food_size;
2253
2254 x1 = 10;
2255 y1 = 10;
2256 x2 = 10;
2257 y2 = 20;
2258 if (!(line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2259 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh))) {
2260 rb->lcd_drawrect(rx, ry, rw, rh);
2261 rb->lcd_drawline(x1, y1, x2, y2);
2262 rb->lcd_putsxy(0, 0, "failed 13");
2263 rb->lcd_update();
2264 rb->button_get(true);
2265 testfailed = 13;
2266 }
2267
2268 /* test 14 */
2269 rx = 9;
2270 ry = 15;
2271 rw = 4;
2272 rh = 4;
2273
2274 x1 = 10;
2275 y1 = 10;
2276 x2 = 10;
2277 y2 = 19;
2278 if (!(line_in_rect(x1, y1, x2, y2, rx, ry, rw, rh) &&
2279 line_in_rect(x2, y2, x1, y1, rx, ry, rw, rh))) {
2280 rb->lcd_drawline(x1, y1, x2, y2);
2281 rb->lcd_invertrect(rx, ry, rw, rh);
2282 rb->lcd_putsxy(0, 0, "failed 14");
2283 rb->lcd_update();
2284 rb->button_get(true);
2285 testfailed = 14;
2286 }
2287
2288 rb->lcd_clear_display();
2289
2290 return testfailed;
2291}
2292
2293/**
2294 * Just a test routine to test wether specific_worm_collision might work properly
2295 */
2296static int test_specific_worm_collision(void)
2297{
2298 int collisions = 0;
2299 int dir;
2300 int x = 0;
2301 int y = 0;
2302 rb->lcd_clear_display();
2303 init_worm(&worms[0], 10, 20);
2304 add_growing(&worms[0], 20 - INITIAL_WORM_LENGTH);
2305
2306 for (dir = EAST; dir < EAST + 4; dir++) {
2307 int i;
2308 set_worm_dir(&worms[0], dir % 4);
2309 for (i = 0; i < 5; i++) {
2310 if (!(dir % 4 == NORTH && i == 9)) {
2311 move_worm(&worms[0]);
2312 draw_worm(&worms[0]);
2313 }
2314 }
2315 }
2316
2317 for (y = 15; y < 30; y ++){
2318 for (x = 5; x < 20; x++) {
2319 if (specific_worm_collision(&worms[0], x, y) != -1) {
2320 collisions ++;
2321 }
2322 rb->lcd_invertpixel(x + FIELD_RECT_X, y + FIELD_RECT_Y);
2323 rb->lcd_putsxyf(0, LCD_HEIGHT - 8, "collisions %d", collisions);
2324 rb->lcd_update();
2325 }
2326 }
2327 if (collisions != 21) {
2328 rb->button_get(true);
2329 }
2330 return collisions;
2331}
2332
2333static void test_make_argh(void)
2334{
2335 int dir;
2336 int seed = 0;
2337 int hit = 0;
2338 int failures = 0;
2339 int last_failures = 0;
2340 int i, worm_idx;
2341 rb->lcd_clear_display();
2342 worm_count = 3;
2343
2344 for (worm_idx = 0; worm_idx < worm_count; worm_idx++) {
2345 init_worm(&worms[worm_idx], 10 + worm_idx * 20, 20);
2346 add_growing(&worms[worm_idx], 40 - INITIAL_WORM_LENGTH);
2347 }
2348
2349 for (dir = EAST; dir < EAST + 4; dir++) {
2350 for (worm_idx = 0; worm_idx < worm_count; worm_idx++) {
2351 set_worm_dir(&worms[worm_idx], dir % 4);
2352 for (i = 0; i < 10; i++) {
2353 if (!(dir % 4 == NORTH && i == 9)) {
2354 move_worm(&worms[worm_idx]);
2355 draw_worm(&worms[worm_idx]);
2356 }
2357 }
2358 }
2359 }
2360
2361 rb->lcd_update();
2362
2363 for (seed = 0; hit < 20; seed += 2) {
2364 int x, y;
2365 rb->srand(seed);
2366 x = rb->rand() % (FIELD_RECT_WIDTH - argh_size);
2367 y = rb->rand() % (FIELD_RECT_HEIGHT - argh_size);
2368
2369 for (worm_idx = 0; worm_idx < worm_count; worm_idx++){
2370 if (expensive_worm_in_rect(&worms[worm_idx], x, y, argh_size, argh_size)) {
2371 int tries = 0;
2372 rb->srand(seed);
2373
2374 tries = make_argh(0);
2375 if ((x == arghx[0] && y == arghy[0]) || tries < 2) {
2376 failures ++;
2377 }
2378
2379 rb->lcd_putsxyf(0, LCD_HEIGHT - 8, "(%d;%d) fail%d try%d",
2380 x, y, failures, tries);
2381 rb->lcd_update();
2382 rb->lcd_invertrect(x + FIELD_RECT_X, y+ FIELD_RECT_Y,
2383 argh_size, argh_size);
2384 rb->lcd_update();
2385 draw_argh(0);
2386 rb->lcd_update();
2387 rb->lcd_invertrect(x + FIELD_RECT_X, y + FIELD_RECT_Y,
2388 argh_size, argh_size);
2389 rb->lcd_clearrect(arghx[0] + FIELD_RECT_X, arghy[0] + FIELD_RECT_Y,
2390 argh_size, argh_size);
2391
2392 if (failures > last_failures) {
2393 rb->button_get(true);
2394 }
2395 last_failures = failures;
2396 hit ++;
2397 }
2398 }
2399 }
2400}
2401
2402static void test_worm_argh_collision_in_moves(void) {
2403 int hit_count = 0;
2404 int i;
2405 rb->lcd_clear_display();
2406 init_worm(&worms[0], 10, 20);
2407
2408 arghx[0] = 20;
2409 arghy[0] = 18;
2410 draw_argh(0);
2411
2412 set_worm_dir(&worms[0], EAST);
2413 for (i = 0; i < 20; i++) {
2414 move_worm(&worms[0]);
2415 draw_worm(&worms[0]);
2416 if (worm_argh_collision_in_moves(&worms[0], 0, 5)){
2417 hit_count ++;
2418 }
2419 rb->lcd_putsxyf(0, LCD_HEIGHT - 8, "in 5 moves hits: %d", hit_count);
2420 rb->lcd_update();
2421 }
2422 if (hit_count != argh_size + 5) {
2423 rb->button_get(true);
2424 }
2425}
2426#endif /* DEBUG_WORMLET */
2427
2428/*
2429 * Reverts default settings
2430 */
2431static void default_settings(void)
2432{
2433 arghs_per_food = ARGHS_PER_FOOD;
2434 argh_size = ARGH_SIZE;
2435 food_size = FOOD_SIZE;
2436 speed = SPEED;
2437 worm_food = WORM_PER_FOOD;
2438 players = 1;
2439 worm_count = MAX_WORMS;
2440 use_remote = false;
2441 return;
2442}
2443
2444/*
2445 * Launches the wormlet game
2446 */
2447static bool launch_wormlet(void)
2448{
2449 int game_result = 1;
2450
2451 rb->lcd_clear_display();
2452
2453 /* Turn off backlight timeout */
2454 backlight_ignore_timeout();
2455
2456 /* start the game */
2457 while (game_result == 1)
2458 game_result = run();
2459
2460 switch (game_result)
2461 {
2462 case 2:
2463
2464 /* Turn on backlight timeout (revert to settings) */
2465 backlight_use_settings();
2466
2467 return false;
2468 break;
2469 }
2470 return false;
2471}
2472
2473/**
2474 * Main entry point
2475 */
2476enum plugin_status plugin_start(const void* parameter)
2477{
2478 int result;
2479 int menu_quit = 0;
2480 int new_setting;
2481
2482 (void)(parameter);
2483
2484 default_settings();
2485 if (configfile_load(SETTINGS_FILENAME, config,
2486 sizeof(config)/sizeof(*config),
2487 SETTINGS_MIN_VERSION ) < 0)
2488 {
2489 /* If the loading failed, save a new config file (as the disk is
2490 already spinning) */
2491 configfile_save(SETTINGS_FILENAME, config,
2492 sizeof(config)/sizeof(*config),
2493 SETTINGS_VERSION);
2494 }
2495
2496#ifdef HAVE_LCD_COLOR
2497 rb->lcd_set_foreground(COLOR_FG);
2498 rb->lcd_set_background(COLOR_BG);
2499#endif
2500
2501#if LCD_DEPTH > 1
2502 rb->lcd_set_backdrop(NULL);
2503#endif
2504
2505#ifdef DEBUG_WORMLET
2506 testline_in_rect();
2507 test_worm_argh_collision_in_moves();
2508 test_make_argh();
2509 test_worm_food_collision();
2510 test_worm_argh_collision();
2511 test_specific_worm_collision();
2512#endif
2513
2514 /* Setup screen */
2515
2516 static const struct opt_items noyes[2] = {
2517 { STR(LANG_SET_BOOL_NO) },
2518 { STR(LANG_SET_BOOL_YES) },
2519 };
2520
2521 static const struct opt_items remoteonly_option[1] = {
2522 { STR(LANG_REMOTE_CONTROL) }
2523 };
2524
2525 static const struct opt_items key24_option[2] = {
2526 { STR(LANG_4_KEY_CONTROL) },
2527 { STR(LANG_2_KEY_CONTROL) }
2528 };
2529
2530#ifdef REMOTE
2531 static const struct opt_items remote_option[2] = {
2532 { STR(LANG_REMOTE_CONTROL) },
2533 { STR(LANG_NO_REM_CONTROL) }
2534 };
2535#else
2536 static const struct opt_items key2_option[1] = {
2537 { STR(LANG_2_KEY_CONTROL) }
2538 };
2539#endif
2540
2541 static const struct opt_items nokey_option[1] = {
2542 { STR(LANG_OUT_OF_CONTROL) }
2543 };
2544
2545 MENUITEM_STRINGLIST(menu, "Wormlet Menu", NULL,
2546 ID2P(LANG_PLAY_WORMLET), ID2P(LANG_NUMBER_OF_WORMS),
2547 ID2P(LANG_NUMBER_OF_PLAYERS), ID2P(LANG_CONTROL_STYLE),
2548 ID2P(LANG_WORM_GROWTH_PER_FOOD), ID2P(LANG_WORM_SPEED),
2549 ID2P(LANG_ARGHS_PER_FOOD), ID2P(LANG_ARGH_SIZE),
2550 ID2P(LANG_FOOD_SIZE), ID2P(LANG_REVERT_TO_DEFAULT_SETTINGS),
2551 ID2P(LANG_PLAYBACK_CONTROL), ID2P(LANG_MENU_QUIT));
2552
2553 rb->button_clear_queue();
2554
2555 while (!menu_quit) {
2556 switch(rb->do_menu(&menu, &result, NULL, false))
2557 {
2558 case 0:
2559 rb->lcd_setfont(FONT_SYSFIXED);
2560 launch_wormlet();
2561 break;
2562 case 1:
2563 rb->set_int(rb->str(LANG_NUMBER_OF_WORMS), "", UNIT_INT, &worm_count, NULL,
2564 1, 1, 3, NULL);
2565 if (worm_count < players) {
2566 worm_count = players;
2567 }
2568 break;
2569 case 2:
2570#ifdef MULTIPLAYER
2571 rb->set_int(rb->str(LANG_NUMBER_OF_PLAYERS), "", UNIT_INT, &players, NULL,
2572 1, 0, 4, NULL);
2573#else
2574 rb->set_int(rb->str(LANG_NUMBER_OF_PLAYERS), "", UNIT_INT, &players, NULL,
2575 1, 0, 2, NULL);
2576#endif
2577 if (players > worm_count) {
2578 worm_count = players;
2579 }
2580 if (players > 2) {
2581 use_remote = true;
2582 }
2583 break;
2584 case 3:
2585 switch(players) {
2586 case 0:
2587 rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote, RB_INT,
2588 nokey_option, 1, NULL);
2589 break;
2590 case 1:
2591 rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote, RB_INT,
2592 key24_option, 2, NULL);
2593 break;
2594 case 2:
2595#ifdef REMOTE
2596 rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote, RB_INT,
2597 remote_option, 2, NULL);
2598#else
2599 rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote, RB_INT,
2600 key2_option, 1, NULL);
2601#endif
2602 break;
2603 case 3:
2604 rb->set_option(rb->str(LANG_CONTROL_STYLE),&use_remote, RB_INT,
2605 remoteonly_option, 1, NULL);
2606 break;
2607 }
2608 break;
2609 case 4:
2610 rb->set_int(rb->str(LANG_WORM_GROWTH_PER_FOOD), "", UNIT_INT, &worm_food,
2611 NULL, 1, 0, 15, NULL);
2612 break;
2613 case 5:
2614 new_setting = 20 - speed;
2615 rb->set_int(rb->str(LANG_WORM_SPEED), "", UNIT_INT, &new_setting,
2616 NULL, 1, 0, 20, NULL);
2617 speed = 20 - new_setting;
2618 break;
2619 case 6:
2620 rb->set_int(rb->str(LANG_ARGHS_PER_FOOD), "", UNIT_INT, &arghs_per_food,
2621 NULL, 1, 0, 8, NULL);
2622 break;
2623 case 7:
2624 rb->set_int(rb->str(LANG_ARGH_SIZE), "", UNIT_INT, &argh_size,
2625 NULL, 1, 2, 10, NULL);
2626 break;
2627 case 8:
2628 rb->set_int(rb->str(LANG_FOOD_SIZE), "", UNIT_INT, &food_size,
2629 NULL, 1, 2, 10, NULL);
2630 break;
2631 case 9:
2632 new_setting = 0;
2633 rb->set_option(rb->str(LANG_RESET), &new_setting, RB_INT, noyes , 2, NULL);
2634 if (new_setting == 1)
2635 default_settings();
2636 break;
2637 case 10:
2638 playback_control(NULL);
2639 break;
2640 default:
2641 menu_quit=1;
2642 break;
2643 }
2644 }
2645
2646 configfile_save(SETTINGS_FILENAME, config,
2647 sizeof(config)/sizeof(*config),
2648 SETTINGS_VERSION);
2649
2650 return PLUGIN_OK;
2651}