/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2004 Mike Holden * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "plugin.h" PLUGIN_HEADER #ifdef HAVE_LCD_BITMAP #define TIMER_Y 1 #else #define TIMER_Y 0 #endif #define LAP_Y TIMER_Y+1 #define MAX_LAPS 64 #define STOPWATCH_FILE PLUGIN_APPS_DIR "/stopwatch.dat" /* variable button definitions */ #if CONFIG_KEYPAD == RECORDER_PAD #define STOPWATCH_QUIT BUTTON_OFF #define STOPWATCH_START_STOP BUTTON_PLAY #define STOPWATCH_RESET_TIMER BUTTON_LEFT #define STOPWATCH_LAP_TIMER BUTTON_ON #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #elif CONFIG_KEYPAD == ARCHOS_AV300_PAD #define STOPWATCH_QUIT BUTTON_OFF #define STOPWATCH_START_STOP BUTTON_SELECT #define STOPWATCH_RESET_TIMER BUTTON_LEFT #define STOPWATCH_LAP_TIMER BUTTON_ON #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #elif CONFIG_KEYPAD == ONDIO_PAD #define STOPWATCH_QUIT BUTTON_OFF #define STOPWATCH_START_STOP BUTTON_RIGHT #define STOPWATCH_RESET_TIMER BUTTON_LEFT #define STOPWATCH_LAP_TIMER BUTTON_MENU #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #elif CONFIG_KEYPAD == PLAYER_PAD #define STOPWATCH_QUIT BUTTON_MENU #define STOPWATCH_START_STOP BUTTON_PLAY #define STOPWATCH_RESET_TIMER BUTTON_STOP #define STOPWATCH_LAP_TIMER BUTTON_ON #define STOPWATCH_SCROLL_UP BUTTON_RIGHT #define STOPWATCH_SCROLL_DOWN BUTTON_LEFT #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ (CONFIG_KEYPAD == IRIVER_H300_PAD) #define STOPWATCH_QUIT BUTTON_OFF #define STOPWATCH_START_STOP BUTTON_SELECT #define STOPWATCH_RESET_TIMER BUTTON_DOWN #define STOPWATCH_LAP_TIMER BUTTON_ON #define STOPWATCH_SCROLL_UP BUTTON_RIGHT #define STOPWATCH_SCROLL_DOWN BUTTON_LEFT #define STOPWATCH_RC_QUIT BUTTON_RC_STOP #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) #define STOPWATCH_QUIT BUTTON_MENU #define STOPWATCH_START_STOP BUTTON_SELECT #define STOPWATCH_RESET_TIMER BUTTON_LEFT #define STOPWATCH_LAP_TIMER BUTTON_RIGHT #define STOPWATCH_SCROLL_UP BUTTON_SCROLL_FWD #define STOPWATCH_SCROLL_DOWN BUTTON_SCROLL_BACK #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD #define STOPWATCH_QUIT BUTTON_PLAY #define STOPWATCH_START_STOP BUTTON_MODE #define STOPWATCH_RESET_TIMER BUTTON_EQ #define STOPWATCH_LAP_TIMER BUTTON_SELECT #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD #define STOPWATCH_QUIT BUTTON_POWER #define STOPWATCH_START_STOP BUTTON_PLAY #define STOPWATCH_RESET_TIMER BUTTON_REC #define STOPWATCH_LAP_TIMER BUTTON_SELECT #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #elif CONFIG_KEYPAD == GIGABEAT_PAD #define STOPWATCH_QUIT BUTTON_POWER #define STOPWATCH_START_STOP BUTTON_SELECT #define STOPWATCH_RESET_TIMER BUTTON_A #define STOPWATCH_LAP_TIMER BUTTON_MENU #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \ (CONFIG_KEYPAD == SANSA_C200_PAD) || \ (CONFIG_KEYPAD == SANSA_CLIP_PAD) || \ (CONFIG_KEYPAD == SANSA_M200_PAD) #define STOPWATCH_QUIT BUTTON_POWER #define STOPWATCH_START_STOP BUTTON_RIGHT #define STOPWATCH_RESET_TIMER BUTTON_LEFT #define STOPWATCH_LAP_TIMER BUTTON_SELECT #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #elif (CONFIG_KEYPAD == SANSA_FUZE_PAD) #define STOPWATCH_QUIT (BUTTON_HOME|BUTTON_REPEAT) #define STOPWATCH_START_STOP BUTTON_RIGHT #define STOPWATCH_RESET_TIMER BUTTON_LEFT #define STOPWATCH_LAP_TIMER BUTTON_SELECT #define STOPWATCH_SCROLL_UP BUTTON_SCROLL_BACK #define STOPWATCH_SCROLL_DOWN BUTTON_SCROLL_FWD #elif CONFIG_KEYPAD == IRIVER_H10_PAD #define STOPWATCH_QUIT BUTTON_POWER #define STOPWATCH_START_STOP BUTTON_PLAY #define STOPWATCH_RESET_TIMER BUTTON_REW #define STOPWATCH_LAP_TIMER BUTTON_FF #define STOPWATCH_SCROLL_UP BUTTON_SCROLL_UP #define STOPWATCH_SCROLL_DOWN BUTTON_SCROLL_DOWN #elif CONFIG_KEYPAD == MROBE500_PAD #define STOPWATCH_QUIT BUTTON_POWER #elif CONFIG_KEYPAD == GIGABEAT_S_PAD #define STOPWATCH_QUIT BUTTON_BACK #define STOPWATCH_START_STOP BUTTON_PLAY #define STOPWATCH_RESET_TIMER BUTTON_MENU #define STOPWATCH_LAP_TIMER BUTTON_SELECT #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #elif CONFIG_KEYPAD == MROBE100_PAD #define STOPWATCH_QUIT BUTTON_POWER #define STOPWATCH_START_STOP BUTTON_SELECT #define STOPWATCH_RESET_TIMER BUTTON_DISPLAY #define STOPWATCH_LAP_TIMER BUTTON_MENU #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #elif CONFIG_KEYPAD == IAUDIO_M3_PAD #define STOPWATCH_QUIT BUTTON_RC_REC #define STOPWATCH_START_STOP BUTTON_RC_PLAY #define STOPWATCH_RESET_TIMER BUTTON_RC_REW #define STOPWATCH_LAP_TIMER BUTTON_RC_FF #define STOPWATCH_SCROLL_UP BUTTON_RC_VOL_UP #define STOPWATCH_SCROLL_DOWN BUTTON_RC_VOL_DOWN #define STOPWATCH_RC_QUIT BUTTON_REC #elif CONFIG_KEYPAD == COWON_D2_PAD #define STOPWATCH_QUIT BUTTON_POWER #elif CONFIG_KEYPAD == IAUDIO67_PAD #define STOPWATCH_QUIT BUTTON_MENU #define STOPWATCH_START_STOP BUTTON_PLAY #define STOPWATCH_RESET_TIMER BUTTON_STOP #define STOPWATCH_LAP_TIMER BUTTON_LEFT #define STOPWATCH_SCROLL_UP BUTTON_VOLUP #define STOPWATCH_SCROLL_DOWN BUTTON_VOLDOWN #define STOPWATCH_RC_QUIT BUTTON_POWER #elif CONFIG_KEYPAD == CREATIVEZVM_PAD #define STOPWATCH_QUIT BUTTON_BACK #define STOPWATCH_START_STOP BUTTON_PLAY #define STOPWATCH_RESET_TIMER BUTTON_SELECT #define STOPWATCH_LAP_TIMER BUTTON_CUSTOM #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD #define STOPWATCH_QUIT BUTTON_POWER #define STOPWATCH_START_STOP BUTTON_SELECT #define STOPWATCH_RESET_TIMER BUTTON_MENU #define STOPWATCH_LAP_TIMER BUTTON_VIEW #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD #define STOPWATCH_QUIT BUTTON_POWER #define STOPWATCH_START_STOP BUTTON_PLAY #define STOPWATCH_RESET_TIMER BUTTON_MENU #define STOPWATCH_LAP_TIMER BUTTON_RIGHT #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #elif CONFIG_KEYPAD == ONDAVX747_PAD #define STOPWATCH_QUIT BUTTON_POWER #elif CONFIG_KEYPAD == ONDAVX777_PAD #define STOPWATCH_QUIT BUTTON_POWER #elif CONFIG_KEYPAD == SAMSUNG_YH_PAD #define STOPWATCH_QUIT BUTTON_REC #define STOPWATCH_START_STOP BUTTON_PLAY #define STOPWATCH_RESET_TIMER BUTTON_LEFT #define STOPWATCH_LAP_TIMER BUTTON_RIGHT #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD #define STOPWATCH_QUIT BUTTON_REC #define STOPWATCH_START_STOP BUTTON_PLAY #define STOPWATCH_RESET_TIMER BUTTON_OK #define STOPWATCH_LAP_TIMER BUTTON_MENU #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN #else #error No keymap defined! #endif #ifdef HAVE_TOUCHSCREEN #ifndef STOPWATCH_QUIT #define STOPWATCH_QUIT BUTTON_TOPLEFT #endif #ifndef STOPWATCH_START_STOP #define STOPWATCH_START_STOP BUTTON_CENTER #endif #ifndef STOPWATCH_RESET_TIMER #define STOPWATCH_RESET_TIMER BUTTON_MIDRIGHT #endif #ifndef STOPWATCH_LAP_TIMER #define STOPWATCH_LAP_TIMER BUTTON_MIDLEFT #endif #ifndef STOPWATCH_SCROLL_UP #define STOPWATCH_SCROLL_UP BUTTON_TOPMIDDLE #endif #ifndef STOPWATCH_SCROLL_DOWN #define STOPWATCH_SCROLL_DOWN BUTTON_BOTTOMMIDDLE #endif #endif static int stopwatch = 0; static long start_at = 0; static int prev_total = 0; static bool counting = false; static int curr_lap = 0; static int lap_scroll = 0; static int lap_start; static int lap_times[MAX_LAPS]; static void ticks_to_string(int ticks,int lap,int buflen, char * buf) { int hours, minutes, seconds, cs; hours = ticks / (HZ * 3600); ticks -= (HZ * hours * 3600); minutes = ticks / (HZ * 60); ticks -= (HZ * minutes * 60); seconds = ticks / HZ; ticks -= (HZ * seconds); cs = ticks; if (!lap) { rb->snprintf(buf, buflen, "%2d:%02d:%02d.%02d", hours, minutes, seconds, cs); } else { if (lap > 1) { int last_ticks, last_hours, last_minutes, last_seconds, last_cs; last_ticks = lap_times[(lap-1)%MAX_LAPS] - lap_times[(lap-2)%MAX_LAPS]; last_hours = last_ticks / (HZ * 3600); last_ticks -= (HZ * last_hours * 3600); last_minutes = last_ticks / (HZ * 60); last_ticks -= (HZ * last_minutes * 60); last_seconds = last_ticks / HZ; last_ticks -= (HZ * last_seconds); last_cs = last_ticks; rb->snprintf(buf, buflen, "%2d %2d:%02d:%02d.%02d [%2d:%02d:%02d.%02d]", lap, hours, minutes, seconds, cs, last_hours, last_minutes, last_seconds, last_cs); } else { rb->snprintf(buf, buflen, "%2d %2d:%02d:%02d.%02d", lap, hours, minutes, seconds, cs); } } } /* * Load saved stopwatch state, if exists. */ void load_stopwatch(void) { int fd; fd = rb->open(STOPWATCH_FILE, O_RDONLY); if (fd < 0) { return; } /* variable stopwatch isn't saved/loaded, because it is only used * temporarily in main loop */ rb->read(fd, &start_at, sizeof(start_at)); rb->read(fd, &prev_total, sizeof(prev_total)); rb->read(fd, &counting, sizeof(counting)); rb->read(fd, &curr_lap, sizeof(curr_lap)); rb->read(fd, &lap_scroll, sizeof(lap_scroll)); rb->read(fd, &lap_start, sizeof(lap_start)); rb->read(fd, lap_times, sizeof(lap_times)); if (counting && start_at > *rb->current_tick) { /* Stopwatch started in the future? Unlikely; probably started on a * previous session and powered off in-between. We'll keep * everything intact (user can clear manually) but stop the * stopwatch to avoid negative timing. */ start_at = 0; counting = false; } rb->close(fd); } /* * Save stopwatch state. */ void save_stopwatch(void) { int fd; fd = rb->open(STOPWATCH_FILE, O_CREAT|O_WRONLY|O_TRUNC); if (fd < 0) { return; } /* variable stopwatch isn't saved/loaded, because it is only used * temporarily in main loop */ rb->write(fd, &start_at, sizeof(start_at)); rb->write(fd, &prev_total, sizeof(prev_total)); rb->write(fd, &counting, sizeof(counting)); rb->write(fd, &curr_lap, sizeof(curr_lap)); rb->write(fd, &lap_scroll, sizeof(lap_scroll)); rb->write(fd, &lap_start, sizeof(lap_start)); rb->write(fd, lap_times, sizeof(lap_times)); rb->close(fd); } enum plugin_status plugin_start(const void* parameter) { char buf[32]; int button; int lap; int done = false; bool update_lap = true; int lines; (void)parameter; #ifdef HAVE_LCD_BITMAP int h; rb->lcd_setfont(FONT_UI); rb->lcd_getstringsize("M", NULL, &h); lines = (LCD_HEIGHT / h) - (LAP_Y); #else lines = 1; #endif load_stopwatch(); rb->lcd_clear_display(); while (!done) { if (counting) { stopwatch = prev_total + *rb->current_tick - start_at; } else { stopwatch = prev_total; } ticks_to_string(stopwatch,0,32,buf); rb->lcd_puts(0, TIMER_Y, buf); if(update_lap) { lap_start = curr_lap - lap_scroll; for (lap = lap_start; lap > lap_start - lines; lap--) { if (lap > 0) { ticks_to_string(lap_times[(lap-1)%MAX_LAPS],lap,32,buf); rb->lcd_puts_scroll(0, LAP_Y + lap_start - lap, buf); } else { rb->lcd_puts(0, LAP_Y + lap_start - lap, " "); } } update_lap = false; } rb->lcd_update(); if (! counting) { button = rb->button_get(true); } else { button = rb->button_get_w_tmo(10); /* Make sure that the jukebox isn't powered off automatically */ rb->reset_poweroff_timer(); } switch (button) { /* exit */ #ifdef STOPWATCH_RC_QUIT case STOPWATCH_RC_QUIT: #endif case STOPWATCH_QUIT: save_stopwatch(); done = true; break; /* Stop/Start toggle */ case STOPWATCH_START_STOP: counting = ! counting; if (counting) { start_at = *rb->current_tick; stopwatch = prev_total + *rb->current_tick - start_at; } else { prev_total += *rb->current_tick - start_at; stopwatch = prev_total; } break; /* Reset timer */ case STOPWATCH_RESET_TIMER: if (!counting) { prev_total = 0; curr_lap = 0; update_lap = true; } break; /* Lap timer */ case STOPWATCH_LAP_TIMER: lap_times[curr_lap%MAX_LAPS] = stopwatch; curr_lap++; update_lap = true; break; /* Scroll Lap timer up */ case STOPWATCH_SCROLL_UP: if (lap_scroll > 0) { lap_scroll --; update_lap = true; } break; /* Scroll Lap timer down */ case STOPWATCH_SCROLL_DOWN: if ((lap_scroll < curr_lap - lines) && (lap_scroll < (MAX_LAPS - lines)) ) { lap_scroll ++; update_lap = true; } break; default: if (rb->default_event_handler(button) == SYS_USB_CONNECTED) return PLUGIN_USB_CONNECTED; break; } } return PLUGIN_OK; } /a> 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Originally by Joshua Oreman, improved by Prashant Varanasi
 * Ported to Rockbox by Ben Basha (Paprica)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "plugin.h"
#include "lib/xlcd.h"
#include "lib/configfile.h"
#include "lib/helper.h"
#include "lib/playback_control.h"

PLUGIN_HEADER

/*
Still To do:
 - Make original speed and further increases in speed depend more on screen size
 - attempt to make the tunnels get narrower as the game goes on
 - make the chopper look better, maybe a picture, and scale according
   to screen size
 - use textures for the color screens for background and terrain,
   eg stars on background
 - allow choice of different levels [later: different screen themes]
 - better high score handling, improved screen etc.
*/

#if (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD)

#define QUIT BUTTON_OFF
#define ACTION BUTTON_UP
#define ACTION2 BUTTON_SELECT
#define ACTIONTEXT "SELECT"

#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
      (CONFIG_KEYPAD == IPOD_3G_PAD) || \
      (CONFIG_KEYPAD == IPOD_1G2G_PAD)

#define QUIT BUTTON_MENU
#define ACTION BUTTON_SELECT
#define ACTIONTEXT "SELECT"

#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD /* grayscale at the moment */

#define QUIT BUTTON_POWER
#define ACTION BUTTON_UP
#define ACTION2 BUTTON_SELECT
#define ACTIONTEXT "SELECT"

#elif CONFIG_KEYPAD == IRIVER_H10_PAD
#define QUIT BUTTON_POWER
#define ACTION BUTTON_RIGHT
#define ACTIONTEXT "RIGHT"

#elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \
      (CONFIG_KEYPAD == SANSA_C200_PAD) || \
      (CONFIG_KEYPAD == SANSA_CLIP_PAD) || \
      (CONFIG_KEYPAD == SANSA_M200_PAD)
#define QUIT BUTTON_POWER
#define ACTION BUTTON_SELECT
#define ACTIONTEXT "SELECT"

#elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
#define QUIT        BUTTON_HOME
#define ACTION      BUTTON_SELECT
#define ACTIONTEXT  "SELECT"

#elif CONFIG_KEYPAD == GIGABEAT_PAD
#define QUIT BUTTON_MENU
#define ACTION BUTTON_SELECT
#define ACTIONTEXT "SELECT"

#elif CONFIG_KEYPAD == RECORDER_PAD
#define QUIT BUTTON_OFF
#define ACTION BUTTON_PLAY
#define ACTIONTEXT "PLAY"

#elif CONFIG_KEYPAD == ONDIO_PAD
#define QUIT BUTTON_OFF
#define ACTION BUTTON_UP
#define ACTION2 BUTTON_MENU
#define ACTIONTEXT "UP"

#elif CONFIG_KEYPAD == GIGABEAT_S_PAD
#define QUIT BUTTON_BACK
#define ACTION BUTTON_SELECT
#define ACTION2 BUTTON_MENU
#define ACTIONTEXT "SELECT"

#elif CONFIG_KEYPAD == MROBE100_PAD
#define QUIT BUTTON_POWER
#define ACTION BUTTON_SELECT
#define ACTIONTEXT "SELECT"

#elif CONFIG_KEYPAD == IAUDIO_M3_PAD
#define QUIT BUTTON_RC_REC
#define ACTION BUTTON_RC_PLAY
#define ACTION2 BUTTON_RC_MODE
#define ACTIONTEXT "PLAY"

#elif CONFIG_KEYPAD == COWON_D2_PAD
#define QUIT BUTTON_POWER

#elif CONFIG_KEYPAD == IAUDIO67_PAD
#define QUIT BUTTON_POWER
#define ACTION BUTTON_PLAY
#define ACTION2 BUTTON_STOP
#define ACTIONTEXT "PLAY"

#elif CONFIG_KEYPAD == CREATIVEZVM_PAD
#define QUIT BUTTON_BACK
#define ACTION BUTTON_UP
#define ACTION2 BUTTON_MENU
#define ACTIONTEXT "UP"

#elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
#define QUIT BUTTON_POWER
#define ACTION BUTTON_MENU
#define ACTION2 BUTTON_SELECT
#define ACTIONTEXT "MENU"

#elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
#define QUIT BUTTON_POWER
#define ACTION BUTTON_MENU
#define ACTION2 BUTTON_PLAY
#define ACTIONTEXT "MENU"

#elif CONFIG_KEYPAD == ONDAVX747_PAD || \
CONFIG_KEYPAD == ONDAVX777_PAD || \
CONFIG_KEYPAD == MROBE500_PAD
#define QUIT BUTTON_POWER

#elif CONFIG_KEYPAD == SAMSUNG_YH_PAD
#define QUIT        BUTTON_LEFT
#define ACTION      BUTTON_RIGHT
#define ACTIONTEXT "RIGHT"

#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
#define QUIT BUTTON_REC
#define ACTION BUTTON_PLAY
#define ACTION2 BUTTON_UP
#define ACTIONTEXT "PLAY"

#else
#error No keymap defined!
#endif

#ifdef HAVE_TOUCHSCREEN
#ifndef QUIT
#define QUIT        BUTTON_TOPLEFT
#endif
#ifndef ACTION
#define ACTION      BUTTON_BOTTOMLEFT
#endif
#ifndef ACTION2
#define ACTION2     BUTTON_BOTTOMRIGHT
#endif
#ifndef ACTIONTEXT
#define ACTIONTEXT "BOTTOMRIGHT"
#endif
#endif

#define NUMBER_OF_BLOCKS 8
#define NUMBER_OF_PARTICLES 3
#define MAX_TERRAIN_NODES 15

#define LEVEL_MODE_NORMAL 0
#define LEVEL_MODE_STEEP 1

#if LCD_HEIGHT <= 64
#define CYCLES 100
static inline int SCALE(int x)
{
    return x == 1 ? x : x >> 1;
}
#define SIZE 2
#else
#define CYCLES 60
#define SCALE(x) (x)
#define SIZE 1
#endif

/* in 10 milisecond (ticks) */
#define CYCLETIME ((CYCLES*HZ)/1000)

/*Chopper's local variables to track the terrain position etc*/
static int chopCounter;
static int iRotorOffset;
static int iScreenX;
static int iScreenY;
static int iPlayerPosX;
static int iPlayerPosY;
static int iCameraPosX;
static int iPlayerSpeedX;
static int iPlayerSpeedY;
static int iLastBlockPlacedPosX;
static int iGravityTimerCountdown;
static int iPlayerAlive;
static int iLevelMode, iCurrLevelMode;
static int blockh,blockw;
static int highscore;
static int score;

#define CFG_FILE "chopper.cfg"
#define MAX_POINTS 50000
static struct configdata config[] =
{
   {TYPE_INT, 0, MAX_POINTS, { .int_p = &highscore }, "highscore", NULL}
};

struct CBlock
{
    int iWorldX;
    int iWorldY;

    int iSizeX;
    int iSizeY;

    int bIsActive;
};

struct CParticle
{
    int iWorldX;
    int iWorldY;

    int iSpeedX;
    int iSpeedY;

    int bIsActive;
};

struct CTerrainNode
{
    int x;
    int y;
};

struct CTerrain
{
    struct CTerrainNode mNodes[MAX_TERRAIN_NODES];
    int iNodesCount;
    int iLastNodePlacedPosX;
};

struct CBlock mBlocks[NUMBER_OF_BLOCKS];
struct CParticle mParticles[NUMBER_OF_PARTICLES];

struct CTerrain mGround;
struct CTerrain mRoof;

/*Function declarations*/
static void chopDrawParticle(struct CParticle *mParticle);
static void chopDrawBlock(struct CBlock *mBlock);
static void chopRenderTerrain(struct CTerrain *ter, bool isground);
void chopper_load(bool newgame);
void cleanup_chopper(void);

static void chopDrawPlayer(int x,int y) /* These are SCREEN coords, not world!*/
{

#if LCD_DEPTH > 2
    rb->lcd_set_foreground(LCD_RGBPACK(50,50,200));
#elif LCD_DEPTH == 2
    rb->lcd_set_foreground(LCD_DARKGRAY);
#endif
    rb->lcd_fillrect(SCALE(x+6), SCALE(y+2), SCALE(12), SCALE(9));
    rb->lcd_fillrect(SCALE(x-3), SCALE(y+6), SCALE(20), SCALE(3));

#if LCD_DEPTH > 2
    rb->lcd_set_foreground(LCD_RGBPACK(50,50,50));
#elif LCD_DEPTH == 2
    rb->lcd_set_foreground(LCD_DARKGRAY);
#endif
    rb->lcd_fillrect(SCALE(x+10), SCALE(y), SCALE(2), SCALE(3));
    rb->lcd_fillrect(SCALE(x+10), SCALE(y), SCALE(1), SCALE(3));

#if LCD_DEPTH > 2
    rb->lcd_set_foreground(LCD_RGBPACK(40,40,100));
#elif LCD_DEPTH == 2
    rb->lcd_set_foreground(LCD_BLACK);
#endif
    rb->lcd_drawline(SCALE(x), SCALE(y+iRotorOffset), SCALE(x+20),
                     SCALE(y-iRotorOffset));

#if LCD_DEPTH > 2
    rb->lcd_set_foreground(LCD_RGBPACK(20,20,50));
#elif LCD_DEPTH == 2
    rb->lcd_set_foreground(LCD_BLACK);
#endif
    rb->lcd_fillrect(SCALE(x - 2), SCALE(y + 5), SCALE(2), SCALE(5));

}

static void chopClearTerrain(struct CTerrain *ter)
{
    ter->iNodesCount = 0;
}


int iR(int low,int high)
{
    return low+rb->rand()%(high-low+1);
}

static void chopCopyTerrain(struct CTerrain *src, struct CTerrain *dest,
                            int xOffset,int yOffset)
{
    int i=0;

    while(i < src->iNodesCount)
    {
        dest->mNodes[i].x = src->mNodes[i].x + xOffset;
        dest->mNodes[i].y = src->mNodes[i].y + yOffset;

        i++;
    }

    dest->iNodesCount = src->iNodesCount;
    dest->iLastNodePlacedPosX = src->iLastNodePlacedPosX;

}

static void chopAddTerrainNode(struct CTerrain *ter, int x, int y)
{
    int i=0;

    if(ter->iNodesCount + 1 >= MAX_TERRAIN_NODES)
    {
        /* DEBUGF("ERROR: Not enough nodes!\n"); */
        return;
    }

    ter->iNodesCount++;

    i = ter->iNodesCount - 1;

    ter->mNodes[i].x = x;
    ter->mNodes[i].y= y;

    ter->iLastNodePlacedPosX = x;

}

static void chopTerrainNodeDeleteAndShift(struct CTerrain *ter,int nodeIndex)
{
    int i=nodeIndex;

    while( i < ter->iNodesCount )
    {
        ter->mNodes[i - 1] = ter->mNodes[i];
        i++;
    }

    ter->iNodesCount--;


}

int chopUpdateTerrainRecycling(struct CTerrain *ter)
{
    int i=1;
    int ret = 0;
    int iNewNodePos,g,v;
    while(i < ter->iNodesCount)
    {

        if( iCameraPosX > ter->mNodes[i].x)
        {

            chopTerrainNodeDeleteAndShift(ter,i);

            iNewNodePos = ter->iLastNodePlacedPosX + 50;
            g = iScreenY - 10;

            v = 3*iPlayerSpeedX;
            if(v>50)
                v=50;
            if(iCurrLevelMode == LEVEL_MODE_STEEP)
                v*=5;

            chopAddTerrainNode(ter,iNewNodePos,g - iR(-v,v));
            ret=1;

        }

        i++;

    }

    return 1;
}

int chopTerrainHeightAtPoint(struct CTerrain *ter, int pX)
{

    int iNodeIndexOne=0,iNodeIndexTwo=0, h, terY1, terY2, terX1, terX2, a, b;
    float c,d;

    int i=0;
    for(i=1;i<MAX_TERRAIN_NODES;i++)
    {
        if(ter->mNodes[i].x > pX)
        {
            iNodeIndexOne = i - 1;
            break;
        }

    }

    iNodeIndexTwo = iNodeIndexOne + 1;
    terY1 = ter->mNodes[iNodeIndexOne].y;
    terY2 = ter->mNodes[iNodeIndexTwo].y;

    terX1 = 0;
    terX2 = ter->mNodes[iNodeIndexTwo].x - ter->mNodes[iNodeIndexOne].x;

    pX-= ter->mNodes[iNodeIndexOne].x;

    a = terY2 - terY1;
    b = terX2;
    c = pX;
    d = (c/b) * a;

    h = d + terY1;

    return h;

}

int chopPointInTerrain(struct CTerrain *ter, int pX, int pY, int iTestType)
{
    int h = chopTerrainHeightAtPoint(ter, pX);

    if(iTestType == 0)
        return (pY > h);
    else
        return (pY < h);
}

static void chopAddBlock(int x,int y,int sx,int sy, int indexOverride)
{
    int i=0;

    if(indexOverride < 0)
    {
        while(mBlocks[i].bIsActive && i < NUMBER_OF_BLOCKS)
            i++;
        if(i==NUMBER_OF_BLOCKS)
        {
            DEBUGF("No blocks!\n");
            return;
        }
    }
    else
        i = indexOverride;

    mBlocks[i].bIsActive = 1;
    mBlocks[i].iWorldX = x;
    mBlocks[i].iWorldY = y;
    mBlocks[i].iSizeX = sx;
    mBlocks[i].iSizeY = sy;

    iLastBlockPlacedPosX = x;
}

static void chopAddParticle(int x,int y,int sx,int sy)
{
    int i=0;

    while(mParticles[i].bIsActive && i < NUMBER_OF_PARTICLES)
        i++;

    if(i==NUMBER_OF_PARTICLES)
        return;

    mParticles[i].bIsActive = 1;
    mParticles[i].iWorldX = x;
    mParticles[i].iWorldY = y;
    mParticles[i].iSpeedX = sx;
    mParticles[i].iSpeedY = sy;
}

static void chopGenerateBlockIfNeeded(void)
{
    int i=0;
    int DistSpeedX = iPlayerSpeedX * 5;
    if(DistSpeedX<200) DistSpeedX = 200;

    while(i < NUMBER_OF_BLOCKS)
    {
        if(!mBlocks[i].bIsActive)
        {
            int iX,iY,sX,sY;

            iX = iLastBlockPlacedPosX + (350-DistSpeedX);
            sX = blockw;

            iY = iR(0,iScreenY);
            sY = blockh + iR(1,blockh/3);

            chopAddBlock(iX,iY,sX,sY,i);
        }

        i++;
    }

}

static int chopBlockCollideWithPlayer(struct CBlock *mBlock)
{
    int px = iPlayerPosX;
    int py = iPlayerPosY;

    int x = mBlock->iWorldX-17;
    int y = mBlock->iWorldY-11;

    int x2 = x + mBlock->iSizeX+17;
    int y2 = y + mBlock->iSizeY+11;

    if(px>x && px<x2)
    {
        if(py>y && py<y2)
        {
            return 1;
        }
    }

    return 0;
}

static int chopBlockOffscreen(struct CBlock *mBlock)
{
    if(mBlock->iWorldX + mBlock->iSizeX < iCameraPosX)
        return 1;
    else
        return 0;
}

static int chopParticleOffscreen(struct CParticle *mParticle)
{
    if (mParticle->iWorldX < iCameraPosX || mParticle->iWorldY < 0 ||
        mParticle->iWorldY > iScreenY || mParticle->iWorldX > iCameraPosX +
        iScreenX)
    {
        return 1;
    }
    else
        return 0;
}

static void chopKillPlayer(void)
{
    int i;

    for (i = 0; i < NUMBER_OF_PARTICLES; i++) {
        mParticles[i].bIsActive = 0;
        chopAddParticle(iPlayerPosX + iR(0,20), iPlayerPosY + iR(0,20),
                        iR(-2,2), iR(-2,2));
    }

    iPlayerAlive--;

    if (iPlayerAlive == 0) {
        rb->splash(HZ, "Game Over");

        if (score > highscore) {
            char scoretext[30];
            highscore = score;
            rb->snprintf(scoretext, sizeof(scoretext), "New High Score: %d",
                         highscore);
            rb->splash(HZ*2, scoretext);
        }
    } else
        chopper_load(false);
}

static void chopDrawTheWorld(void)
{
    int i=0;

    while(i < NUMBER_OF_BLOCKS)
    {
        if(mBlocks[i].bIsActive)
        {
            if(chopBlockOffscreen(&mBlocks[i]) == 1)
                mBlocks[i].bIsActive = 0;
            else
                chopDrawBlock(&mBlocks[i]);
        }

        i++;
    }

    i=0;

    while(i < NUMBER_OF_PARTICLES)
    {
        if(mParticles[i].bIsActive)
        {
            if(chopParticleOffscreen(&mParticles[i]) == 1)
                mParticles[i].bIsActive = 0;
            else
                chopDrawParticle(&mParticles[i]);
        }

        i++;
    }

    chopRenderTerrain(&mGround, true);
    chopRenderTerrain(&mRoof, false);

}

static void chopDrawParticle(struct CParticle *mParticle)
{

    int iPosX = (mParticle->iWorldX - iCameraPosX);
    int iPosY = (mParticle->iWorldY);
#if LCD_DEPTH > 2
    rb->lcd_set_foreground(LCD_RGBPACK(192,192,192));
#elif LCD_DEPTH == 2
    rb->lcd_set_foreground(LCD_LIGHTGRAY);
#endif
    rb->lcd_fillrect(SCALE(iPosX), SCALE(iPosY), SCALE(3), SCALE(3));

}

static void chopDrawScene(void)
{
    char s[30];
    int w;
#if LCD_DEPTH > 2
    rb->lcd_set_background(LCD_BLACK);
#elif LCD_DEPTH == 2
    rb->lcd_set_background(LCD_WHITE);
#endif
    rb->lcd_clear_display();
    chopDrawTheWorld();
    chopDrawPlayer(iPlayerPosX - iCameraPosX, iPlayerPosY);

    score = -20 + iPlayerPosX/3;

#if LCD_DEPTH == 1
    rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
#else
    rb->lcd_set_drawmode(DRMODE_FG);
#endif

#if LCD_DEPTH > 2
    rb->lcd_set_foreground(LCD_BLACK);
#elif LCD_DEPTH == 2
    rb->lcd_set_foreground(LCD_WHITE);
#endif
    
#if LCD_WIDTH <= 128
    rb->snprintf(s, sizeof(s), "Dist: %d", score);
#else
    rb->snprintf(s, sizeof(s), "Distance: %d", score);
#endif
    rb->lcd_getstringsize(s, &w, NULL);
    rb->lcd_putsxy(2, 2, s);
    if (score < highscore)
    {
        int w2;
#if LCD_WIDTH <= 128
        rb->snprintf(s, sizeof(s), "Hi: %d", highscore);
#else
        rb->snprintf(s, sizeof(s), "Best: %d", highscore);
#endif
        rb->lcd_getstringsize(s, &w2, NULL);
        if (LCD_WIDTH - 2 - w2 > w + 2)
            rb->lcd_putsxy(LCD_WIDTH - 2 - w2, 2, s);
    }
    rb->lcd_set_drawmode(DRMODE_SOLID);

    rb->lcd_update();
}

static bool _ingame;
static int chopMenuCb(int action, const struct menu_item_ex *this_item)
{
    if(action == ACTION_REQUEST_MENUITEM
       && !_ingame && ((intptr_t)this_item)==0)
        return ACTION_EXIT_MENUITEM;
    return action;
}
static int chopMenu(int menunum)
{
    int result = 0;
    int res = 0;
    bool menu_quit = false;

    static const struct opt_items levels[2] = {
        { "Normal", -1 },
        { "Steep", -1 },
    };
    
    MENUITEM_STRINGLIST(menu,"Chopper Menu",chopMenuCb,
                        "Resume Game","Start New Game",
                        "Level","Playback Control","Quit");
    _ingame = (menunum!=0);

#ifdef HAVE_LCD_COLOR
    rb->lcd_set_foreground(LCD_WHITE);
    rb->lcd_set_background(LCD_BLACK);
#elif LCD_DEPTH == 2
    rb->lcd_set_foreground(LCD_BLACK);
    rb->lcd_set_background(LCD_WHITE);
#endif

    rb->lcd_clear_display();
    rb->button_clear_queue();

    while (!menu_quit) {
        switch(rb->do_menu(&menu, &result, NULL, false))
        {
            case 0:     /* Resume Game */
                menu_quit=true;
                res = -1;
                break;
            case 1:     /* Start New Game */
                menu_quit=true;
                chopper_load(true);
                res = -1;
                break;
            case 2:
                rb->set_option("Level", &iLevelMode, INT, levels, 2, NULL);
                break;
            case 3:
                playback_control(NULL);
                break;
            case 4:
                menu_quit=true;
                res = PLUGIN_OK;
                break;
            case MENU_ATTACHED_USB:
                menu_quit=true;
                res = PLUGIN_USB_CONNECTED;
                break;
        }
    }
    rb->lcd_clear_display();
    return res;
}

static int chopGameLoop(void)
{
    int move_button, ret;
    bool exit=false;
    int end, i=0, bdelay=0, last_button=BUTTON_NONE;

    if (chopUpdateTerrainRecycling(&mGround) == 1)
        /* mirror the sky if we've changed the ground */
        chopCopyTerrain(&mGround, &mRoof, 0, - ( (iScreenY * 3) / 4));

    ret = chopMenu(0);
    if (ret != -1)
        return PLUGIN_OK;

    chopDrawScene();

    while (!exit) {

        end = *rb->current_tick + CYCLETIME;

        if(chopUpdateTerrainRecycling(&mGround) == 1)
            /* mirror the sky if we've changed the ground */
            chopCopyTerrain(&mGround, &mRoof, 0, - ( (iScreenY * 3) / 4));

        iRotorOffset = iR(-1,1);

        /* We need to have this here so particles move when we're dead */

        for (i=0; i < NUMBER_OF_PARTICLES; i++)
            if(mParticles[i].bIsActive == 1)
            {
                mParticles[i].iWorldX += mParticles[i].iSpeedX;
                mParticles[i].iWorldY += mParticles[i].iSpeedY;
            }

        /* Redraw the main window: */
        chopDrawScene();


        iGravityTimerCountdown--;

        if(iGravityTimerCountdown <= 0)
        {
            iGravityTimerCountdown = 3;
            chopAddParticle(iPlayerPosX, iPlayerPosY+5, 0, 0);
        }

        if(iCurrLevelMode == LEVEL_MODE_NORMAL)
            chopGenerateBlockIfNeeded();


        move_button=rb->button_status();
        if (rb->button_get(false) == QUIT) {
            ret = chopMenu(1);
            if (ret != -1)
                return PLUGIN_OK;
            bdelay = 0;
            last_button = BUTTON_NONE;
            move_button = BUTTON_NONE;
        }

        switch (move_button) {
            case ACTION:
#ifdef ACTION2
            case ACTION2:
#endif
                if (last_button != ACTION
#ifdef ACTION2
                    && last_button != ACTION2
#endif
                   )
                    bdelay = -2;
                if (bdelay == 0)
                    iPlayerSpeedY = -3;
                break;

            default:
                if (last_button == ACTION
#ifdef ACTION2
                    || last_button == ACTION2
#endif
                   )
                    bdelay = 3;
                if (bdelay == 0)
                    iPlayerSpeedY = 4;

                if (rb->default_event_handler(move_button) == SYS_USB_CONNECTED)
                    return PLUGIN_USB_CONNECTED;
                break;
        }
        last_button = move_button;

        if (bdelay < 0) {
            iPlayerSpeedY = bdelay;
            bdelay++;
        } else if (bdelay > 0) {
            iPlayerSpeedY = bdelay;
            bdelay--;
        }

        iCameraPosX = iPlayerPosX - 25;
        iPlayerPosX += iPlayerSpeedX;
        iPlayerPosY += iPlayerSpeedY;

        chopCounter++;
        /* increase speed as we go along */
        if (chopCounter == 100){
            iPlayerSpeedX++;
            chopCounter=0;
        }

        if (iPlayerPosY > iScreenY-10 || iPlayerPosY < -5 ||
           chopPointInTerrain(&mGround, iPlayerPosX, iPlayerPosY + 10, 0) ||
           chopPointInTerrain(&mRoof, iPlayerPosX ,iPlayerPosY, 1))
        {
           chopKillPlayer();
           chopDrawScene();
           ret = chopMenu(0);
           if (ret != -1)
               return ret;
        }

        for (i=0; i < NUMBER_OF_BLOCKS; i++)
            if(mBlocks[i].bIsActive == 1)
                if(chopBlockCollideWithPlayer(&mBlocks[i])) {
                    chopKillPlayer();
                    chopDrawScene();
                    ret = chopMenu(0);
                    if (ret != -1)
                        return ret;
                }

        if (TIME_BEFORE(*rb->current_tick, end))
            rb->sleep(end - *rb->current_tick); /* wait until time is over */
        else
            rb->yield();

    }
    return PLUGIN_OK;
}

static void chopDrawBlock(struct CBlock *mBlock)
{
    int iPosX = (mBlock->iWorldX - iCameraPosX);
    int iPosY = (mBlock->iWorldY);
#if LCD_DEPTH > 2
    rb->lcd_set_foreground(LCD_RGBPACK(100,255,100));
#elif LCD_DEPTH == 2
    rb->lcd_set_foreground(LCD_BLACK);
#endif
    rb->lcd_fillrect(SCALE(iPosX), SCALE(iPosY), SCALE(mBlock->iSizeX),
                     SCALE(mBlock->iSizeY));
}


static void chopRenderTerrain(struct CTerrain *ter, bool isground)
{

    int i = 1;

    int oldx = 0;

    while(i < ter->iNodesCount && oldx < iScreenX)
    {

        int x = ter->mNodes[i-1].x - iCameraPosX;
        int y = ter->mNodes[i-1].y;

        int x2 = ter->mNodes[i].x - iCameraPosX;
        int y2 = ter->mNodes[i].y;

        int ax, ay;

        if ((y < y2) != isground)
        {
            ax = x2;
            ay = y;
        }
        else
        {
            ax = x;
            ay = y2;
        }
#if LCD_DEPTH > 2
        rb->lcd_set_foreground(LCD_RGBPACK(100,255,100));
#elif LCD_DEPTH == 2
        rb->lcd_set_foreground(LCD_DARKGRAY);
#endif

        rb->lcd_drawline(SCALE(x), SCALE(y), SCALE(x2), SCALE(y2));

        xlcd_filltriangle(SCALE(x), SCALE(y), SCALE(x2), SCALE(y2),
                          SCALE(ax), SCALE(ay));

        if (isground)
        {
            y = ay;
            y2 = (LCD_HEIGHT*SIZE);
        }
        else
        {
            y = 0;
            y2 = ay;
        }
        if (y2-y > 0)
            rb->lcd_fillrect(SCALE(x), SCALE(y), SCALE(x2-x)+1, SCALE(y2-y)+1);

        oldx = x;
        i++;
    }
}

void chopper_load(bool newgame)
{

    int i;
    int g;

    if (newgame) {
        iScreenX = LCD_WIDTH * SIZE;
        iScreenY = LCD_HEIGHT * SIZE;
        blockh = iScreenY / 5;
        blockw = iScreenX / 20;
        iPlayerAlive = 1;
        iCurrLevelMode = iLevelMode;
        score = 0;
    }
    iRotorOffset = 0;
    iPlayerPosX = 60;
    iPlayerPosY = (iScreenY * 4) / 10;
    iLastBlockPlacedPosX = 0;
    iGravityTimerCountdown = 2;
    chopCounter = 0;
    iPlayerSpeedX = 3;
    iPlayerSpeedY = 0;
    iCameraPosX = 30;

    for (i=0; i < NUMBER_OF_PARTICLES; i++)
        mParticles[i].bIsActive = 0;

    for (i=0; i < NUMBER_OF_BLOCKS; i++)
        mBlocks[i].bIsActive = 0;

    g = iScreenY - 10;
    chopClearTerrain(&mGround);

    for (i=0; i < MAX_TERRAIN_NODES; i++)
        chopAddTerrainNode(&mGround,i * 30,g - iR(0,20));

    if (chopUpdateTerrainRecycling(&mGround) == 1)
        /* mirror the sky if we've changed the ground */
        chopCopyTerrain(&mGround, &mRoof, 0, - ( (iScreenY * 3) / 4));

    if (iCurrLevelMode == LEVEL_MODE_NORMAL)
        /* make it a bit more exciting, cause it's easy terrain... */
        iPlayerSpeedX *= 2;
}

/* this is the plugin entry point */
enum plugin_status plugin_start(const void* parameter)
{
    (void)parameter;
    int ret;

    rb->lcd_setfont(FONT_SYSFIXED);
#if LCD_DEPTH > 1
    rb->lcd_set_backdrop(NULL);
#endif
#ifdef HAVE_LCD_COLOR