Pong2
// PONG: один файл, фронт (I/O) + демо‑бэкенд (логика); только <stdio.h>, без массивов, без goto.
#include <stdio.h> // Ввод/вывод: printf/putchar/getchar/fflush.
// ----------------------- Константы конфигурации поля ---------------------------------
#define WIDTH 80 // Ширина поля (0..79).
#define HEIGHT 25 // Высота поля (0..24).
#define PADDLE_SIZE 3 // Высота ракетки (3 символа).
// ----------------------- Фронтенд: рендер и ввод -------------------------------------
// Большие цифры (3x5) формируем через битовые маски, без массивов.
static int digit_mask_row(int d, int r) { // Вернёт 3-битную маску столбцов для цифры d на строке r (0..4).
if (d < 0 || d > 9 || r < 0 || r > 4) return 0;
if (d == 0) { if (r==0) return 7; if (r==1) return 5; if (r==2) return 5; if (r==3) return 5; return 7; }
if (d == 1) { if (r==0) return 2; if (r==1) return 6; if (r==2) return 2; if (r==3) return 2; return 7; }
if (d == 2) { if (r==0) return 7; if (r==1) return 1; if (r==2) return 7; if (r==3) return 4; return 7; }
if (d == 3) { if (r==0) return 7; if (r==1) return 1; if (r==2) return 7; if (r==3) return 1; return 7; }
if (d == 4) { if (r==0) return 5; if (r==1) return 5; if (r==2) return 7; if (r==3) return 1; return 1; }
if (d == 5) { if (r==0) return 7; if (r==1) return 4; if (r==2) return 7; if (r==3) return 1; return 7; }
if (d == 6) { if (r==0) return 7; if (r==1) return 4; if (r==2) return 7; if (r==3) return 5; return 7; }
if (d == 7) { if (r==0) return 7; if (r==1) return 1; if (r==2) return 1; if (r==3) return 1; return 1; }
/* d == 9 */ if (r==0) return 7; if (r==1) return 5; if (r==2) return 7; if (r==3) return 1; return 7;
}
// Вернёт 1, если (x,y) — «пиксель» крупной цифры счёта.
static int is_score_pixel(int x, int y, int sl, int sr) {
int y0 = 1, y1 = 5;
if (y < y0 || y > y1) return 0;
int lt = sl / 10, lo = sl % 10, rt = sr / 10, ro = sr % 10, row = y - y0;
int lc = WIDTH / 4, ls = lc - 4;
if (x >= ls + 1 && x <= ls + 3) { int col = x - (ls + 1); int m = digit_mask_row(lt, row); if (((m >> (2 - col)) & 1) != 0) return 1; }
if (x >= ls + 5 && x <= ls + 7) { int col = x - (ls + 5); int m = digit_mask_row(lo, row); if (((m >> (2 - col)) & 1) != 0) return 1; }
int rc = (3 * WIDTH) / 4, rs = rc - 4;
if (x >= rs + 1 && x <= rs + 3) { int col = x - (rs + 1); int m = digit_mask_row(rt, row); if (((m >> (2 - col)) & 1) != 0) return 1; }
if (x >= rs + 5 && x <= rs + 7) { int col = x - (rs + 5); int m = digit_mask_row(ro, row); if (((m >> (2 - col)) & 1) != 0) return 1; }
// Полный кадр: стены → счёт → ракетки → мяч → сетка → фон.
static void draw_frame(int sl, int sr, int bx, int by, int lp, int rp, int run) {
(void)run; // фронту всё равно — отрисовываем присланное состояние.
printf("\033[H\033[J"); // ANSI: очистить экран и вернуть курсор в (1,1).
int y = 0;
while (y < HEIGHT) {
int x = 0;
while (x < WIDTH) {
if (y == 0 || y == HEIGHT - 1 || x == 0 || x == WIDTH - 1) putchar('#'); // Рамка
else if (is_score_pixel(x, y, sl, sr)) putchar('#'); // Пиксели счёта
else if (x == 2 && y >= lp && y < lp + PADDLE_SIZE) putchar('|'); // Левая ракетка
else if (x == WIDTH - 3 && y >= rp && y < rp + PADDLE_SIZE) putchar('|'); // Правая ракетка
else if (x == bx && y == by) putchar('o'); // Мяч
else if (x == WIDTH / 2) putchar('|'); // Центральная сетка
else putchar(' '); // Пустое поле
x = x + 1;
}
putchar('\n');
y = y + 1;
}
printf("Controls: Left[a/z], Right[k/m], q=quit. One key per frame.\n");
fflush(stdout); // Важно: для «капризных» консолей — выталкиваем кадр сейчас же.
}
// Блокирующее чтение «клавиши» (в каноническом режиме нужно нажимать Enter).
static int read_key_blocking(void) {
int c = getchar();
if (c == EOF) return '\n';
return c;
}
// Экран победы + ожидание клавиши.
static void show_win_and_wait(int winner) {
printf("\n\n========================\n");
if (winner == 1) printf(" PLAYER 1 WINS! (21)\n");
else printf(" PLAYER 2 WINS! (21)\n");
printf(" Press any key to restart...\n");
printf("========================\n");
fflush(stdout);
(void)getchar();
}
// ----------------------- Контракт фронт↔бэк (структура и API) ------------------------
typedef struct GameState {
int score_player_1;
int score_player_2;
int pointX;
int pointY;
int speed_point_x;
int speed_point_y;
int fPr;
int sPr;
int game_running;
} GameState;
static void backend_init(GameState* s);
static void backend_apply_input(GameState* s, int key);
static void backend_step(GameState* s);
static int backend_is_running(const GameState* s);
static int backend_has_winner(const GameState* s);
static int backend_winner(const GameState* s);
static void backend_on_restart(GameState* s, int last_winner);
// ----------------------- ДЕМО‑БЭКЭНД (логика для запуска из коробки) -----------------
static int clamp_paddle_backend(int top) {
if (top < 1) return 1;
if (top > HEIGHT - 1 - PADDLE_SIZE) return HEIGHT - 1 - PADDLE_SIZE;
return top;
}
static void reset_ball_backend(GameState* s, int dir_x) {
s->pointX = WIDTH / 2;
s->pointY = HEIGHT / 2;
s->speed_point_x = dir_x;
s->speed_point_y = (dir_x > 0 ? 1 : -1);
if (s->speed_point_y == 0) s->speed_point_y = 1;
}
static void backend_init(GameState* s) {
s->score_player_1 = 0; s->score_player_2 = 0;
s->pointX = WIDTH / 2; s->pointY = HEIGHT / 2;
s->speed_point_x = -1; s->speed_point_y = 1;
s->fPr = (HEIGHT - PADDLE_SIZE) / 2; s->sPr = (HEIGHT - PADDLE_SIZE) / 2;
s->game_running = 1;
}
static void backend_apply_input(GameState* s, int key) {
if (key == 'a' || key == 'A') s->fPr = clamp_paddle_backend(s->fPr - 1);
else if (key == 'z' || key == 'Z') s->fPr = clamp_paddle_backend(s->fPr + 1);
else if (key == 'k' || key == 'K') s->sPr = clamp_paddle_backend(s->sPr - 1);
else if (key == 'm' || key == 'M') s->sPr = clamp_paddle_backend(s->sPr + 1);
else { /* no-op */ }
}
static void backend_step(GameState* s) {
int nx = s->pointX + s->speed_point_x;
int ny = s->pointY + s->speed_point_y;
if (ny <= 1) { ny = 1; s->speed_point_y = -s->speed_point_y; }
else if (ny >= HEIGHT - 2) { ny = HEIGHT - 2; s->speed_point_y = -s->speed_point_y; }
if (nx == 3) {
if (ny >= s->fPr && ny < s->fPr + PADDLE_SIZE) {
nx = 3; s->speed_point_x = -s->speed_point_x;
if (ny == s->fPr) s->speed_point_y = -1; else if (ny == s->fPr + 1) s->speed_point_y = 0; else s->speed_point_y = 1;
if (s->speed_point_y == 0) s->speed_point_y = (ny <= HEIGHT / 2 ? -1 : 1);
}
}
if (nx == WIDTH - 4) {
if (ny >= s->sPr && ny < s->sPr + PADDLE_SIZE) {
nx = WIDTH - 4; s->speed_point_x = -s->speed_point_x;
if (ny == s->sPr) s->speed_point_y = -1; else if (ny == s->sPr + 1) s->speed_point_y = 0; else s->speed_point_y = 1;
if (s->speed_point_y == 0) s->speed_point_y = (ny <= HEIGHT / 2 ? -1 : 1);
}
}
if (nx <= 0) { s->score_player_2 = s->score_player_2 + 1; reset_ball_backend(s, -1); return; }
if (nx >= WIDTH - 1) { s->score_player_1 = s->score_player_1 + 1; reset_ball_backend(s, +1); return; }
s->pointX = nx; s->pointY = ny;
}
static int backend_is_running(const GameState* s) { return s->game_running; }
static int backend_has_winner(const GameState* s) { if (s->score_player_1 >= 21) return 1; if (s->score_player_2 >= 21) return 1; return 0; }
static int backend_winner(const GameState* s) { if (s->score_player_1 >= 21) return 1; if (s->score_player_2 >= 21) return 2; return 0; }
static void backend_on_restart(GameState* s, int last_winner) {
s->score_player_1 = 0; s->score_player_2 = 0;
s->fPr = (HEIGHT - PADDLE_SIZE) / 2; s->sPr = (HEIGHT - PADDLE_SIZE) / 2;
if (last_winner == 1) reset_ball_backend(s, +1);
else if (last_winner == 2) reset_ball_backend(s, -1);
else reset_ball_backend(s, -1);
s->game_running = 1;
}
// ----------------------- Main: склейка фронта и бэка ---------------------------------
int main(void) {
// Важно для macOS/IDE: отключаем буферизацию stdout, чтобы кадры появлялись мгновенно.
setvbuf(stdout, NULL, _IONBF, 0);
GameState state;
backend_init(&state);
// Рисуем стартовый кадр сразу — до первого чтения ввода.
draw_frame(state.score_player_1, state.score_player_2, state.pointX, state.pointY,
state.fPr, state.sPr, state.game_running);
while (backend_is_running(&state)) {
// Экран победы / рестарт — проверяем до чтения клавиши, но после первичной отрисовки.
if (backend_has_winner(&state)) {
int w = backend_winner(&state);
if (w == 1 || w == 2) { show_win_and_wait(w); backend_on_restart(&state, w); draw_frame(state.score_player_1, state.score_player_2, state.pointX, state.pointY, state.fPr, state.sPr, state.game_running); }
}
// Читаем одну «клавишу» (в обычном режиме — клавиша + Enter).
{
int key = read_key_blocking();
if (key == 'q' || key == 'Q') { state.game_running = 0; break; }
backend_apply_input(&state, key);
}
// Физика → отрисовка следующего кадра.
backend_step(&state);
draw_frame(state.score_player_1, state.score_player_2, state.pointX, state.pointY,
state.fPr, state.sPr, state.game_running);
}