/* * 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; }