Zainspirowany postem kolegi nie radzącym sobie z pracą domową, w ramach prokrastynacji przed sesją, walnąłem sobie konsolowego arkanoida:
#define NDEBUG
#include <curses.h>
#include <stdlib.h>
#include <stdbool.h>
#include <signal.h>
#include <math.h>
#include <assert.h>
const short brick_color = 1;
const short other_color = 2;
#define board_y_size 21
#define board_x_size 78
typedef enum { EMPTY, BRICK, BALL } content;
typedef struct
{
unsigned lv;
unsigned balls_left;
bool running;
content board[board_y_size][board_x_size];
int racket_pos;
int delta_y, delta_x;
unsigned score;
} game_data;
void init_new_level(game_data* gd)
{
gd->lv++;
if(gd->lv % 3 == 0)
gd->balls_left++;
gd->running = false;
gd->racket_pos = 39;
for(int y = 0; y < board_y_size; y++)
for(int x = 0; x < board_x_size; x++)
gd->board[y][x] = EMPTY;
for(int y = 2; y < board_y_size - 4; y++)
for(int x = 2; x < board_x_size - 2; x++)
gd->board[y][x] = BRICK;
gd->board[board_y_size-1][gd->racket_pos] = BALL;
}
void advance_ball(game_data* gd, int old_y, int old_x,
int new_y, int new_x)
{
gd->board[old_y][old_x] = EMPTY;
gd->board[new_y][new_x] = BALL;
}
void recover_from_miss(game_data* gd)
{
gd->racket_pos = 39;
int ball_y, ball_x;
for(int y = 0; y < board_y_size; y++)
for(int x = 0; x < board_x_size; x++)
if(gd->board[y][x] == BALL)
{
ball_y = y;
ball_x = x;
goto ball_found;
}
ball_found:
advance_ball(gd, ball_y, ball_x, board_y_size-1, gd->racket_pos);
}
int start_level(game_data* gd, int input)
{
gd->delta_y = -1;
if(input == KEY_LEFT)
gd->delta_x = -1;
else
gd->delta_x = 1;
gd->running = true;
return (int)round(1000.0/pow(1.25, gd->lv-1));
}
void y_bounce(game_data* gd)
{
gd->delta_y *= -1;
}
void x_bounce(game_data* gd)
{
gd->delta_x *= -1;
}
void flip(game_data* gd)
{
y_bounce(gd);
x_bounce(gd);
}
void crush_bricks(game_data* gd, int y, int x)
{
if(y >= 0 && y < board_y_size && x >= 0 && x < board_x_size &&
gd->board[y][x] == BRICK)
{
gd->board[y][x] = EMPTY;
gd->score++;
}
}
bool is_out_of_board(int y)
{
return y >= board_y_size;
}
bool is_blocked(game_data* gd, int y, int x)
{
if(y < 0 || x < 0 || x >= board_x_size)
return true;
return gd->board[y][x] == BRICK;
}
bool normal_move(game_data* gd, int old_y, int old_x)
{
int new_y = old_y + gd->delta_y;
int new_x = old_x + gd->delta_x;
if(is_out_of_board(new_y))
{
gd->running = false;
gd->balls_left--;
return false;
}
else
{
int actual_new_x = new_x, actual_new_y = new_y;
if(is_blocked(gd, old_y, new_x) && is_blocked(gd, new_y, old_x) ||
is_blocked(gd, new_y, new_x) &&
!is_blocked(gd, old_y, new_x) && !is_blocked(gd, new_y, old_x))
{
flip(gd);
actual_new_x = old_x;
actual_new_y = old_y;
}
else if(is_blocked(gd, old_y, new_x))
{
actual_new_x = old_x;
x_bounce(gd);
}
else if(is_blocked(gd, new_y, old_x))
{
actual_new_y = old_y;
y_bounce(gd);
}
advance_ball(gd, old_y, old_x, actual_new_y, actual_new_x);
crush_bricks(gd, old_y, new_x);
crush_bricks(gd, new_y, old_x);
crush_bricks(gd, new_y, new_x);
}
return true;
}
bool collide_with_racket(game_data* gd, int old_y, int old_x)
{
int new_y = old_y + gd->delta_y;
int new_x = old_x + gd->delta_x;
if(old_y != board_y_size-1 || new_y != board_y_size)
return false;
if(new_x == gd->racket_pos-1 || new_x == gd->racket_pos-2 && gd->delta_x < 0)
{
y_bounce(gd);
new_x = gd->racket_pos+1;
gd->delta_x = 1;
new_y = old_y-1;
advance_ball(gd, old_y, old_x, new_y, new_x);
return true;
}
else if(new_x == gd->racket_pos+1 || new_x == gd->racket_pos + 2 && gd->delta_x > 0)
{
y_bounce(gd);
new_x = gd->racket_pos-1;
gd->delta_x = -1;
new_y = old_y-1;
advance_ball(gd, old_y, old_x, new_y, new_x);
return true;
}
else if(new_x == gd->racket_pos)
{
y_bounce(gd);
new_x += gd->delta_x;
new_y = old_y-1;
advance_ball(gd, old_y, old_x, new_y, new_x);
return true;
}
return false;
}
bool no_bricks_left(game_data* gd)
{
for(int y = 0; y < board_y_size; y++)
for(int x = 0; x < board_x_size; x++)
if(gd->board[y][x] == BRICK)
return false;
gd->running = false;
return true;
}
bool process_turn(game_data* gd)
{
int ball_y, ball_x;
for(int y = 0; y < board_y_size; y++)
for(int x = 0; x < board_x_size; x++)
if(gd->board[y][x] == BALL)
{
ball_y = y;
ball_x = x;
goto ball_found;
}
ball_found:
if(!collide_with_racket(gd, ball_y, ball_x))
{
if(!normal_move(gd, ball_y, ball_x))
return false;
return no_bricks_left(gd);
}
return false;
}
void move_racket(game_data* gd, int racket_move)
{
if(gd->racket_pos + racket_move >= 1 &&
gd->racket_pos + racket_move < board_x_size-1)
{
gd->racket_pos+=racket_move;
}
}
void print_game_data(game_data const* gd)
{
for(int x = 0; x < 80; x++)
{
mvaddch(0, x, ' ');
mvaddch(23, x, ' ');
}
mvprintw(0, 3, "Lv: %u", gd->lv);
mvprintw(0, 65, "Balls: %u", gd->balls_left);
mvprintw(0, 52, "Score: %u", gd->score);
if(!gd->running)
{
attron(A_BLINK);
if(gd->balls_left)
mvprintw(0, 16, "PRESS LEFT OR RIGHT TO START");
else
mvprintw(0, 25, "GAME OVER");
attroff(A_BLINK);
}
mvaddch(1, 0, ACS_ULCORNER);
mvaddch(1, 79, ACS_URCORNER);
for(int y = 2; y < 24; y++)
{
mvaddch(y, 0, ACS_VLINE);
mvaddch(y, 79, ACS_VLINE);
}
for(int x = 1; x < 79; x++)
mvaddch(1, x, ACS_HLINE);
const int y_offset = 2;
const int x_offset = 1;
for(int y = 0; y < board_y_size; y++)
for(int x = 0; x < board_x_size; x++)
{
chtype char_to_print;
switch(gd->board[y][x])
{
case EMPTY:
char_to_print = ' ';
break;
case BRICK:
char_to_print = ACS_CKBOARD | COLOR_PAIR(brick_color);
break;
case BALL:
char_to_print = 'O';
break;
}
mvaddch(y+y_offset, x+x_offset, char_to_print);
}
const int racket_print_pos = gd->racket_pos+x_offset;
mvaddch(23, racket_print_pos-1, ACS_HLINE);
mvaddch(23, racket_print_pos, ACS_TTEE);
mvaddch(23, racket_print_pos+1, ACS_HLINE);
refresh();
}
void game_loop(game_data* gd)
{
int sleep_time;
bool advance_lv = true;
while(true)
{
if(!gd->running)
{
nodelay(stdscr, FALSE);
if(gd->balls_left)
if(advance_lv)
init_new_level(gd);
else
recover_from_miss(gd);
print_game_data(gd);
int input;
do input = getch();
while(!(gd->balls_left && (input == KEY_LEFT || input == KEY_RIGHT)));
sleep_time = start_level(gd, input);
nodelay(stdscr, TRUE);
process_turn(gd);
}
else
{
print_game_data(gd);
napms(sleep_time);
int racket_move = 0;
int input;
while((input = getch()) != ERR)
if(input == KEY_LEFT)
racket_move = -1;
else if(input == KEY_RIGHT)
racket_move = 1;
else if(input == KEY_DOWN)
racket_move = 0;
move_racket(gd, racket_move);
advance_lv = process_turn(gd);
}
}
}
void cleanup(int signal)
{
endwin();
exit(0);
}
int main()
{
struct sigaction cleanup_action = { .sa_handler = cleanup, .sa_flags = 0 };
sigfillset(&cleanup_action.sa_mask);
sigaction(SIGINT, &cleanup_action, NULL);
initscr();
cbreak();
keypad(stdscr, TRUE);
noecho();
nonl();
curs_set(0);
start_color();
nodelay(stdscr, TRUE);
init_pair(brick_color, COLOR_RED, COLOR_WHITE);
init_pair(other_color, COLOR_BLACK, COLOR_WHITE);
attrset(COLOR_PAIR(other_color));
for(int y = 0; y < 24; y++)
for(int x = 0; x < 80; x++)
mvaddch(y, x, ' ');
refresh();
game_data gd = {.lv = 0, .balls_left = 3, .score = 0};
game_loop(&gd);
return 0;
}
Komentarze?
(Tak wiem, nie mam obsługi błędów, ale obsługa błędów w C jest upierdliwa a te funkcje których używam raczej nie powinny wywalać błędów na rozsądnych platformach, a pisałem to dla rozrywki)