https://bitbucket.org/daniel_fort/magic-lantern
Tip revision: 06b3bb79ef80342b74e8548d07472f8e68a85b0f authored by danne on 02 February 2017, 20:26:43 UTC
blacks out
blacks out
Tip revision: 06b3bb7
arkanoid.c
#include <module.h>
#include <dryos.h>
#include <bmp.h>
#include <config.h>
#include <menu.h>
#include <font.h>
#include <beep.h>
#include "arkanoid.h"
#include <cordic-16bit.h>
#define M_PI 3.1415926536897932384626f
// start elem
static element *head = NULL;
// basic elem loop
#define ELEM_LOOP(code) { element *temp; element *e; for(temp = e = head; temp; temp = e = temp->next) { code; } }
// elem calculations
#define COERCE_ABS(i, min, max) if(i < min) { i = (min) - (i - (min)); } if(i > max) { i = (max) - (i - (max)); }
#define EXCLUDE_RANGE(i, min, max) if(i > min && i < max) { if(i > ((max) - (min)) / 2) i = max; else i = min;}
// elem types
#define ELEM_NULL 0
#define ELEM_PAD 1
#define ELEM_BALL 2
#define ELEM_BRICK 3
#define ELEM_FALL_BRICK 4
#define ELEM_ML 5
#define ELEM_PRESENT 6
// is arkanoid active?
static bool arkanoid_running = 0;
// running states
#define ARK_IDLE 0 // reset
#define ARK_INRO 1
#define ARK_PRESENT 2
#define ARK_LOGO 3
#define ARK_NEW_GAME 4
#define ARK_PLAY 5
// should be read only
static int arkanoid_state = -1;
// set to whatever you want
static int arkanoid_next_state = ARK_IDLE;
// sound
static int sound_event;
#define SOUND_EVENT_COLLISION 1
#define SOUND_EVENT_BALL_LOST 2
#define SOUND_EVENT_ALL_BALLS_LOST 4
// last key pressed
static int last_key;
// big step for front and rear wheel
static int big_step;
// count bricks
static int brick_count;
// count bals
static int ball_count;
// game paused?
static int game_paused;
// configs
static CONFIG_INT("games.arkanoid.level", level, 1);
static CONFIG_INT("games.arkanoid.sound", sound, 1);
// extern
extern int menu_redraw_blocked;
extern int menu_shown;
// logo graphics
#define LOGO_ARR_LEN 153
static int logo_arr[LOGO_ARR_LEN+1][2] = {{268,48}, {147,57}, {229,46}, {269,72}, {229,70}, {159,81}, {195,83}, {269,83}, {229,81}, {279,45}, {147,46}, {148,81}, {290,50}, {158,46}, {288,61}, {167,52}, {170,63}, {211,74}, {206,83}, {197,47}, {288,75}, {251,83}, {240,82}, {203,65}, {297,83}, {167,74}, {193,57}, {209,46}, {186,78}, {279,67}, {269,61}, {147,69}, {229,58}, {133,216}, {328,215}, {193,219}, {459,195}, {548,204}, {459,255}, {559,263}, {258,200}, {526,201}, {387,205}, {439,200}, {441,212}, {124,239}, {314,252}, {195,249}, {258,226}, {526,238}, {526,227}, {548,249}, {387,249}, {408,222}, {119,250}, {178,264}, {308,263}, {195,264}, {258,253}, {258,239}, {526,263}, {525,250}, {387,262}, {424,244}, {139,202}, {341,189}, {192,205}, {467,189}, {548,190}, {458,243}, {457,230}, {548,234}, {257,187}, {526,188}, {387,190}, {440,188}, {147,188}, {332,200}, {191,190}, {478,187}, {561,189}, {154,201}, {349,199}, {397,200}, {218,190}, {204,188}, {490,187}, {573,192}, {161,214}, {371,250}, {349,228}, {416,232}, {230,197}, {500,193}, {505,204}, {584,200}, {509,239}, {507,228}, {596,222}, {596,233}, {297,188}, {164,228}, {169,241}, {358,212}, {402,211}, {227,248}, {219,238}, {222,222}, {269,231}, {270,219}, {152,230}, {317,240}, {233,210}, {506,217}, {592,210}, {492,264}, {508,250}, {502,259}, {592,244}, {582,254}, {287,199}, {172,252}, {375,262}, {367,238}, {362,226}, {237,262}, {293,262}, {257,265}, {279,241}, {285,250}, {141,230}, {115,262}, {324,228}, {208,229}, {478,264}, {572,260}, {280,210}, {129,228}, {338,229}, {194,232}, {457,218}, {457,206}, {548,219}, {466,263}, {548,262}, {257,212}, {526,214}, {387,219}, {387,234}, {441,223}, {440,236}, {441,248}, {432,254}, {441,263}};
static element* new_elem(int type) {
// allocate new elem
element *new = malloc(sizeof(element));
if(!new) return NULL;
memset(new, 0, sizeof(element));
new->type = type;
// no items => head is new
if(!head)
{
head = new;
}
// otherwise append at the end
else
{
// add next reference at the end of elems
element *end = head;
while(end->next) end = end->next;
end->next = new;
// add previous reference
new->prev = end;
}
return new;
}
static void delete_elem(element *e) {
e->next->prev = e->prev;
e->prev->next = e->next;
// if e was head => head is now the next on the right
if(e == head) head = e->next;
free(e);
}
static void reset_elems() {
ELEM_LOOP ( delete_elem(e) )
head = NULL;
}
static void arkanoid_draw_elem(element * e, int x, int y, int color)
{
switch(e->type) {
case ELEM_PAD:
bmp_draw_rect_chamfer(color, x, y, e->w, e->h, 4, 0);
bmp_draw_rect_chamfer(color, x + 1, y + 1, e->w - 2, e->h - 2, 4, 0);
break;
case ELEM_BALL:
bmp_draw_rect_chamfer(color, x, y, e->w, e->h, (e->w + e->h) / 8, 0);
break;
case ELEM_BRICK:
bmp_draw_rect_chamfer(color, x, y, e->w, e->h, 2, 0);
break;
case ELEM_FALL_BRICK:
bmp_draw_rect_chamfer(color, x, y, e->w, e->h, 2, 0);
bfnt_draw_char(e->c1, x, y, color, 0);
break;
case ELEM_ML:
bmp_printf(FONT(FONT_LARGE, color, 0), 720 / 2 - 13 * font_large.width / 2, 480 / 2 - font_large.width / 2, "Magic Lantern");
break;
case ELEM_PRESENT:
bmp_printf(FONT(FONT_LARGE, color, 0), 720 / 2 - 7 * font_large.width / 2, 480 / 2 - font_large.width / 2, "present");
break;
}
//bmp_printf(FONT_MED, (int)e->x, (int)e->y, "%d", e->c1);
}
static void arkanoid_redraw()
{
ELEM_LOOP
(
// erase elements that changed their position (to minimize flicker)
if (e->old_x != e->x || e->old_y != e->y)
{
arkanoid_draw_elem(e, e->old_x, e->old_y, 0);
}
// remove deleted elements from simulation
if (e->deleted)
{
// we cannot erase text because of bmp.c:186
if(e->type != ELEM_ML && e->type != ELEM_PRESENT) {
arkanoid_draw_elem(e, e->x, e->y, 0);
}
delete_elem(e);
}
)
ELEM_LOOP
(
// draw the rest
arkanoid_draw_elem(e, e->x, e->y, e->color);
// keep track of old position
e->old_x = e->x;
e->old_y = e->y;
)
/*
int i = 0;
ELEM_LOOP(i++);
bmp_printf(FONT_LARGE, 0, 0, "%d %dKB ", i, sizeof(element) * i / 1024);
*/
}
static int last_delta() {
switch(last_key) {
case MODULE_KEY_PRESS_LEFT:
return -1;
case MODULE_KEY_WHEEL_UP:
case MODULE_KEY_WHEEL_LEFT:
if(!big_step--) last_key = 0;
return -1;
case MODULE_KEY_PRESS_RIGHT:
return 1;
case MODULE_KEY_WHEEL_RIGHT:
case MODULE_KEY_WHEEL_DOWN:
if(!big_step--) last_key = 0;
return 1;
}
return 0;
}
static void fade(element *e, int fade_delta) {
if(!e) return;
e->fade_delta = fade_delta;
}
static void fade_set(element *e, int fade_delta, int start) {
e->fade_delta = fade_delta;
e->fade = start;
}
static void generate_level() {
int x, y;
int width = 0;
int i = - NUM_ML_ICONS - 2 + rand()%100;
for(y = 20; y < 380;y += 44)
{
for(x = 50; x < 720 - 50; )
{
if((level * level < rand() % 100))
{
x += rand()%20;
continue;
}
element *e = new_elem(ELEM_BRICK);
if(!e) continue;
brick_count++;
while(i++<100)
{
width = bfnt_char_get_width(i);
if (width > 0 && width < 100) break;
}
e->x = x;
e->y = y;
e->w = width;
e->h = 40;
e->c1 = i;
fade(e, 1 + (rand() % 5));
x += e->w + 5;
}
if((rand() % (level * 100)) < 95) y += 22;
}
}
static int FAST hit_test_test(element *a, element *b) {
if (
a->x + a->w >= b->x &&
a->x <= b->x + b->w &&
a->y + a->h >= b->y &&
a->y <= b->y + b->h
) return 1;
return 0;
}
static void set_direction(element *e, int angle) {
int s,c;
bool reverse = 0;
angle %= 360;
if(angle > 180) { //cordic funcion doesnt have full 2PI range
angle %= 180;
reverse = 1;
}
float p = M_PI * (- 0.5f) + (angle / 180.0f) * M_PI;
cordic((p*MUL), &s, &c, 11); //11 for 1 degree accuracy
if(!reverse) {
s *= -1;
c *= -1;
}
e->deltaX = s / MUL;
e->deltaY = c / MUL;
}
static element* new_ball() {
element *e = new_elem(ELEM_BALL);
if(!e) return NULL;
e->w = 10;
e->h = 10;
e->x = 720 / 2 - e->w / 2;
e->y = 480 / 2 - e->h / 2;
e->color = COLOR_WHITE;
set_direction(e, rand() % 360);
e->speed = 3 + rand()%300 / 100.0;
return e;
}
static void handle_fades(element *e) {
if(e->fade_delta == 0) return;
e->fade += e->fade_delta;
e->fade = COERCE(e->fade, 0, 100);
e->color = COLOR_GRAY(e->fade);
if(e->fade == 0 || e->fade == 100) e->fade_delta = 0;
if(e->fade == 0) e->deleted = 1;
}
// state transition, to be called only from arkanoid task
static void arkanoid_game_init() {
brick_count = ball_count = 0;
reset_elems();
element *p = new_elem(ELEM_PAD);
if(!p) return;
p->w = MIN(60 * level, 720);
p->h = 20;
p->x = 720 / 2 - p->w / 2;
p->y = 450;
p->speed = 15;
p->color = COLOR_WHITE;
int i = 0;
while(i++ < level)
{
element *e = new_ball();
if(!e) continue;
ball_count++;
int start = (720 / 2) - ( level * e->w + (level - 1) * e->w ) / 2;
e->x = start + ( i - 1 ) * ( e->w + e->w );
e->y = p->y - e->h;
e->speed = 0;
set_direction(e, -90);
if(ball_count == 5) break;
}
generate_level();
}
// state transition, to be called only from arkanoid task
static void arkanoid_game_start() {
ELEM_LOOP
(
if(e->type != ELEM_BALL) continue;
e->speed = 5 + (level * 5);
)
}
static void FAST hit_test(element *a) {
ELEM_LOOP
(
if(e->type != ELEM_PAD && e->type != ELEM_BRICK) continue;
if(!hit_test_test(a, e)) continue;
sound_event |= SOUND_EVENT_COLLISION;
if(e->type == ELEM_PAD) {
int angle = 180 - ABS((a->x + a->w / 2) - e->x) / e->w * 180;
angle = COERCE(angle, 10, 170);
set_direction(a, angle);
}
else {
int ball_center;
ball_center = a->x + a->w / 2;
int x_len = MIN(
ABS(ball_center - e->x),
ABS(ball_center - (e->x + e->w))
);
ball_center = a->y + a->h / 2;
int y_len = MIN(
ABS(ball_center - e->y),
ABS(ball_center - (e->y + e->h))
);
if(x_len < y_len)a->deltaX *= -1;
else a->deltaY *= -1;
e->type = ELEM_FALL_BRICK;
fade_set(e, -4, 70);
e->speed = 5 + rand()%5;
if(--brick_count == 0) {
level = MIN(level + 1, 10);
arkanoid_next_state = ARK_NEW_GAME;
}
}
)
}
// state transition, to be called only from arkanoid task
static void arkanoid_logo() {
// hide all leave balls and count balls
int bals = 0;
ELEM_LOOP
(
if(e->type != ELEM_BALL) fade(e, -10);
else bals++;
)
// add new balls
bals -= LOGO_ARR_LEN + 50;
while(bals++ < 0) {
element* e = new_ball();
if(!e) continue;
fade(e, 5);
}
// logo assoc
ELEM_LOOP
(
if(e->type != ELEM_BALL) continue;
e->c1 = -1;
)
element* closest = head;
for(int i = 0; i != LOGO_ARR_LEN; i++) {
int dist = INT_MAX;
ELEM_LOOP
(
if(e->type != ELEM_BALL || e->c1 != -1) continue;
int manhattan = ABS(e->x - logo_arr[i][0]) + ABS(e->y - logo_arr[i][1]);
if(manhattan < dist) {
dist = manhattan;
closest = e;
}
)
closest->c1 = i;
closest->speed = 0.2;
}
}
// state transition, to be called only from arkanoid task
static void arkanoid_intro() {
reset_elems();
element *e = new_elem(ELEM_ML);
fade(e, 2);
}
static void arkanoid_present() {
element *e = new_elem(ELEM_PRESENT);
fade(e, 4);
}
static void ml_ef(element* e) {
if(arkanoid_state != ARK_INRO) return;
element* b = new_ball();
if(b) fade(b, 2);
if(e->fade == 100) fade(e, -2);
if(e->fade == 0) arkanoid_next_state = ARK_PRESENT;
}
static void present_ef(element* e) {
if(arkanoid_state != ARK_PRESENT) return;
element* b = new_ball();
if(b) fade(b, 3);
if(e->fade == 100) fade(e, -3);
if(e->fade == 0) arkanoid_next_state = ARK_LOGO;
}
static void ball_coerce(element* e) {
if(e->x < 0 || e->x > 720 - e->w) e->deltaX *= -1;
if(e->y < 0 || e->y > 480 - e->h) e->deltaY *= -1;
COERCE_ABS(e->x, 0, 720 - e->w);
COERCE_ABS(e->y, 0, 480 - e->h);
}
static void FAST ball_ef(element* e) {
float plusX;
float plusY;
// logo assoc
if(arkanoid_state == ARK_LOGO && e->c1 != -1) {
plusX = logo_arr[e->c1][0] - e->x;
plusY = logo_arr[e->c1][1] - e->y;
e->x += plusX * 0.1;
e->y += plusY * 0.1;
if(!( rand() % 2)) set_direction(e, rand() % 360);
}
// movement is computed as deltaX/Y (sin cos (-1, +1) ) * number of steps (speed)
float step = (float)e->speed;
int x, y;
int last_x = INT_MAX;
int last_y = INT_MAX;
while(step-- > 0) {
float cur_step = step < 1 ? step : 1;
e->x += e->deltaX * cur_step;
e->y += e->deltaY * cur_step;
ball_coerce(e);
// collision only and ball lose in gameplay
if(arkanoid_state == ARK_PLAY) {
x = (int)e->x;
y = (int)e->y;
if(ABS(x - last_x) > e->w || ABS(y - last_y) > e->h) {
hit_test(e);
last_y = y;
last_x = x;
}
if(e->y > 460) {
e->deleted = 1;
if(--ball_count == 0) {
arkanoid_next_state = ARK_LOGO;
sound_event |= SOUND_EVENT_ALL_BALLS_LOST;
}
else
{
sound_event |= SOUND_EVENT_BALL_LOST;
}
break;
}
}
}
}
static void pad_ef(element* e) {
if(arkanoid_state != ARK_PLAY) return;
e->x = COERCE(e->x + last_delta() * e->speed, 0, 720 - e->w);
}
static void fall_brick_ef(element* e) {
e->y += e->speed;
}
static void arkanoid_task()
{
clrscr();
arkanoid_running = 1;
menu_redraw_blocked = 1;
last_key = 0;
TASK_LOOP
{
// pause
if (game_paused) {
goto frame_skip;
}
// if menu is not shown > quit
if (!gui_menu_shown()) goto quit;
// change the state
if (arkanoid_next_state != arkanoid_state)
{
switch (arkanoid_next_state)
{
case ARK_IDLE:
case ARK_INRO:
arkanoid_intro();
// rewrite the AKR_IDLE
arkanoid_next_state = ARK_INRO;
break;
case ARK_PRESENT:
arkanoid_present();
break;
case ARK_LOGO:
arkanoid_logo();
break;
case ARK_NEW_GAME:
arkanoid_game_init();
break;
case ARK_PLAY:
arkanoid_game_start();
break;
}
arkanoid_state = arkanoid_next_state;
clrscr();
}
ELEM_LOOP
(
handle_fades(e);
switch (e->type) {
case ELEM_ML: ml_ef(e); break;
case ELEM_PRESENT: present_ef(e); break;
case ELEM_BALL: ball_ef(e); break;
case ELEM_PAD: pad_ef(e); break;
case ELEM_FALL_BRICK: fall_brick_ef(e); break;
}
)
if (sound)
{
// play the most important event
if (sound_event & SOUND_EVENT_ALL_BALLS_LOST)
{
info_led_on();
beep_custom(1000, 200, 0);
info_led_off();
}
else if (sound_event & SOUND_EVENT_BALL_LOST)
{
info_led_on();
beep_custom(300, 200, 0);
info_led_off();
}
else if (sound_event & SOUND_EVENT_COLLISION)
{
// regular collision
beep_custom(20, 750 + rand()%500, 0);
}
sound_event = 0;
}
frame_skip:
arkanoid_redraw();
msleep(40);
}
quit:
clrscr();
arkanoid_running = 0;
menu_redraw_blocked = 0;
}
static MENU_SELECT_FUNC(arkanoid_start) {
task_create("arkanoid_task", 0x1c, 0x1000, arkanoid_task, (void*)0);
}
static MENU_SELECT_FUNC(level_select) {
level += delta;
level = COERCE(level, 1, 10);
// if we are in game > reset
if (arkanoid_state == ARK_PLAY || arkanoid_state == ARK_NEW_GAME) {
arkanoid_next_state = ARK_LOGO;
}
}
static struct menu_entry arkanoid_menu[] =
{
{
.name = "Arkanoid",
.select = arkanoid_start,
.help = "Second game for Magic Lantern. Try it in LiveView.",
.children = (struct menu_entry[])
{
{
.name = "Level",
.priv = &level,
.select = level_select,
.max = 10,
.min = 1,
},
{
.name = "Sound",
.priv = &sound,
.max = 1,
},
MENU_EOL,
}
}
};
static unsigned int arkanoid_init() {
menu_add("Games", arkanoid_menu, COUNT(arkanoid_menu));
return 0;
}
static unsigned int arkanoid_deinit()
{
return 0;
}
static unsigned int arkanoid_keypress(unsigned int key)
{
// if arakanoid is not running do nothing
if(!arkanoid_running) return 1;
// save last key to decide later what we should do
last_key = key;
switch(key) {
// Q and PicStyle reset the game
case MODULE_KEY_Q:
case MODULE_KEY_PICSTYLE:
arkanoid_next_state = ARK_IDLE;
break;
// set pauses the game
case MODULE_KEY_PRESS_SET:
game_paused = !game_paused;
break;
// arrows control the rest
case MODULE_KEY_WHEEL_LEFT:
case MODULE_KEY_WHEEL_RIGHT:
case MODULE_KEY_WHEEL_UP:
case MODULE_KEY_WHEEL_DOWN:
big_step = (key == MODULE_KEY_WHEEL_LEFT || key == MODULE_KEY_WHEEL_RIGHT) ? 3 : 1;
// fall through
case MODULE_KEY_PRESS_LEFT:
case MODULE_KEY_PRESS_RIGHT:
// if the game is paused, any of the playing keys will resume it
game_paused = 0;
switch(arkanoid_state) {
case ARK_INRO:
arkanoid_next_state = ARK_LOGO;
break;
case ARK_LOGO:
arkanoid_next_state = ARK_NEW_GAME;
break;
case ARK_NEW_GAME:
arkanoid_next_state = ARK_PLAY;
break;
case ARK_PLAY:
// nothing to do, just keep playing
break;
}
break;
// block these keys to avoid side effects in menu
// don't block delete and half-shutter, so the game will pause when you close the menu
// todo: provide proper backend support for things to run on top of the menu (the IME modules likely have the same issue)
case MODULE_KEY_JOY_CENTER:
case MODULE_KEY_PRESS_UP:
case MODULE_KEY_PRESS_UP_RIGHT:
case MODULE_KEY_PRESS_UP_LEFT:
case MODULE_KEY_PRESS_DOWN_RIGHT:
case MODULE_KEY_PRESS_DOWN_LEFT:
case MODULE_KEY_PRESS_DOWN:
case MODULE_KEY_UNPRESS_UDLR:
case MODULE_KEY_UNPRESS_SET:
case MODULE_KEY_MENU:
case MODULE_KEY_INFO:
break;
// be nice and don't block all GUI events
default:
return 1;
}
// block events that were handled
return 0;
}
MODULE_INFO_START()
MODULE_INIT(arkanoid_init)
MODULE_DEINIT(arkanoid_deinit)
MODULE_INFO_END()
MODULE_CBRS_START()
MODULE_CBR(CBR_KEYPRESS, arkanoid_keypress, 0)
MODULE_CBRS_END()
MODULE_CONFIGS_START()
MODULE_CONFIG(level)
MODULE_CONFIG(sound)
MODULE_CONFIGS_END()