1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Originally by Joshua Oreman, improved by Prashant Varanasi
11 * Ported to Rockbox by Ben Basha (Paprica)
13 * All files in this archive are subject to the GNU General Public License.
14 * See the file COPYING in the source tree root for full license agreement.
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
19 ****************************************************************************/
23 #include "configfile.h"
28 /* - Make original speed and further increases in speed depend more on screen size*/
29 /* - attempt to make the tunnels get narrower as the game goes on*/
30 /* - make the chopper look better, maybe a picture, and scale according to screen size*/
31 /* - use textures for the color screens for background and terrain, eg stars on background*/
32 /* - allow choice of different levels [later: different screen themes]*/
33 /* - better high score handling, improved screen etc. */
35 #if (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD)
37 #define QUIT BUTTON_OFF
38 #define ACTION BUTTON_UP
39 #define ACTION2 BUTTON_SELECT
40 #define ACTIONTEXT "SELECT"
42 #elif (CONFIG_KEYPAD == IPOD_3G_PAD) || \
43 (CONFIG_KEYPAD == IPOD_4G_PAD)
45 #define QUIT BUTTON_MENU
46 #define ACTION BUTTON_SELECT
47 #define ACTIONTEXT "SELECT"
49 #elif CONFIG_KEYPAD == IAUDIO_X5_PAD /* grayscale at the moment */
51 #define QUIT BUTTON_POWER
52 #define ACTION BUTTON_UP
53 #define ACTION2 BUTTON_SELECT
54 #define ACTIONTEXT "SELECT"
56 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
57 #define QUIT BUTTON_POWER
58 #define ACTION BUTTON_RIGHT
59 #define ACTIONTEXT "RIGHT"
61 #elif CONFIG_KEYPAD == SANSA_E200_PAD
62 #define QUIT BUTTON_POWER
63 #define ACTION BUTTON_SELECT
64 #define ACTIONTEXT "SELECT"
66 #elif CONFIG_KEYPAD == GIGABEAT_PAD
67 #define QUIT BUTTON_MENU
68 #define ACTION BUTTON_SELECT
69 #define ACTIONTEXT "SELECT"
71 #elif CONFIG_KEYPAD == RECORDER_PAD
72 #define QUIT BUTTON_OFF
73 #define ACTION BUTTON_PLAY
74 #define ACTIONTEXT "PLAY"
76 #elif CONFIG_KEYPAD == ONDIO_PAD
77 #define QUIT BUTTON_OFF
78 #define ACTION BUTTON_UP
79 #define ACTION2 BUTTON_MENU
80 #define ACTIONTEXT "UP"
83 #error Unsupported keypad
86 static struct plugin_api
* rb
;
88 #define NUMBER_OF_BLOCKS 8
89 #define NUMBER_OF_PARTICLES 3
90 #define MAX_TERRAIN_NODES 15
92 #define LEVEL_MODE_NORMAL 0
93 #define LEVEL_MODE_STEEP 1
97 #define SCALE(x) ((x)==1 ? (x) : ((x) >> 1))
105 /*Chopper's local variables to track the terrain position etc*/
106 static int chopCounter
;
107 static int iRotorOffset
;
110 static int iPlayerPosX
;
111 static int iPlayerPosY
;
112 static int iCameraPosX
;
113 static int iPlayerSpeedX
;
114 static int iPlayerSpeedY
;
115 static int iLastBlockPlacedPosX
;
116 static int iGravityTimerCountdown
;
117 static int iPlayerAlive
;
118 static int iLevelMode
;
119 static int blockh
,blockw
;
120 static int highscore
;
123 #define CFG_FILE "chopper.cfg"
124 #define MAX_POINTS 50000
125 static struct configdata config
[] =
127 {TYPE_INT
, 0, MAX_POINTS
, &highscore
, "highscore", NULL
, NULL
}
160 struct CTerrainNode mNodes
[MAX_TERRAIN_NODES
];
162 int iLastNodePlacedPosX
;
165 struct CBlock mBlocks
[NUMBER_OF_BLOCKS
];
166 struct CParticle mParticles
[NUMBER_OF_PARTICLES
];
168 struct CTerrain mGround
;
169 struct CTerrain mRoof
;
171 /*Function declarations*/
172 static void chopDrawParticle(struct CParticle
*mParticle
);
173 static void chopDrawBlock(struct CBlock
*mBlock
);
174 static void chopRenderTerrain(struct CTerrain
*ter
);
175 void chopper_load(bool newgame
);
176 void cleanup_chopper(void);
178 static void chopDrawPlayer(int x
,int y
) /* These are SCREEN coords, not world! */
182 rb
->lcd_set_foreground(LCD_RGBPACK(50,50,200));
184 rb
->lcd_set_foreground(LCD_DARKGRAY
);
186 rb
->lcd_fillrect(SCALE(x
+6), SCALE(y
+2), SCALE(12), SCALE(9));
187 rb
->lcd_fillrect(SCALE(x
-3), SCALE(y
+6), SCALE(20), SCALE(3));
190 rb
->lcd_set_foreground(LCD_RGBPACK(50,50,50));
192 rb
->lcd_set_foreground(LCD_DARKGRAY
);
194 rb
->lcd_fillrect(SCALE(x
+10), SCALE(y
), SCALE(2), SCALE(3));
195 rb
->lcd_fillrect(SCALE(x
+10), SCALE(y
), SCALE(1), SCALE(3));
198 rb
->lcd_set_foreground(LCD_RGBPACK(40,40,100));
200 rb
->lcd_set_foreground(LCD_BLACK
);
202 rb
->lcd_drawline(SCALE(x
), SCALE(y
+iRotorOffset
), SCALE(x
+20), SCALE(y
-iRotorOffset
));
205 rb
->lcd_set_foreground(LCD_RGBPACK(20,20,50));
207 rb
->lcd_set_foreground(LCD_BLACK
);
209 rb
->lcd_fillrect(SCALE(x
- 2), SCALE(y
+ 5), SCALE(2), SCALE(5));
213 static void chopClearTerrain(struct CTerrain
*ter
)
215 ter
->iNodesCount
= 0;
219 int iR(int low
,int high
)
221 return low
+rb
->rand()%(high
-low
+1);
224 static void chopCopyTerrain(struct CTerrain
*src
, struct CTerrain
*dest
,
225 int xOffset
,int yOffset
)
229 while(i
< src
->iNodesCount
)
231 dest
->mNodes
[i
].x
= src
->mNodes
[i
].x
+ xOffset
;
232 dest
->mNodes
[i
].y
= src
->mNodes
[i
].y
+ yOffset
;
237 dest
->iNodesCount
= src
->iNodesCount
;
238 dest
->iLastNodePlacedPosX
= src
->iLastNodePlacedPosX
;
242 static void chopAddTerrainNode(struct CTerrain
*ter
, int x
, int y
)
246 if(ter
->iNodesCount
+ 1 >= MAX_TERRAIN_NODES
)
248 /* DEBUGF("ERROR: Not enough nodes!\n"); */
254 i
= ter
->iNodesCount
- 1;
256 ter
->mNodes
[i
].x
= x
;
259 ter
->iLastNodePlacedPosX
= x
;
263 static void chopTerrainNodeDeleteAndShift(struct CTerrain
*ter
,int nodeIndex
)
267 while( i
< ter
->iNodesCount
)
269 ter
->mNodes
[i
- 1] = ter
->mNodes
[i
];
278 int chopUpdateTerrainRecycling(struct CTerrain
*ter
)
283 while(i
< ter
->iNodesCount
)
286 if( iCameraPosX
> ter
->mNodes
[i
].x
)
289 chopTerrainNodeDeleteAndShift(ter
,i
);
291 iNewNodePos
= ter
->iLastNodePlacedPosX
+ 50;
297 if(iLevelMode
== LEVEL_MODE_STEEP
)
300 chopAddTerrainNode(ter
,iNewNodePos
,g
- iR(-v
,v
));
312 int chopTerrainHeightAtPoint(struct CTerrain
*ter
, int pX
)
315 int iNodeIndexOne
=0,iNodeIndexTwo
=0, h
, terY1
, terY2
, terX1
, terX2
, a
, b
;
319 for(i
=1;i
<MAX_TERRAIN_NODES
;i
++)
321 if(ter
->mNodes
[i
].x
> pX
)
323 iNodeIndexOne
= i
- 1;
329 iNodeIndexTwo
= iNodeIndexOne
+ 1;
330 terY1
= ter
->mNodes
[iNodeIndexOne
].y
;
331 terY2
= ter
->mNodes
[iNodeIndexTwo
].y
;
334 terX2
= ter
->mNodes
[iNodeIndexTwo
].x
- ter
->mNodes
[iNodeIndexOne
].x
;
336 pX
-= ter
->mNodes
[iNodeIndexOne
].x
;
349 int chopPointInTerrain(struct CTerrain
*ter
, int pX
, int pY
, int iTestType
)
351 int h
= chopTerrainHeightAtPoint(ter
, pX
);
359 static void chopAddBlock(int x
,int y
,int sx
,int sy
, int indexOverride
)
363 if(indexOverride
< 0)
365 while(mBlocks
[i
].bIsActive
&& i
< NUMBER_OF_BLOCKS
)
367 if(i
==NUMBER_OF_BLOCKS
)
369 DEBUGF("No blocks!\n");
376 mBlocks
[i
].bIsActive
= 1;
377 mBlocks
[i
].iWorldX
= x
;
378 mBlocks
[i
].iWorldY
= y
;
379 mBlocks
[i
].iSizeX
= sx
;
380 mBlocks
[i
].iSizeY
= sy
;
382 iLastBlockPlacedPosX
= x
;
385 static void chopAddParticle(int x
,int y
,int sx
,int sy
)
389 while(mParticles
[i
].bIsActive
&& i
< NUMBER_OF_PARTICLES
)
392 if(i
==NUMBER_OF_PARTICLES
)
395 mParticles
[i
].bIsActive
= 1;
396 mParticles
[i
].iWorldX
= x
;
397 mParticles
[i
].iWorldY
= y
;
398 mParticles
[i
].iSpeedX
= sx
;
399 mParticles
[i
].iSpeedY
= sy
;
402 static void chopGenerateBlockIfNeeded(void)
405 int DistSpeedX
= iPlayerSpeedX
* 5;
406 if(DistSpeedX
<200) DistSpeedX
= 200;
408 while(i
< NUMBER_OF_BLOCKS
)
410 if(!mBlocks
[i
].bIsActive
)
414 iX
= iLastBlockPlacedPosX
+ (350-DistSpeedX
);
418 sY
= blockh
+ iR(1,blockh
/3);
420 chopAddBlock(iX
,iY
,sX
,sY
,i
);
428 static int chopBlockCollideWithPlayer(struct CBlock
*mBlock
)
430 int px
= iPlayerPosX
;
431 int py
= iPlayerPosY
;
433 int x
= mBlock
->iWorldX
-17;
434 int y
= mBlock
->iWorldY
-11;
436 int x2
= x
+ mBlock
->iSizeX
+17;
437 int y2
= y
+ mBlock
->iSizeY
+11;
450 static int chopBlockOffscreen(struct CBlock
*mBlock
)
452 if(mBlock
->iWorldX
+ mBlock
->iSizeX
< iCameraPosX
)
458 static int chopParticleOffscreen(struct CParticle
*mParticle
)
460 if (mParticle
->iWorldX
< iCameraPosX
|| mParticle
->iWorldY
< 0 ||
461 mParticle
->iWorldY
> iScreenY
|| mParticle
->iWorldX
> iCameraPosX
+
470 static void chopKillPlayer(void)
474 for (i
= 0; i
< NUMBER_OF_PARTICLES
; i
++) {
475 mParticles
[i
].bIsActive
= 0;
476 chopAddParticle(iPlayerPosX
+ iR(0,20), iPlayerPosY
+ iR(0,20),
482 if (iPlayerAlive
== 0) {
483 rb
->lcd_set_drawmode(DRMODE_FG
);
485 rb
->lcd_set_foreground(LCD_LIGHTGRAY
);
487 rb
->splash(HZ
, "Game Over");
489 if (score
> highscore
) {
492 rb
->snprintf(scoretext
, sizeof(scoretext
), "New High Score: %d",
494 rb
->splash(HZ
*2, scoretext
);
497 rb
->splash(HZ
/4, "Press " ACTIONTEXT
" to continue");
500 rb
->lcd_set_drawmode(DRMODE_SOLID
);
503 button
= rb
->button_get(true);
510 button
= rb
->button_get(true);
511 if (button
== (ACTION
| BUTTON_REL
)
513 || button
== (ACTION2
| BUTTON_REL
)
528 static void chopDrawTheWorld(void)
532 while(i
< NUMBER_OF_BLOCKS
)
534 if(mBlocks
[i
].bIsActive
)
536 if(chopBlockOffscreen(&mBlocks
[i
]) == 1)
537 mBlocks
[i
].bIsActive
= 0;
539 chopDrawBlock(&mBlocks
[i
]);
547 while(i
< NUMBER_OF_PARTICLES
)
549 if(mParticles
[i
].bIsActive
)
551 if(chopParticleOffscreen(&mParticles
[i
]) == 1)
552 mParticles
[i
].bIsActive
= 0;
554 chopDrawParticle(&mParticles
[i
]);
560 chopRenderTerrain(&mGround
);
561 chopRenderTerrain(&mRoof
);
565 static void chopDrawParticle(struct CParticle
*mParticle
)
568 int iPosX
= (mParticle
->iWorldX
- iCameraPosX
);
569 int iPosY
= (mParticle
->iWorldY
);
571 rb
->lcd_set_foreground(LCD_RGBPACK(192,192,192));
573 rb
->lcd_set_foreground(LCD_LIGHTGRAY
);
575 rb
->lcd_fillrect(SCALE(iPosX
), SCALE(iPosY
), SCALE(3), SCALE(3));
579 static void chopDrawScene(void)
584 rb
->lcd_set_background(LCD_BLACK
);
586 rb
->lcd_set_background(LCD_WHITE
);
588 rb
->lcd_clear_display();
591 chopDrawPlayer(iPlayerPosX
- iCameraPosX
, iPlayerPosY
);
593 score
= -20 + iPlayerPosX
/3;
596 rb
->lcd_set_drawmode(DRMODE_COMPLEMENT
);
598 rb
->lcd_set_drawmode(DRMODE_FG
);
602 rb
->lcd_set_foreground(LCD_BLACK
);
604 rb
->lcd_set_foreground(LCD_WHITE
);
608 rb
->snprintf(s
, sizeof(s
), "Dist: %d", score
);
609 rb
->lcd_putsxy(1, 1, s
);
610 rb
->snprintf(s
, sizeof(s
), "Hi: %d", highscore
);
611 rb
->lcd_getstringsize(s
, &w
, NULL
);
612 rb
->lcd_putsxy(LCD_WIDTH
- 1 - w
, 1, s
);
614 rb
->snprintf(s
, sizeof(s
), "Distance: %d", score
);
615 rb
->lcd_putsxy(2, 2, s
);
616 rb
->snprintf(s
, sizeof(s
), "Best: %d", highscore
);
617 rb
->lcd_getstringsize(s
, &w
, NULL
);
618 rb
->lcd_putsxy(LCD_WIDTH
- 2 - w
, 2, s
);
620 rb
->lcd_set_drawmode(DRMODE_SOLID
);
625 static int chopMenu(int menunum
)
630 bool menu_quit
= false;
632 static const struct menu_item items
[] = {
633 { "Start New Game", NULL
},
634 { "Resume Game", NULL
},
639 static const struct opt_items levels
[2] = {
644 #ifdef HAVE_LCD_COLOR
645 rb
->lcd_set_foreground(LCD_WHITE
);
646 rb
->lcd_set_background(LCD_BLACK
);
648 rb
->lcd_set_foreground(LCD_BLACK
);
649 rb
->lcd_set_background(LCD_WHITE
);
652 rb
->lcd_clear_display();
654 m
= rb
->menu_init(items
, sizeof(items
) / sizeof(*items
),
655 NULL
, NULL
, NULL
, NULL
);
658 result
=rb
->menu_show(m
);
661 case 0: /* Start New Game */
666 case 1: /* Resume Game */
670 } else if(menunum
==0){
671 rb
->splash(HZ
, "No game to resume");
675 rb
->set_option("Level", &iLevelMode
, INT
, levels
, 2, NULL
);
681 case MENU_ATTACHED_USB
:
683 res
= PLUGIN_USB_CONNECTED
;
687 rb
->lcd_clear_display();
692 static int chopGameLoop(void)
694 int move_button
, ret
;
696 int end
, i
=0, bdelay
=0, last_button
=BUTTON_NONE
;
698 if (chopUpdateTerrainRecycling(&mGround
) == 1)
699 /* mirror the sky if we've changed the ground */
700 chopCopyTerrain(&mGround
, &mRoof
, 0, - ( (iScreenY
* 3) / 4));
710 end
= *rb
->current_tick
+ (CYCLETIME
* HZ
) / 1000;
712 if(chopUpdateTerrainRecycling(&mGround
) == 1)
713 /* mirror the sky if we've changed the ground */
714 chopCopyTerrain(&mGround
, &mRoof
, 0, - ( (iScreenY
* 3) / 4));
716 iRotorOffset
= iR(-1,1);
718 /* We need to have this here so particles move when we're dead */
720 for (i
=0; i
< NUMBER_OF_PARTICLES
; i
++)
721 if(mParticles
[i
].bIsActive
== 1)
723 mParticles
[i
].iWorldX
+= mParticles
[i
].iSpeedX
;
724 mParticles
[i
].iWorldY
+= mParticles
[i
].iSpeedY
;
727 /* Redraw the main window: */
731 iGravityTimerCountdown
--;
733 if(iGravityTimerCountdown
<= 0)
735 iGravityTimerCountdown
= 3;
736 chopAddParticle(iPlayerPosX
, iPlayerPosY
+5, 0, 0);
739 if(iLevelMode
== LEVEL_MODE_NORMAL
)
740 chopGenerateBlockIfNeeded();
743 move_button
=rb
->button_status();
744 if (rb
->button_get(false) == QUIT
) {
749 last_button
= BUTTON_NONE
;
750 move_button
= BUTTON_NONE
;
753 switch (move_button
) {
758 if (last_button
!= ACTION
760 && last_button
!= ACTION2
769 if (last_button
== ACTION
771 || last_button
== ACTION2
778 if (rb
->default_event_handler(move_button
) == SYS_USB_CONNECTED
)
779 return PLUGIN_USB_CONNECTED
;
782 last_button
= move_button
;
785 iPlayerSpeedY
= bdelay
;
787 } else if (bdelay
> 0) {
788 iPlayerSpeedY
= bdelay
;
792 iCameraPosX
= iPlayerPosX
- 25;
793 iPlayerPosX
+= iPlayerSpeedX
;
794 iPlayerPosY
+= iPlayerSpeedY
;
797 /* increase speed as we go along */
798 if (chopCounter
== 100){
803 if (iPlayerPosY
> iScreenY
-10 || iPlayerPosY
< -5 ||
804 chopPointInTerrain(&mGround
, iPlayerPosX
, iPlayerPosY
+ 10, 0) ||
805 chopPointInTerrain(&mRoof
, iPlayerPosX
,iPlayerPosY
, 1))
814 for (i
=0; i
< NUMBER_OF_BLOCKS
; i
++)
815 if(mBlocks
[i
].bIsActive
== 1)
816 if(chopBlockCollideWithPlayer(&mBlocks
[i
])) {
824 if (end
> *rb
->current_tick
)
825 rb
->sleep(end
-*rb
->current_tick
);
833 static void chopDrawBlock(struct CBlock
*mBlock
)
835 int iPosX
= (mBlock
->iWorldX
- iCameraPosX
);
836 int iPosY
= (mBlock
->iWorldY
);
838 rb
->lcd_set_foreground(LCD_RGBPACK(100,255,100));
840 rb
->lcd_set_foreground(LCD_BLACK
);
842 rb
->lcd_fillrect(SCALE(iPosX
), SCALE(iPosY
), SCALE(mBlock
->iSizeX
),
843 SCALE(mBlock
->iSizeY
));
847 static void chopRenderTerrain(struct CTerrain
*ter
)
855 if(ter
->mNodes
[0].y
< (LCD_HEIGHT
*SIZE
)/2)
858 ay
=(LCD_HEIGHT
*SIZE
);
860 while(i
< ter
->iNodesCount
&& oldx
< iScreenX
)
863 int x
= ter
->mNodes
[i
-1].x
- iCameraPosX
;
864 int y
= ter
->mNodes
[i
-1].y
;
866 int x2
= ter
->mNodes
[i
].x
- iCameraPosX
;
867 int y2
= ter
->mNodes
[i
].y
;
869 rb
->lcd_set_foreground(LCD_RGBPACK(100,255,100));
871 rb
->lcd_set_foreground(LCD_DARKGRAY
);
874 rb
->lcd_drawline(SCALE(x
), SCALE(y
), SCALE(x2
), SCALE(y2
));
876 xlcd_filltriangle(SCALE(x
), SCALE(y
), SCALE(x2
), SCALE(y2
), SCALE(x2
), SCALE(ay
));
877 xlcd_filltriangle(SCALE(x
), SCALE(ay
), SCALE(x2
), SCALE(y2
), SCALE(x2
), SCALE(ay
));
880 xlcd_filltriangle(SCALE(x
), SCALE(ay
), SCALE(x
), SCALE(y
), SCALE(x2
), SCALE(y2
/ 2));
882 xlcd_filltriangle(SCALE(x
), SCALE(ay
), SCALE(x
), SCALE(y
), SCALE(x2
), SCALE((LCD_HEIGHT
*SIZE
) - ((LCD_HEIGHT
*SIZE
) - y2
) / 2));
891 void chopper_load(bool newgame
)
898 iScreenX
= LCD_WIDTH
* SIZE
;
899 iScreenY
= LCD_HEIGHT
* SIZE
;
900 blockh
= iScreenY
/ 5;
901 blockw
= iScreenX
/ 20;
907 iPlayerPosY
= (iScreenY
* 4) / 10;
908 iLastBlockPlacedPosX
= 0;
909 iGravityTimerCountdown
= 2;
915 for (i
=0; i
< NUMBER_OF_PARTICLES
; i
++)
916 mParticles
[i
].bIsActive
= 0;
918 for (i
=0; i
< NUMBER_OF_BLOCKS
; i
++)
919 mBlocks
[i
].bIsActive
= 0;
922 chopClearTerrain(&mGround
);
924 for (i
=0; i
< MAX_TERRAIN_NODES
; i
++)
925 chopAddTerrainNode(&mGround
,i
* 30,g
- iR(0,20));
927 if (chopUpdateTerrainRecycling(&mGround
) == 1)
928 /* mirror the sky if we've changed the ground */
929 chopCopyTerrain(&mGround
, &mRoof
, 0, - ( (iScreenY
* 3) / 4));
931 iLevelMode
= LEVEL_MODE_NORMAL
;
932 if (iLevelMode
== LEVEL_MODE_NORMAL
)
933 /* make it a bit more exciting, cause it's easy terrain... */
937 /* this is the plugin entry point */
938 enum plugin_status
plugin_start(struct plugin_api
* api
, void* parameter
)
944 rb
->lcd_setfont(FONT_SYSFIXED
);
946 rb
->lcd_set_backdrop(NULL
);
948 #ifdef HAVE_LCD_COLOR
949 rb
->lcd_set_background(LCD_BLACK
);
950 rb
->lcd_set_foreground(LCD_WHITE
);
953 /* Permanently enable the backlight (unless the user has turned it off) */
954 if (rb
->global_settings
->backlight_timeout
> 0)
955 rb
->backlight_set_timeout(1);
957 rb
->srand( *rb
->current_tick
);
961 configfile_load(CFG_FILE
, config
, 1, 0);
964 ret
= chopGameLoop();
966 configfile_save(CFG_FILE
, config
, 1, 0);
968 /* Restore user's original backlight setting */
969 rb
->lcd_setfont(FONT_UI
);
970 rb
->backlight_set_timeout(rb
->global_settings
->backlight_timeout
);