/*
* world.c -- Representación del estado del juego y carga del nivel.
* Copyright (C) 2018 Juan MarÃn Noguera
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include "block.h"
#include "block_list.h"
#include "collision.h"
#include "collision_type.h"
#include "error.h"
#include "item.h"
#include "item_list.h"
#include "screen.h"
#include "world.h"
#define WORLD_BGCOLOR_R 128
#define WORLD_BGCOLOR_G 128
#define WORLD_BGCOLOR_B 255
#define WORLD_BGCOLOR_A 255
struct World {
BlockList bl;
ItemList il;
Player p;
int scroll;
int dir;
int max_scroll;
};
struct WorldBlockDec {
BlockType type; // Block type
int ix; // X coordinate of the block in the picture (tiles)
int iy; // Y coordinate of the block in the picture (tiles)
int nanim; // Number of animation steps
int dx; // In world_block_dec, (dx,dy) is the size of the
int dy; // structure. In world_block_conts, this is the
// position of the block relative to the position of
// the first block defined in the structure.
int next; // Index of the next block in world_block_conts,
// or -1.
};
const struct WorldBlockDec world_block_dec[] = {
{BLOCK_TYPE_UPGRADE, 24, 0, 3, 1, 1, -1}, //a: Upgrade ? block
{BLOCK_TYPE_PASSTHROUGH, 2, 0, 1, 5, 5, 16}, //b: Castle
{BLOCK_TYPE_COIN, 2, 0, 1, 1, 1, -1}, //c: Simple coin block
{BLOCK_TYPE_DESTROYCOIN, 2, 0, 1, 1, 1, -1}, //d: Destroy coin block
{BLOCK_TYPE_COIN, 24, 0, 3, 1, 1, -1}, //e: Coin ? block
{BLOCK_TYPE_OPAQUE, 0, 0, 1, 1, 1, -1}, //f: Floor block
{BLOCK_TYPE_MULTICOIN, 2, 0, 1, 1, 1, -1}, //g: Multiple coin block
{BLOCK_TYPE_PASSTHROUGH, 8, 8, 1, 5, 3, 0}, //h: Hill
{BLOCK_TYPE_PASSTHROUGH, 8, 8, 1, 3, 2, 11}, //i: Small hill
{0, 16, 8, 1, 1, 1, -1}, //j: Nothing
{0, 16, 8, 1, 1, 1, -1}, //k: Nothing
{0, 16, 8, 1, 1, 1, -1}, //l: Nothing
{BLOCK_TYPE_PASSTHROUGH, 11, 9, 1, 1, 1, -1}, //m: Bush (left)
{BLOCK_TYPE_PASSTHROUGH, 12, 9, 1, 1, 1, -1}, //n: Bush (middle)
{BLOCK_TYPE_PASSTHROUGH, 13, 9, 1, 1, 1, -1}, //o: Bush (right)
{0, 16, 8, 1, 1, 1, -1}, //p: Nothing
{0, 16, 8, 1, 1, 1, -1}, //q: Nothing
{0, 16, 8, 1, 1, 1, -1}, //r: Nothing
{BLOCK_TYPE_OPAQUE, 0, 1, 1, 1, 1, -1}, //s: Stairs
{BLOCK_TYPE_OPAQUE, 0, 8, 1, 2, 1, 14}, //t: Tube (upper part)
{BLOCK_TYPE_OPAQUE, 0, 9, 1, 2, 1, 15}, //u: Tube (lower part)
{0, 16, 8, 1, 1, 1, -1}, //v: Nothing
{0, 16, 8, 1, 1, 1, -1}, //w: Nothing
{BLOCK_TYPE_PASSTHROUGH, 0, 21, 1, 1, 2, 8}, //x: Cloud (left)
{BLOCK_TYPE_PASSTHROUGH, 1, 21, 1, 1, 2, 9}, //y: Cloud (middle)
{BLOCK_TYPE_PASSTHROUGH, 2, 21, 1, 1, 1, 10}, //z: Cloud (right)
}, world_block_conts[] = {
{BLOCK_TYPE_PASSTHROUGH, 8, 8, 1, 1, 1, 1}, // Hill
{BLOCK_TYPE_PASSTHROUGH, 8, 9, 1, 1, 0, 2},
{BLOCK_TYPE_PASSTHROUGH, 10, 9, 1, 3, 0, 3},
{BLOCK_TYPE_PASSTHROUGH, 8, 9, 1, 2, 1, 4},
{BLOCK_TYPE_PASSTHROUGH, 9, 9, 1, 2, 0, 5},
{BLOCK_TYPE_PASSTHROUGH, 9, 8, 1, 2, 2, 6},
{BLOCK_TYPE_PASSTHROUGH, 10, 8, 1, 3, 1, 7},
{BLOCK_TYPE_PASSTHROUGH, 10, 8, 1, 4, 0, -1},
{BLOCK_TYPE_PASSTHROUGH, 0, 20, 1, 0, 1, -1}, // Cloud (left)
{BLOCK_TYPE_PASSTHROUGH, 1, 20, 1, 0, 1, -1}, // Cloud (middle)
{BLOCK_TYPE_PASSTHROUGH, 2, 20, 1, 0, 1, -1}, // Cloud (right)
{BLOCK_TYPE_PASSTHROUGH, 8, 9, 1, 1, 0, 12}, // Small hill
{BLOCK_TYPE_PASSTHROUGH, 10, 8, 1, 2, 0, 13},
{BLOCK_TYPE_PASSTHROUGH, 9, 8, 1, 1, 1, -1},
{BLOCK_TYPE_OPAQUE, 1, 8, 1, 1, 0, -1}, // Tube (upper part)
{BLOCK_TYPE_OPAQUE, 1, 9, 1, 1, 0, -1}, // Tube (lower part)
{BLOCK_TYPE_PASSTHROUGH, 2, 0, 1, 1, 0, 17}, // Castle
{BLOCK_TYPE_PASSTHROUGH, 2, 0, 1, 0, 1, 18},
{BLOCK_TYPE_PASSTHROUGH, 2, 0, 1, 1, 1, 19},
{BLOCK_TYPE_PASSTHROUGH, 2, 0, 1, 3, 0, 20},
{BLOCK_TYPE_PASSTHROUGH, 2, 0, 1, 3, 1, 21},
{BLOCK_TYPE_PASSTHROUGH, 2, 0, 1, 4, 0, 22},
{BLOCK_TYPE_PASSTHROUGH, 2, 0, 1, 4, 1, 23},
{BLOCK_TYPE_PASSTHROUGH, 13, 1, 1, 2, 0, 24},
{BLOCK_TYPE_PASSTHROUGH, 12, 1, 1, 2, 1, 25},
{BLOCK_TYPE_PASSTHROUGH, 11, 0, 1, 0, 2, 26},
{BLOCK_TYPE_PASSTHROUGH, 11, 1, 1, 1, 2, 27},
{BLOCK_TYPE_PASSTHROUGH, 11, 1, 1, 2, 2, 28},
{BLOCK_TYPE_PASSTHROUGH, 11, 1, 1, 3, 2, 29},
{BLOCK_TYPE_PASSTHROUGH, 11, 0, 1, 4, 2, 30},
{BLOCK_TYPE_PASSTHROUGH, 14, 0, 1, 1, 3, 31},
{BLOCK_TYPE_PASSTHROUGH, 2, 0, 1, 2, 3, 32},
{BLOCK_TYPE_PASSTHROUGH, 14, 0, 1, 3, 3, 33},
{BLOCK_TYPE_PASSTHROUGH, 11, 0, 1, 1, 4, 34},
{BLOCK_TYPE_PASSTHROUGH, 11, 0, 1, 2, 4, 35},
{BLOCK_TYPE_PASSTHROUGH, 11, 0, 1, 3, 4, -1}
};
Player world_read_player(FILE *f)
{
int x, y, lives;
if (fscanf(f, " %i %i %i", &x, &y, &lives) != 3)
error_exit(ERR_INVALID_INPUT);
return player_create(x * BLOCK_SIZE, y * BLOCK_SIZE, lives);
}
Block world_block_from_dec(const struct WorldBlockDec *wbd, int x, int y)
{
return block_create(wbd->type, x, y, wbd->ix, wbd->iy, wbd->nanim);
}
void world_parse_one_block(char type, int x, int y, BlockList bl)
{
const struct WorldBlockDec *wbd;
wbd = &(world_block_dec[type - 'a']);
block_list_append(bl, world_block_from_dec(wbd, x, y));
while (wbd->next != -1) {
wbd = &world_block_conts[wbd->next];
block_list_append(bl,
world_block_from_dec(wbd, x + wbd->dx, y + wbd->dy));
}
}
void world_parse_block(char type, int x, int y, int nx, int ny,
BlockList bl)
{
int i, j, dx, dy;
const struct WorldBlockDec *wbd;
if (type < 'a' || type > 'z')
error_exit(ERR_INVALID_INPUT);
wbd = &(world_block_dec[type - 'a']);
dx = wbd->dx;
dy = wbd->dy;
for (i = 0; i < nx; i++)
for (j = 0; j < ny; j++)
world_parse_one_block(
type,
x + dx*i,
y + dy*j,
bl);
}
BlockList world_read_blocks(FILE *f)
{
BlockList bl = block_list_create();
char type;
int x, y, nx, ny, read;
while ((read = fscanf(f, " %c %i %i %i %i", &type, &x, &y, &nx, &ny))
>= 3) {
if (read < 5) {
ny = 1;
if (read == 3)
nx = 1;
}
world_parse_block(type, x, y, nx, ny, bl);
}
return block_list_sort(bl);
}
ItemType world_item_type(char c)
{
switch (c) {
case 'g':
return ITEM_TYPE_GOOMBA;
case 'k':
return ITEM_TYPE_KOOPA;
case 'c':
return ITEM_TYPE_COIN;
default:
error_exit(ERR_INVALID_INPUT);
return 0; // Execution will never reach this point
}
}
ItemList world_read_items(FILE *f)
{
ItemList il = item_list_create();
char type;
int x, y, read;
while ((read = fscanf(f, " %c %i %i", &type, &x, &y)) == 3)
item_list_append(il,
item_create(world_item_type(type),
x * BLOCK_SIZE,
y * BLOCK_SIZE));
return il;
}
World world_create(FILE *f)
{
World w = malloc(sizeof(struct World));
if (w == NULL)
error_libc_exit();
w->p = world_read_player(f);
if (fscanf(f, " %i", &w->max_scroll) != 1)
error_libc_exit();
w->max_scroll *= BLOCK_SIZE;
w->bl = world_read_blocks(f);
w->il = world_read_items(f);
w->scroll = 0;
return w;
}
void world_free(World w)
{
player_free(w->p);
block_list_free(w->bl);
item_list_free(w->il);
free(w);
}
void world_end()
{
block_end();
item_end();
player_end();
}
Player world_player(World w)
{
return w->p;
}
void world_substitute_player(World w, Player p)
{
int x = player_x(w->p), y = player_y(w->p);
player_free(w->p);
w->p = p;
player_set_x(w->p, x);
player_set_y(w->p, y);
}
WorldState world_play_frame(Screen scr, World w)
{
SDL_Scancode sc;
int k;
char coins[7];
while ((k = screen_get_keyboard_event(&sc)) != 0)
if (k == SCREEN_KEYDOWN)
switch (sc) {
case SDL_SCANCODE_UP:
player_jump(w->p);
break;
case SDL_SCANCODE_LEFT:
w->dir = -1;
break;
case SDL_SCANCODE_RIGHT:
w->dir = 1;
break;
default:
;
}
else if (k == SCREEN_KEYUP)
switch (sc) {
case SDL_SCANCODE_LEFT:
w->dir = screen_key_pressed(SDL_SCANCODE_RIGHT)
? 1 : 0;
break;
case SDL_SCANCODE_RIGHT:
w->dir = screen_key_pressed(SDL_SCANCODE_LEFT)
? -1 : 0;
break;
default:
;
}
switch(w->dir) {
case -1:
player_accel(w->p, 1);
break;
case 0:
player_stop(w->p);
break;
case 1:
player_accel(w->p, 0);
}
player_update(w->p);
if (player_x(w->p) < w->scroll)
player_set_x(w->p, w->scroll);
if ((player_x(w->p) - w->scroll) * 2 > screen_width(scr))
w->scroll = player_x(w->p) - screen_width(scr) / 2;
if ((player_y(w->p) + player_height(w->p)) < 0)
player_die(w->p);
item_list_update(w->il, w->scroll, screen_width(scr));
collision_player_with_blocks(w->p, w->bl, w->il);
collision_player_with_items(w->p, w->il);
collision_blocks_with_items(w->bl, w->il);
screen_fill(scr, WORLD_BGCOLOR_R, WORLD_BGCOLOR_G, WORLD_BGCOLOR_B,
WORLD_BGCOLOR_A);
block_list_render(scr, w->bl, w->scroll);
item_list_render(scr, w->il, w->scroll);
player_render(scr, w->p, w->scroll);
if (player_coins(w->p) >= 1000000)
return WORLD_ST_MANY_COINS;
sprintf(coins, "%06d", player_coins(w->p));
screen_text_print(scr, 0, 0, 0, coins);
screen_update(scr);
return player_state(w->p) == PLAYER_ST_DEAD ? WORLD_ST_DEAD :
w->scroll >= w->max_scroll ? WORLD_ST_WON : WORLD_ST_KEEP_ON;
}