void Game::input()
{
SDL_Event event;
while(SDL_PollEvent(&event)) {
if(event.type == SDL_QUIT) {
exit_ = true;
}
if(event.type == SDL_KEYDOWN) {
switch(event.key.keysym.sym) {
case SDLK_ESCAPE:
exit_ = true;
break;
case SDLK_F11:
//If game window is in fullscreen mode - restore default resolution. Otherwise set fullscreen mode
if (SDL_GetWindowFlags(window_) & SDL_WINDOW_FULLSCREEN) {
SDL_SetWindowFullscreen(window_, 0);
} else {
SDL_SetWindowFullscreen(window_, SDL_WINDOW_FULLSCREEN);
}
break;
case SDLK_SPACE:
if(!start_) {
start_ = true;
game_over_ = false;
win_ = false;
if(!game_over_) {
time_from_start_ = SDL_GetTicks();
}
time_of_status_change_ = time_from_start_;
}
break;
default:
break;
}
//This conditional makes pacman movement impossible if space was not pressed
if(start_) {
switch(event.key.keysym.sym) {
case SDLK_UP:
new_direction_ = direction::UP;
break;
case SDLK_DOWN:
new_direction_ = direction::DOWN;
break;
case SDLK_LEFT:
new_direction_ = direction::LEFT;
break;
case SDLK_RIGHT:
new_direction_ = direction::RIGHT;
break;
default:
//Default direction is NONE (player didn't push any arrow key)
new_direction_ = direction::NONE;
break;
}
}
}
}
}
void Game::update()
{
handle_ghosts_collision();
handle_teleports();
handle_eatable_collision();
handle_pacman_moves();
handle_ghosts_moves();
}
void Game::handle_ghosts_collision()
{
for(int i = 0; i < 4; ++i) {
if(!ghosts_[i]->get_is_dead()) {
int distance_x = abs(pacman_->get_x() - ghosts_[i]->get_x());
int distance_y = abs(pacman_->get_y() - ghosts_[i]->get_y());
int tollerance;
if(ghosts_[i]->get_direction() == pacman_->get_direction()) {
tollerance = board_->get_size_of_element() / 5 * 4;
} else {
tollerance = board_->get_size_of_element() / 2;
}
if(distance_x < tollerance && distance_y < tollerance) {
if(ghosts_[i]->get_is_scared()) {
++eaten_ghosts_;
score_ += 100 * static_cast<int>(pow(2.0, eaten_ghosts_));
update_actual_score_ = true;
ghosts_[i]->set_scared(false);
ghosts_[i]->set_is_dead(true);
if(score_ > highest_score_) {
highest_score_ = score_;
update_highest_score_ = true;
}
if(!Mix_Playing(1)) {
Mix_PlayChannel(1, eat_ghost_sound_, 0);
}
} else {
handle_dead();
return;
}
}
}
}
}
void Game::handle_teleports()
{
int pacman_x = pacman_->get_x();
int pacman_y = pacman_->get_y();
if(board_->is_teleport_collision(pacman_->get_x(), pacman_->get_y())) {
board_->teleport_object(&pacman_x, &pacman_y);
pacman_->set_position(pacman_x, pacman_y);
}
for(int i = 0; i < 4; ++i) {
int ghost_x = ghosts_[i]->get_x();
int ghost_y = ghosts_[i]->get_y();
if(board_->is_teleport_collision(ghost_x, ghost_y)) {
board_->teleport_object(&ghost_x, &ghost_y);
ghosts_[i]->set_position(ghost_x, ghost_y);
}
}
}
void Game::handle_eatable_collision()
{
int pacman_x = pacman_->get_x();
int pacman_y = pacman_->get_y();
bool was_fruit_eaten = false;
bool was_booster_eaten = false;
if(board_->is_eatable_collision(pacman_x, pacman_y)) {
char object_at_pacman_position = board_->get_element_by_center(pacman_x, pacman_y);
if(object_at_pacman_position == static_cast<char>(symbols::SCORE)) {
score_ += 1;
board_->set_was_eaten(pacman_x, pacman_y, true);
++eaten_scores_;
} else if(object_at_pacman_position == static_cast<char>(symbols::BOOSTER)) {
score_ += 10;
board_->set_was_eaten(pacman_x, pacman_y, true);
++eaten_scores_;
was_booster_eaten = true;
if(!Mix_Playing(3)) {
Mix_PlayChannel(3, booster_sound_, 0);
}
} else if(object_at_pacman_position == static_cast<char>(symbols::FRUIT) && board_->is_fruit(pacman_x, pacman_y)) {
object_type fruit_type = board_->get_fruit_type();
auto it = scoring.find(fruit_type);
if(it == scoring.end()) {
std::cerr << "Game::update(): " << static_cast<char>(fruit_type) << std::endl;
throw "Game::update() failed! There is not such a fruit type!";
}
score_ += it->second;
was_fruit_eaten = true;
if(!Mix_Playing(2)) {
Mix_PlayChannel(2, eat_fruit_sound_, 0);
}
}
if(eaten_scores_ == board_->get_objects_to_win()) {
handle_win();
return;
}
update_actual_score_ = true;
if(score_ > highest_score_) {
highest_score_ = score_;
update_highest_score_ = true;
}
}
handle_booster(was_booster_eaten);
board_->handle_fruit(time_from_start_, was_fruit_eaten);
}
void Game::handle_booster(bool was_eaten_moment_ago)
{
if(was_eaten_moment_ago) {
pacman_->set_boosted(true);
pacman_->set_status(status::BOOST);
for(int i = 0; i < 4; ++i) {
if(!ghosts_[i]->get_is_dead()) {
ghosts_[i]->set_scared(true);
ghosts_[i]->change_direction_to_opposite();
}
}
eaten_ghosts_ = 0;
} else {
pacman_->boost_countdown();
if(!pacman_->get_boosted()) {
for(int i = 0; i < 4; ++i) {
if(!ghosts_[i]->get_is_dead()) {
ghosts_[i]->set_scared(false);
}
}
for(int i = 0; i < 4; ++i) {
if(!ghosts_[i]->get_is_dead()) {
ghosts_[i]->make_correction(*board_);
}
}
}
}
}
void Game::handle_ghosts_moves()
{
for(int i = 0; i < 4; ++i) {
if(!ghosts_[i]->get_is_dead()) {
if(SDL_GetTicks() - time_from_start_ < ghosts_[i]->get_delay()) {
ghosts_[i]->AI(pacman_->get_x(), pacman_->get_y(), *board_);
} else if(ghosts_[i]-> get_is_inside()) {
ghosts_[i]->AI(board_->get_navigation_position_x(), board_->get_navigation_position_y(), *board_, false);
if(ghosts_[i]->get_x() == board_->get_navigation_position_x() && ghosts_[i]->get_y() == board_->get_navigation_position_y())
ghosts_[i]->set_is_inside(false);
} else {
//20 seconds of chasing and 10 seconds of "stupid-mode"
if(SDL_GetTicks() - time_from_start_ > time_from_chase_ && SDL_GetTicks() - time_from_start_ < time_from_random_) {
ghosts_[i]->AI(pacman_->get_x(), pacman_->get_y(), *board_);
update_time_from_chase_ = true;
} else if(update_time_from_chase_) {
time_from_chase_ += 30'000;
update_time_from_chase_ = false;
}
if(SDL_GetTicks() - time_from_start_ > time_from_random_ && SDL_GetTicks() - time_from_start_ < time_from_chase_) {
ghosts_[i]->AI(ghosts_[i]->get_corner_x(), ghosts_[i]->get_corner_y(), *board_);
update_time_from_random_ = true;
} else if(update_time_from_random_) {
time_from_random_ += 30'000;
update_time_from_random_ = false;
}
}
} else {
ghosts_[i]->AI(board_->get_resurection_position_x(), board_->get_resurection_position_y(), *board_, false);
if(ghosts_[i]->get_x() == board_->get_resurection_position_x() && ghosts_[i]->get_y() == board_->get_resurection_position_y()) {
ghosts_[i]->set_is_dead(false);
ghosts_[i]->set_is_inside(true);
}
}
}
}
void Game::handle_pacman_moves()
{
//Predicted Pacman position is equal to it's current position +/- his velocity (correction is made later by predict_pacman_position())
int predicted_x = pacman_->get_x();
int predicted_y = pacman_->get_y();
//Default: Pacman is snapping
render_pacman_new_status_ = true;
//If new_direction_ exists and move is possible - do it. If not - remember this direction
pacman_->predict_pacman_position(&predicted_x, &predicted_y, new_direction_);
if(new_direction_ != direction::NONE) {
if(!board_->is_wall_collision(predicted_x, predicted_y, new_direction_)) {
pacman_->move(predicted_x, predicted_y, new_direction_);
stored_direction_ = new_direction_;
if_possible_direction_ = new_direction_;
if(Mix_Playing(0) == 0) {
Mix_PlayChannel(0, move_sound_, 0);
}
return;
} else {
if_possible_direction_ = new_direction_;
}
}
//Previous values were changed by predict_pacman_position()
predicted_x = pacman_->get_x();
predicted_y = pacman_->get_y();
//If if_possible_direction_ exists and move is possible - do it
pacman_->predict_pacman_position(&predicted_x, &predicted_y, if_possible_direction_);
if(if_possible_direction_ != direction::NONE) {
if(!board_->is_wall_collision(predicted_x, predicted_y, if_possible_direction_)) {
pacman_->move(predicted_x, predicted_y, if_possible_direction_);
stored_direction_ = if_possible_direction_;
if(Mix_Playing(0) == 0) {
Mix_PlayChannel(0, move_sound_, 0);
}
return;
}
}
//Previous values were changed by predict_pacman_position()
predicted_x = pacman_->get_x();
predicted_y = pacman_->get_y();
//If above moves are impossible - check stored_direction_
pacman_->predict_pacman_position(&predicted_x, &predicted_y, stored_direction_);
if(stored_direction_ != direction::NONE && !board_->is_wall_collision(predicted_x, predicted_y, stored_direction_)) {
pacman_->move(predicted_x, predicted_y, stored_direction_);
if(Mix_Playing(0) == 0) {
Mix_PlayChannel(0, move_sound_, 0);
}
return;
}
//If Pacman does not move (no return was called up to this moment) than he is not snapping
render_pacman_new_status_ = false;
Mix_HaltChannel(0);
}
void Game::handle_win()
{
Mix_HaltChannel(-1);
Mix_PlayChannel(6, beginning_sound_, 0);
time_from_chase_ = 0;
eaten_scores_ = 0;
time_from_random_ = 20'000;
update_time_from_chase_ = false;
update_time_from_random_ = false;
was_reset_ = true;
win_ = true;
start_ = false;
new_direction_ = direction::NONE;
stored_direction_ = direction::NONE;
if_possible_direction_ = direction::NONE;
delete board_;
board_ = new Board(board_txt_path_);
delete pacman_;
pacman_ = new Pacman(board_->get_starting_position_y(symbols::PACMAN), board_->get_starting_position_x(symbols::PACMAN));
delete_ghosts();
make_ghosts();
}
void Game::handle_dead()
{
time_from_chase_ = 0;
time_from_random_ = 20'000;
was_reset_ = true;
start_ = false;
new_direction_ = direction::NONE;
stored_direction_ = direction::NONE;
if_possible_direction_ = direction::NONE;
delete pacman_;
pacman_ = new Pacman(board_->get_starting_position_y(symbols::PACMAN), board_->get_starting_position_x(symbols::PACMAN));
delete_ghosts();
make_ghosts();
Mix_HaltChannel(-1);
if(--lives_ == 0) {
handle_game_over();
return;
}
Mix_PlayChannel(4, death_sound_, 0);
}
void Game::handle_game_over()
{
Mix_PlayChannel(5, game_over_sound_, 0);
lives_ = 3;
score_ = 0;
eaten_scores_ = 0;
game_over_ = true;
update_actual_score_ = true;
delete board_;
board_ = new Board(board_txt_path_);
}
I powiązany z nim plik nagłówkowy:
#ifndef SRC_GAME_H_
#define SRC_GAME_H_
#include <string>
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <SDL2/SDL_mixer.h>
#include "enums.h"
#include "board.h"
#include "pacman.h"
#include "clyde.h"
#include "pinky.h"
#include "blinky.h"
#include "inky.h"
class Game
{
public:
Game(int argc, char* argv[]);
~Game();
//Game loop
void execute();
private:
const int screen_width_ = 800;
const int screen_height_ = 620;
//Font sizes
const int score_font_size_ = 24;
const int caption_font_size_ = 38;
const int manual_font_size_ = 18;
//The more it grow, the slower it snapps
const int snap_velocity_ = 250;
const std::string board_txt_path_ = "board.txt";
const std::string font_name_ = "../fonts/NES-Chimera/NES-Chimera.ttf";
const std::map<object_type, int> scoring = {
{object_type::CHERRY, 100},
{object_type::STRAWBERRY, 300},
{object_type::ORANGE, 500},
{object_type::APPLE, 700},
{object_type::MELON, 1000},
{object_type::GALAXIAN, 2000},
{object_type::BELL, 3000},
{object_type::KEY, 5000}
};
int score_;
int highest_score_;
int eaten_scores_;
int eaten_ghosts_;
int lives_;
//When was a last time you changed a pacman status (from OPEN to CLOSE or from CLOSE to OPEN)?
int time_of_status_change_;
int time_from_start_;
int time_from_chase_;
int time_from_random_;
bool render_game_over_;
bool render_mazee;
bool render_pacman_new_status_;
bool update_highest_score_;
bool update_actual_score_;
bool update_time_from_chase_;
bool update_time_from_random_;
bool was_reset_;
bool start_;
bool win_;
bool game_over_;
bool exit_;
//Debug variables
bool sdl_init_was_correct_;
bool img_init_was_correct_;
bool mixer_init_was_correct_;
bool ttf_init_was_correct_;
bool cursor_was_disabled_;
//Board contains ASCII representation of the game
Board* board_;
//Screen handling
SDL_Window* window_;
SDL_Renderer* renderer_;
//Sound handling
Mix_Chunk* beginning_sound_;
Mix_Chunk* move_sound_;
Mix_Chunk* death_sound_;
Mix_Chunk* eat_fruit_sound_;
Mix_Chunk* eat_ghost_sound_;
Mix_Chunk* booster_sound_;
Mix_Chunk* game_over_sound_;
//These variables are used to print text
SDL_Texture* highest_score_caption_font_;
SDL_Texture* highest_score_font_;
SDL_Texture* actual_score_caption_font_;
SDL_Texture* actual_score_font_;
SDL_Texture* game_over_font_;
SDL_Texture* counting_down_font_;
SDL_Texture* manual_caption_font_;
//These variables are used to render .png files
SDL_Texture* score_texture_;
SDL_Texture* boost_texture_;
SDL_Texture* wall_texture_;
SDL_Texture* cherry_texture_;
SDL_Texture* strawberry_texture_;
SDL_Texture* orange_texture_;
SDL_Texture* apple_texture_;
SDL_Texture* melon_texture_;
SDL_Texture* galaxian_texture_;
SDL_Texture* bell_texture_;
SDL_Texture* key_texture_;
SDL_Texture* scared_texture_;
SDL_Texture* eyes_texture_;
SDL_Texture* pacman_open_texture_;
SDL_Texture* pacman_close_texture_;
SDL_Texture* pacman_boost_texture_;
SDL_Texture* clyde_texture_;
SDL_Texture* pinky_texture_;
SDL_Texture* blinky_texture_;
SDL_Texture* inky_texture_;
SDL_Texture* live_texture_;
//These will be used in game loop to change pacman direction
direction new_direction_;
direction stored_direction_;
direction if_possible_direction_;
Pacman* pacman_;
Ghost* ghosts_[4];
//Game loop's methods
void input();
void update();
void render();
void render_maze();
void render_pacman();
void render_ghosts();
void render_scores();
void render_manual();
double rotation_texture_angle() const;
//Check debug variables. If sth is wrong - throw exception
void was_init_correct();
void handle_win();
void handle_game_over();
void handle_pacman_moves();
void handle_ghosts_moves();
void handle_dead();
void handle_booster(bool was_eaten_moment_ago = false);
void handle_ghosts_collision();
void handle_teleports();
void handle_eatable_collision();
void delete_ghosts();
void make_ghosts();
};
#endif
Jeżeli takie kody (oczywiście pod względem stylu, a nie objętości i złożoności ) będziecie wklejali na SPOJa, to poza szacunkiem i uznaniem z mojej strony (zawsze plus jedna osoba, nawet tak nijaka ), a także szybszą odpowiedzią ze strony innych (bo każdy woli analizować logiczny kod a nie zlepek losowych instrukcji), zwiększycie szanse na znalezienie pracy na dwa sposoby: w porządnej developerce jakość kodu ma znaczenie bo zwiększa wydajność zespołu i wy tą wydajność na pewno zwiększycie, a także szybciej przyswoicie sobie dowolny język programowania (mniej szukania oczywistych błędów)
I jakby tu podsumować ten spam Oczywiście najważniejsze, aby program działał, tzn. realizował określone funkcjonalności w określonym czasie. Druga istotna cecha kodu, to jego czytelność. Czytelność staje się istotna w kodzie, który ma być czytany wielokrotnie. Za czytelność w największym stopniu odpowiadają (kolejność od najważniejszego do najmniej ważnego, przy czym jest to moja skromna opinia):
- Prawidłowa indentacja
- Obrazowe, intuicyjne nazewnictwo zmiennych i funkcji
- Nieużywanie / nienadużywanie trudnych do analizowania instrukcji, jak np. goto
- Dobre komentarze
- Dobre rozbicie kodu na pliki, funkcje, klasy, funkcjonalności, …
- Konsystentny kod - wszędzie należy stosować te same metody nazewnictwa, robienia wcięć itd. W żadnym miejscu nie należy robić wyjątków, np. z 8 spacji na wcięcie przechodzić na 2 spacje, albo z ifów tworzonych w stylu “} else if(…) {” przechodzić na styl “else if(…) {”
Prosząc o pomoc na SPOJu zaleca się zwracać uwagę na 1), 2) i w mniejszym stopniu resztę. W realnym życiu życzę powodzenia każdemu, kto oleje którykolwiek z tych punktów robiąc projekt na > 5k linii kodu.