view examples/tank/tank_main.c @ 1395:a768d74e5f49

Fix the svg:use. For a svg:use, it is a group which include the content it reference. It means that we can not tween it to its origin object directly. Instead, we need to ungroup it and then use the result matrix to generate the tweened transformation matrix. Therefore, we need to concate its matrix to the referenced object. Ad center object when the bbox-x is not available.
author wycc
date Sat, 02 Apr 2011 05:36:36 +0800
parents 7451af5d63ec
children
line wrap: on
line source

#include <math.h>
#include <sys/time.h>
#include <string.h>
#include <mb.h>
#include <mb_tools.h>
#include "tank.h"
#include "svgs.h"

/*! \defgroup tank Example Tank
 * @{
 */
char map[12][16] = {
    { MUD, MUD, MUD, MUD, MUD, MUD, MUD, MUD,
      MUD, MUD, MUD, MUD, MUD, MUD, MUD, MUD},
    { MUD, ROC, ROC, ROC, MUD, BSH, BSH, ROC,
      BSH, ROC, MUD, BSH, BSH, ROC, ROC, MUD},
    { MUD, MUD, BRI, MUD, MUD, MUD, MUD, MUD,
      MUD, MUD, MUD, BRI, MUD, MUD, BSH, MUD},
    { BRI, MUD, MUD, MUD, MUD, MUD, BRI, MUD,
      BRI, MUD, MUD, MUD, MUD, MUD, MUD, MUD},
    { MUD, MUD, BRI, MUD, BRI, BSH, BRI, BRI,
      BRI, BRI, BSH, ROC, ROC, MUD, BRI, MUD},
    { MUD, BRI, BRI, MUD, BRI, MUD, BRI, MUD,
      ROC, MUD, MUD, MUD, MUD, MUD, MUD, MUD},
    { MUD, MUD, MUD, MUD, MUD, MUD, MUD, MUD,
      MUD, MUD, MUD, BRI, BRI, BRI, BRI, MUD},
    { MUD, BRI, MUD, BRI, BRI, MUD, BRI, BRI,
      BSH, BRI, MUD, MUD, MUD, MUD, MUD, MUD},
    { MUD, BRI, MUD, MUD, MUD, MUD, MUD, MUD,
      MUD, MUD, MUD, BRI, BRI, MUD, BRI, BRI},
    { MUD, BRI, MUD, BRI, BRI, MUD, BRI, BRI,
      BRI, BRI, MUD, BRI, MUD, MUD, MUD, MUD},
    { MUD, BSH, MUD, BRI, MUD, MUD, BRI, MUD,
      MUD, BRI, MUD, MUD, MUD, BRI, BRI, MUD},
    { MUD, MUD, MUD, MUD, MUD, MUD, BRI, MUD,
      MUD, BRI, MUD, BRI, MUD, MUD, MUD, MUD}
};
/* @} */

/*! \ingroup tank_elf
 * @{
 */
static tank_t *tank_new(coord_t *coord_pos,
			coord_t *coord_rot,
			int map_x, int map_y,
			tank_rt_t *tank_rt) {
    tank_t *tank;
    redraw_man_t *rdman;

    tank = O_ALLOC(tank_t);
    if(tank == NULL)
	return NULL;

    rdman = mb_runtime_rdman(tank_rt->mb_rt);

    tank->coord_pos = coord_pos;
    tank->coord_rot = coord_rot;
    tank->map_x = map_x;
    tank->map_y = map_y;
    tank->direction = TD_UP;
    tank->progm = NULL;
    tank->bullet = NULL;
    tank->tank_rt = tank_rt;

    memset(coord_pos->matrix, 0, sizeof(co_aix[6]));
    coord_pos->matrix[0] = 1;
    coord_pos->matrix[2] = map_x * 50;
    coord_pos->matrix[4] = 1;
    coord_pos->matrix[5] = map_y * 50;
    rdman_coord_changed(rdman, coord_pos);

    return tank;
}

static void tank_free(tank_t *tank, mb_rt_t *mb_rt) {
    if(tank->progm) {
	mb_progm_abort(tank->progm);
    }
    free(tank);
}

/*! \brief Clean program for a tank.
 *
 * It is called when the program is completed.
 */
static void clean_tank_progm_handler(event_t *event, void *arg) {
    tank_t *tank = (tank_t *)arg;

    mb_progm_free(tank->progm);
    tank->progm = NULL;
}

#define PI 3.1415926

void
tank_move(tank_t *tank, int direction, tank_rt_t *tank_rt) {
    mb_rt_t *mb_rt = tank_rt->mb_rt;
    redraw_man_t *rdman;
    mb_timer_man_t *timer_man;
    observer_factory_t *factory;
    /* for the program */
    mb_progm_t *progm;
    subject_t *comp_sub;
    mb_word_t *word;
    mb_timeval_t start, playing;
    mb_timeval_t now;
    /* for position */
    co_aix sh_x, sh_y;
    /* for direction */
    float ang1, ang2;
    float rot_diff;
    int i;
    static co_aix shift_xy[][2] = {{0, -50}, {50, 0}, {0, 50}, {-50, 0}};
    static int map_shift[4][2] = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}};
    static float angles[4] = {0, PI / 2, PI , PI * 3 / 2};
    static float rotations[7] = {PI / 2, PI , -PI / 2,
				 0, PI / 2, PI , -PI / 2};

    if(tank->progm != NULL)
	return;

    /*
     * Keep inside the map.
     */
    if(direction == tank->direction) {
	switch(direction) {
	case TD_UP:
	    if(tank->map_y == 0)
		return;
	    if(map[tank->map_y - 1][tank->map_x] != MUD)
		return;
	    break;
	case TD_RIGHT:
	    if(tank->map_x >= (MAP_W - 1))
		return;
	    if(map[tank->map_y][tank->map_x + 1] != MUD)
		return;
	    break;
	case TD_DOWN:
	    if(tank->map_y >= (MAP_H - 1))
		return;
	    if(map[tank->map_y + 1][tank->map_x] != MUD)
		return;
	    break;
	case TD_LEFT:
	    if(tank->map_x == 0)
		return;
	    if(map[tank->map_y][tank->map_x - 1] != MUD)
		return;
	    break;
	}

	tank->map_x += map_shift[direction][0];
	tank->map_y += map_shift[direction][1];
	for(i = 0; i < tank_rt->n_tanks; i++) {
	    if(tank != tank_rt->tanks[i] &&
	       tank->map_x == tank_rt->tanks[i]->map_x &&
	       tank->map_y == tank_rt->tanks[i]->map_y) {
		tank->map_x -= map_shift[direction][0];
		tank->map_y -= map_shift[direction][1];
		return;
	    }
	}
    }

    rdman = mb_runtime_rdman(mb_rt);
    timer_man = mb_runtime_timer_man(mb_rt);
    factory = mb_runtime_observer_factory(mb_rt);

    progm = mb_progm_new(1, rdman);
    tank->progm = progm;

    MB_TIMEVAL_SET(&start, 0, 0);
    MB_TIMEVAL_SET(&playing, 0, 500000);
    word = mb_progm_next_word(progm, &start, &playing);

    if(direction == tank->direction) {
	/* Shift/move */
	sh_x = shift_xy[direction][0];
	sh_y = shift_xy[direction][1];
	mb_shift_new(sh_x, sh_y, tank->coord_pos, word);
    } else {
	/* Change direction */
	rot_diff = rotations[3 - tank->direction + direction];
	ang1 = angles[tank->direction];
	ang2 = ang1 + rot_diff;
	mb_rotate_new(ang1, ang2, tank->coord_rot, word);
	tank->direction = direction;
    }

    /* Clean program when it is completed. */
    comp_sub = mb_progm_get_complete(progm);
    subject_add_observer(comp_sub, clean_tank_progm_handler, tank);

    get_now(&now);
    mb_progm_start(progm, timer_man, &now);
}

/* @} */

/*! \ingroup bullet_elf
 * @{
 */
/*! \brief Make coord objects for bullet elfs. */
static void make_bullet_elf_coords(redraw_man_t *rdman, coord_t **coord_pos,
				   coord_t **coord_rot,
				   coord_t **coord_center) {
    coord_t *coord_back;

    *coord_pos = rdman_coord_new(rdman, rdman->root_coord);

    coord_back = rdman_coord_new(rdman, *coord_pos);
    coord_back->matrix[2] = 25;
    coord_back->matrix[5] = 25;
    rdman_coord_changed(rdman, coord_back);

    *coord_rot = rdman_coord_new(rdman, coord_back);

    *coord_center = rdman_coord_new(rdman, *coord_rot);
    (*coord_center)->matrix[2] = -5;
    (*coord_center)->matrix[5] = +15;
    rdman_coord_changed(rdman, *coord_center);
}

static tank_bullet_t *tank_bullet_new(redraw_man_t *rdman,
				      int map_x, int map_y,
				      int direction) {
    tank_bullet_t *bullet;
    coord_t *coord_center;
    co_aix *matrix;
    static float _sins[] = { 0, 1, 0, -1};
    static float _coss[] = { 1, 0, -1, 0};
    float _sin, _cos;

    bullet = O_ALLOC(tank_bullet_t);
    bullet->rdman = rdman;
    bullet->hit_tmr = -1;

    make_bullet_elf_coords(rdman, &bullet->coord_pos,
			   &bullet->coord_rot,
			   &coord_center);
    bullet->bullet_obj = bullet_new(rdman, coord_center);
    
    bullet->start_map_x = map_x;
    bullet->start_map_y = map_y;
    bullet->direction = direction;
    bullet->progm = NULL;

    matrix = bullet->coord_pos->matrix;
    matrix[2] = map_x * 50;
    matrix[5] = map_y * 50;
    rdman_coord_changed(rdman, bullet->coord_pos);

    _sin = _sins[direction];
    _cos = _coss[direction];
    matrix = bullet->coord_rot->matrix;
    matrix[0] = _cos;
    matrix[1] = -_sin;
    matrix[3] = _sin;
    matrix[4] = _cos;

    return bullet;
}

static void tank_bullet_free(tank_bullet_t *bullet) {
    if(bullet->hit_tmr != -1)
	mb_timer_man_remove(bullet->timer_man, bullet->hit_tmr);
    bullet_free(bullet->bullet_obj);
    rdman_coord_subtree_free(bullet->rdman, bullet->coord_pos);
}

static void bullet_go_out_map(event_t *event, void *arg) {
    tank_t *tank = (tank_t *)arg;
    tank_bullet_t *bullet;
    redraw_man_t *rdman;

    bullet = tank->bullet;
    rdman = bullet->rdman;

    if(bullet->hit_tmr != -1) {
	mb_timer_man_remove(bullet->timer_man, bullet->hit_tmr);
	bullet->hit_tmr = -1;
    }

    coord_hide(bullet->coord_pos);
    rdman_coord_changed(rdman, bullet->coord_pos);
    
    bullet = tank->bullet;
    mb_progm_free(bullet->progm);
    tank_bullet_free(tank->bullet);
    tank->bullet = NULL;
}

static void bullet_bang(tank_bullet_t *bullet, int map_x, int map_y) {
    redraw_man_t *rdman;
    mb_timer_man_t *timer_man;
    mb_progm_t *progm;
    mb_word_t *word;
    mb_timeval_t start, playing;
    mb_timeval_t now;
    bang_t *bang;
    co_aix *matrix;

    rdman = bullet->rdman;
    timer_man = bullet->timer_man;

    bang = bang_new(rdman, rdman->root_coord);
    matrix = bang->root_coord->matrix;
    matrix[2] = map_x * 50;
    matrix[5] = map_y * 50;
    rdman_coord_changed(rdman, bang->root_coord);

    progm = mb_progm_new(3, rdman);

    MB_TIMEVAL_SET(&start, 1, 0);
    MB_TIMEVAL_SET(&playing, 0, 0);
    word = mb_progm_next_word(progm, &start, &playing);
    mb_visibility_new(VIS_HIDDEN, bang->root_coord, word);

    MB_TIMEVAL_SET(&start, 1, 500000);
    word = mb_progm_next_word(progm, &start, &playing);
    mb_visibility_new(VIS_VISIBLE, bang->root_coord, word);

    MB_TIMEVAL_SET(&start, 2, 500000);
    word = mb_progm_next_word(progm, &start, &playing);
    mb_visibility_new(VIS_HIDDEN, bang->root_coord, word);

    mb_subtree_free_new(bang->root_coord, word);

    mb_progm_free_completed(progm);

    get_now(&now);
    mb_progm_start(progm, timer_man, &now);
}

/*! \brief To check if a bullet hits walls or tanks. */
static void bullet_hit_chk(int hdl,
			   const mb_timeval_t *tmo,
			   const mb_timeval_t *now,
			   void *arg) {
    tank_t *tank = (tank_t *)arg;
    tank_rt_t *tank_rt = tank->tank_rt;
    tank_t *tank_hitted;
    tank_bullet_t *bullet;
    mb_timeval_t diff, next;
    mb_timeval_t unit_tm;
    float move_units_f;
    int move_units;
    int x, y;
    int dir;
    int i;
    static int move_adj[][2] = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}};

    bullet = tank->bullet;
    bullet->hit_tmr = -1;  /* clear hit timer that bullet_go_out_map()
			    * can not remove it & currupt memory.
			    */

    MB_TIMEVAL_CP(&diff, now);
    MB_TIMEVAL_DIFF(&diff, &bullet->start_time);
    MB_TIMEVAL_SET(&unit_tm, 0, 250000);
    move_units_f = MB_TIMEVAL_DIV(&diff, &unit_tm);
    move_units = floorl(move_units_f);
    dir = bullet->direction;
    x = bullet->start_map_x + move_adj[dir][0] * move_units;
    y = bullet->start_map_y + move_adj[dir][1] * move_units;

    if(map[y][x] != MUD) {
	mb_progm_abort(bullet->progm);
	bullet_go_out_map(NULL, tank);
	bullet_bang(bullet, x, y);
	return;
    }

    /*! \todo Move tanks into a list. */
    for(i = 0; i < tank_rt->n_enemy; i++) {
	tank_hitted = tank_rt->tank_enemies[i];
	if(tank_hitted == tank)
	    continue;
	if(tank_hitted->map_x == x &&
	   tank_hitted->map_y == y) {
	    mb_progm_abort(bullet->progm);
	    bullet_go_out_map(NULL, tank);
	    bullet_bang(bullet, x, y);
	    return;
	}
    }

    if(tank_rt->tank1 != tank) {
	tank_hitted = tank_rt->tank1;
	if(tank_hitted->map_x == x &&
	   tank_hitted->map_y == y) {
	    mb_progm_abort(bullet->progm);
	    bullet_go_out_map(NULL, tank);
	    bullet_bang(bullet, x, y);
	    return;
	}
    }

    if(tank_rt->tank2 != tank) {
	tank_hitted = tank_rt->tank2;
	if(tank_hitted->map_x == x &&
	   tank_hitted->map_y == y) {
	    mb_progm_abort(bullet->progm);
	    bullet_go_out_map(NULL, tank);
	    bullet_bang(bullet, x, y);
	    return;
	}
    }

    MB_TIMEVAL_SET(&next, 0, 100000);
    MB_TIMEVAL_ADD(&next, now);
    bullet->hit_tmr = mb_timer_man_timeout(bullet->timer_man, &next,
					   bullet_hit_chk, arg);
}

/*! \brief To fire a bullet for a tank. */
void
tank_fire_bullet(tank_rt_t *tank_rt, tank_t *tank) {
    mb_rt_t *mb_rt;
    redraw_man_t *rdman;
    int map_x, map_y;
    int shift_x, shift_y;
    int shift_len;
    int dir;
    tank_bullet_t *bullet;
    mb_progm_t *progm;
    mb_word_t *word;
    mb_action_t *act;
    mb_timeval_t start, playing;
    mb_timeval_t now, next;
    mb_timer_man_t *timer_man;
    subject_t *subject;
    static int map_xy_adj[][2] = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}};

    if(tank->bullet != NULL)
	return;

    mb_rt = tank_rt->mb_rt;
    rdman = mb_runtime_rdman(mb_rt);
    timer_man = mb_runtime_timer_man(mb_rt);

    dir = tank->direction;
    map_x = tank->map_x + map_xy_adj[dir][0];
    map_y = tank->map_y + map_xy_adj[dir][1];
    switch(dir) {
    case TD_UP:
	shift_len = map_y + 1;
	shift_x = 0;
	shift_y = -shift_len * 50;
	break;
    case TD_RIGHT:
	shift_len = 16 - map_x;
	shift_x = shift_len * 50;
	shift_y = 0;
	break;
    case TD_DOWN:
	shift_len = 12 - map_y;
	shift_x = 0;
	shift_y = shift_len * 50;
	break;
    case TD_LEFT:
	shift_len = map_x + 1;
	shift_x = -shift_len * 50;
	shift_y = 0;
	break;
    }

    if(shift_len <= 0)
	return;

    tank->bullet = tank_bullet_new(rdman, map_x, map_y, dir);
    bullet = tank->bullet;
    bullet->timer_man = timer_man;

    progm = mb_progm_new(2, rdman);
    MB_TIMEVAL_SET(&start, 0, 0);
    MB_TIMEVAL_SET(&playing, shift_len / 4, (shift_len % 4) * 250000);
    word = mb_progm_next_word(progm, &start, &playing);
    act = mb_shift_new(shift_x, shift_y, bullet->coord_pos, word);
    bullet->progm = progm;
    
    /*! \todo Simplify the procdure of using observer pattern. */
    subject = mb_progm_get_complete(progm);
    subject_add_observer(subject, bullet_go_out_map, tank);

    get_now(&now);
    MB_TIMEVAL_CP(&bullet->start_time, &now);
    mb_progm_start(progm, timer_man, &now);

    MB_TIMEVAL_SET(&next, 0, 100000);
    MB_TIMEVAL_ADD(&next, &now);
    bullet->hit_tmr = mb_timer_man_timeout(timer_man, &next,
					   bullet_hit_chk, tank);
}

/* @} */

/*! \ingroup tank
 * @{
 */
#define CHANGE_POS(g, x, y) do {			\
	(g)->root_coord->matrix[0] = 1.0;		\
	(g)->root_coord->matrix[2] = x;			\
	(g)->root_coord->matrix[4] = 1.0;		\
	(g)->root_coord->matrix[5] = y;			\
	rdman_coord_changed(rdman, (g)->root_coord);	\
    } while(0)

static void keyboard_handler(event_t *event, void *arg) {
    mb_kb_event_t *xkey = (mb_kb_event_t *)event;
    tank_rt_t *tank_rt = (tank_rt_t *)arg;
    int direction;

    if(xkey->event.type != EVT_KB_PRESS)
	return;

    switch(xkey->sym) {
    case 0xff51:		/* left */
	direction = TD_LEFT;
	tank_move(tank_rt->tank1, direction, tank_rt);
	break;

    case 0xff52:		/* up */
	direction = TD_UP;
	tank_move(tank_rt->tank1, direction, tank_rt);
	break;

    case 0xff53:		/* right */
	direction = TD_RIGHT;
	tank_move(tank_rt->tank1, direction, tank_rt);
	break;

    case 0xff54:		/* down */
	direction = TD_DOWN;
	tank_move(tank_rt->tank1, direction, tank_rt);
	break;

    case 0x20:			/* space */
	tank_fire_bullet(tank_rt, tank_rt->tank1);
	break;
    case 0xff0d:		/* enter */
    default:
	return;
    }

}

static void init_keyboard(tank_rt_t *tank_rt) {
    mb_rt_t *mb_rt;
    subject_t *kbevents;
    redraw_man_t *rdman;

    mb_rt = tank_rt->mb_rt;
    kbevents = mb_runtime_kbevents(mb_rt);

    rdman = mb_runtime_rdman(mb_rt);

    tank_rt->kb_observer =
	subject_add_observer(kbevents, keyboard_handler, tank_rt);
}

/*! \brief Make coord objects to decorate elfs (tanks).
 *
 * These coords are used to shift (move) and rotate decorated graphic.
 * The coords can easy work of programmer to manipulate geometry of
 * decorated graphic.
 */
static void make_elf_coords(redraw_man_t *rdman, coord_t **coord_pos,
			    coord_t **coord_rot, coord_t **coord_center) {
    coord_t *coord_back;

    *coord_pos = rdman_coord_new(rdman, rdman->root_coord);

    coord_back = rdman_coord_new(rdman, *coord_pos);
    coord_back->matrix[2] = 25;
    coord_back->matrix[5] = 25;
    rdman_coord_changed(rdman, coord_back);

    *coord_rot = rdman_coord_new(rdman, coord_back);

    *coord_center = rdman_coord_new(rdman, *coord_rot);
    (*coord_center)->matrix[2] = -25;
    (*coord_center)->matrix[5] = -25;
    rdman_coord_changed(rdman, *coord_center);
}

void
initial_tank(tank_rt_t *tank_rt, mb_rt_t *mb_rt) {
    redraw_man_t *rdman;
    /* for map areas */
    mud_t *mud;
    brick_t *brick;
    rock_t *rock;
    bush_t *bush;
    /* for tanks */
    coord_t *coord_center, *coord_pos, *coord_rot;
    tank1_t *tank1_o;
    tank2_t *tank2_o;
    tank_en_t *tank_en_o;
    int i, j;

    rdman = mb_runtime_rdman(mb_rt);

    tank_rt->mb_rt = mb_rt;
    for(i = 0; i < 12; i++) {
	for(j = 0; j < 16; j++) {
	    switch(map[i][j]) {
	    case MUD:
		mud = mud_new(rdman, rdman->root_coord);
		CHANGE_POS(mud, j * 50, i * 50);
		tank_rt->map[i][j] = (void *)mud;
		break;
	    case BRI:
		brick = brick_new(rdman, rdman->root_coord);
		CHANGE_POS(brick, j * 50, i * 50);
		tank_rt->map[i][j] = (void *)brick;
		break;
	    case ROC:
		rock = rock_new(rdman, rdman->root_coord);
		CHANGE_POS(rock, j * 50, i * 50);
		tank_rt->map[i][j] = (void *)rock;
		break;
	    case BSH:
		bush = bush_new(rdman, rdman->root_coord);
		CHANGE_POS(bush, j * 50, i * 50);
		tank_rt->map[i][j] = (void *)bush;
		break;
	    }
	}
    }

    make_elf_coords(rdman, &coord_pos, &coord_rot, &coord_center);
    tank1_o = tank1_new(rdman, coord_center);
    tank_rt->tank1 = tank_new(coord_pos, coord_rot, 5, 11, tank_rt);
    tank_rt->tank1_o = tank1_o;

    make_elf_coords(rdman, &coord_pos, &coord_rot, &coord_center);
    tank2_o = tank2_new(rdman, coord_center);
    tank_rt->tank2 = tank_new(coord_pos, coord_rot, 10, 11, tank_rt);
    tank_rt->tank2_o = tank2_o;

    for(i = 0; i < 3; i++) {
	make_elf_coords(rdman, &coord_pos, &coord_rot, &coord_center);
	tank_en_o = tank_en_new(rdman, coord_center);
	tank_rt->tank_enemies[i] = tank_new(coord_pos, coord_rot,
					    i * 3 + 3, 0, tank_rt);
	tank_rt->tank_enemies_o[i] = tank_en_o;
	tank_rt->tanks[i] = tank_rt->tank_enemies[i];
    }
    tank_rt->n_enemy = i;

    tank_rt->tanks[i++] =tank_rt->tank1;
    tank_rt->tanks[i++] =tank_rt->tank2;
    tank_rt->n_tanks = i;

    init_keyboard(tank_rt);
}

int
main(int argc, char *const argv[]) {
    mb_rt_t *rt;
    tank_rt_t tank_rt;

#ifdef CONSOLE_BACKEND
    rt = mb_runtime_new(NULL, 800, 600);
#else
    rt = mb_runtime_new(":0.0", 800, 600);
#endif

    initial_tank(&tank_rt, rt);
    
    init_enemies(&tank_rt);
    
    mb_runtime_event_loop(rt);

    mb_runtime_free(rt);
}

/* @} */