Тестовое в Firefly Studios или игра за час

a6b89c9c9382a7cfb055f3e8d36268db.jpg

Пару недель назад, на меня вышла HR «светлячков» и пригласила поговорить о позиции AI программера в их новую старую игру. Я был несколько удивлен, потому что знаком с парой ребят из студии и знаю, что найм у них сейчас остановлен. Но всегда интересно пообщаться с умными людьми, поэтому отказываться не стал. Первый созвон с HR вышел стандартный, где над чем работал, какие игры шипнул. Не очень понимаю зачем все это было спрашивать, если все это есть на линкедине подробно и с датами. Ну да ладно — видно такая их эйчарская доля по тридцать три раза переспрашивать. Или HR дальше второй страницы просто поленилась почитать.

Забукали время технического интервью. В полдень четверга на встречу приходит сотрудник студии и начинает просматривать резюмешку дальше второй страницы, где натыкается на скрин опенсорсного проекта StoneKingdoms, в который я некоторое время активно комитил. Проект, если что, получил благословение самого Simon Bradbury (владельца студии), так что проблем с правами на использование ресурсов из Stronghold нет. Посыпались вопросы, а что за проект?, а как делаете? и что все на lua?, а как же плюсы? Где-то на середине разговора, к нам подключился другой разработчик «светлячков», с которым мое знакомство началось еще в 2010, когда он помогал восстанавливать исходники Caesar III и просто давал консультации как реализована игровая симуляция. Мы и сейчас иногда общаемся на форуме по ремейкам старых игр.

Есть у этого человека одна особенность — если затронуть проекты где он участвовал, то следующие полчаса все будут слушать байки и истории как этот проект делали. А рассказывает он интересно и с огоньком, бывало и парой часов все не заканчивалось. Мы как раз обсуждали реализацию теней на изометрической карте. Надо был видеть выражение лица интервьюера, когда присоединившийся поздоровался со мной по имени и спросил как продвигается работа над Фараоном. А потом мы слушали рассказ как тени были реализованы в первом Stronghold’e, технические вопросы как-то отошли в сторону. Чуть позже разговор и вовсе перешел на обсуждение особенностей устройства игр времен Древнего Рима и пятой династии Египта.

Про опенсорс Stronghold: я туда уже не комичу, но если у кого будет время обязательно посмотрите, там много чего восстановили из оригинальной игры (https://gitlab.com/stone-kingdoms/stone-kingdoms)

The stock pile is full. My lord!

The stock pile is full. My lord!

А это оригинал, как говорится - найдите пять отличий

А это оригинал, как говорится — найдите пять отличий

Тот час, что был отведен на интервью пролетел как будто и не было его. Под завершение разговора интервьювер спросил в шутку соглашусь ли на тестовое, я также в шутку согласился. Понятно, что смысла в нем никакого не было, но меня всегда интересовали разные мозголомные задания, которые студии придумывают для потенциальных сотрудников. Итак, за второй час собеса на лайв кодинге надо было написать игру, рассказывая о принятых решениях по ходу создания игры. Но не просто написать — за каждую лишнюю строку после 150-ой отнимался балл, за каждую пустую строчку кода перед 150-ой — соответственно добавлялся балл. Как потом сказали мои знакомые, рекорд был 85 строк рабочего арканоида и читабельного кода. За каждую дополнительную игровую фичу вроде жизней или очков, еще плюс 10 баллов. За нечитаемый или непонятный код также снимают баллы, максимум могут снять 30.

За час надо было сделать играбельный арканоид.

Вот такое говорят люди пишут за 5 наносекунд

Вот такое говорят люди пишут за 5 наносекунд

Но не подумайте, что там совсем уж звери сидят с нуля писать игру. Даже такую простую. Каркас приложения уже написан, надо накидать своей логики, и как я сказал выше, постараться уместить в 150 строк кода. Я не очень быстро пишу код, и зачастую мне нужно время чтобы подумать над решением, даже если оно очевидное или где-то уже было сделано в прошлых проектах. Поэтому для меня это было действительно непростой задачей, в плане успеть по времени.

#include 

const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;

const int BRICK_WIDTH = 48;
const int BRICK_HEIGHT = 20;

struct Brick{
    SDL_Rect rect;
} ;

struct Ball {
    SDL_Rect rect;
} ;

struct Paddle {
    SDL_Rect rrect;
} ;

struct Game {
    Brick bricks[16][10];
    Ball ball;
    Paddle paddle;
};

int main(int argc, char* args[]) {
    Game game;

    SDL_Window* window = NULL;
    SDL_Surface* screenSurface = SDL_GetWindowSurface(window);

    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
    window = SDL_CreateWindow( "Strongout", 50, 50, 
                              SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
    bool quit = false;
    while (!quit) {
        SDL_Event event;
        while (SDL_PollEvent(&event)){
            if( event.type == SDL_QUIT){
                quit = true;
                break;
            }

            if( event.type == SDL_KEYDOWN ){
            }

            if( event.type == SDL_KEYUP ){
            } 
        }
        SDL_FillRect(screenSurface, NULL, 
                     SDL_MapRGB( screenSurface->format, 0, 0, 0));
        // Your code here
        SDL_UpdateWindowSurface(window);
    }

    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

Первым делом была добавлена отрисовка кирпичей. Прошу не ругать сильно за код, в условиях ограниченного времени и необходимости отладить функционал думать об архитектуре очень сложно, поэтому писал просто и топорно, чтобы можно было быстро итерироваться и отлаживать.

void drawLiveBricks(SDL_Surface* screen, SDL_Window* window, Game &game) {
    for (int i = 0; i < 16; i++) {
        for (int j = 0; j < 10; j++){
            if (game.bricks[i][j].is_alive) {
                SDL_FillRect(screen, &game.bricks[i][j].rect, 
                             SDL_MapRGB( screen->format, 255, 0, 0));
            }
        }
    }
}

acdf62ceac9340f8718f665e966badd5.png

Потом настал черед планки и шарика

SDL_FillRect(screenSurface, &game.paddle.rect, SDL_MapRGB( screenSurface->format, 0, 255, 0));
SDL_FillRect(screenSurface, &game.ball.rect, SDL_MapRGB( screenSurface->format, 255, 255, 255));

af41943e50b35ce231d1497bb5ed6363.png

Теперь заставим планку перемещатьcя при нажатии кнопок A/D. Velocity нужна, чтобы передать направление в планку.

struct Paddle {
    SDL_Point velocity;
    SDL_Rect area;

    Paddle() {
        area = {350, 550, 100, 20};
        velocity = {0, 0, 0, 0};
    }

    void move(SDL_Keycode key) {
        if (key == SDLK_a){
            velocity.x = -1;
            if (area.x <= 0) {
                velocity.x = 0;
                area.x = 0;
            }
        }
        if (key == SDLK_d){
            velocity.x = 1;
            if (area.x + area.w >= SCREEN_WIDTH) {
                velocity.x = 0;
                area.x = SCREEN_WIDTH - area.w;
            }
        }
    }

    void update() {
        area.x += velocity.x;
        if (area.x <= 0) {
            area.x = 0;
        }
        if (area.x + area.w >= SCREEN_WIDTH) {
            area.x = SCREEN_WIDTH - area.w;
        }
    }

};

И конечно же апдейт перемещения шарика, пусть в данном приближении наш шарик выглядит как квадрат. Да простят меня боги хорошего кода, я прокинул обсчет столкновений со стенками и планкой прямо в апдейт шарика.

    bool update(const Paddle &paddle) {
        if (SDL_GetTicks() % 2 == 1) { return true; }
        velocity.x *= (position.x <= 0 || position.x >= SW.x - position.w) ? -1 : 1;
        velocity.y *= (position.y <= 0) ? -1 : 1;
        if (position.y >= SW.y - position.h) {
            velocity = {0, 0};
            position = {395, 295, 10, 10};
            return false;
        }
        velocity.y *= (position.y == (paddle.y) && (position.x >= paddle.x) && (position.x <= paddle.x + paddle.w)) ? -1 : 1;
        position.x += velocity.x;
        position.y += velocity.y;
        (SDL_Point&)r = (SDL_Point&)position;
        return true;
    }

Осталось сделать разбивание блоков. Не придумалось ничего лучше, как просто смотреть пересечение всех блоков с шариком после апдейта мува. Даже если получится ситуация, что шарик будет внутри блока, то все равно получаем его разбитие.

        for (int i = 0; i < 160; i++) {
            if (SDL_HasIntersection(&game.ball.r, &game.bricks[i].r) && game.bricks[i].is_alive) {
                game.ball.position.y += 2;
                game.ball.velocity.y *= -1;
                game.bricks[i].is_alive = false;
            }
        }

На все это плюс отладочные запуски ушло примерно 30 минут, плюс приходилось комментировать свои действия, что тоже оказалось довольно сложно параллельно с написанием кода. В итоге получился вот такой код:

Strongout/146 lines

сonst int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;

const int BRICK_WIDTH = 48;
const int BRICK_HEIGHT = 20;

using p = SDL_Rect; using v = p;

struct Brick {
    p r;
    bool is_alive;
} ;

struct Paddle {
    v velocity = {0, 0, 0, 0};
    p area = {350, 550, 100, 20};

    void move(SDL_Keycode key) {
        if (key == SDLK_a){
            velocity.x = -1;
            if (area.x <= 0) {
                velocity.x = 0;
                area.x = 0;
            }
        }
        if (key == SDLK_d){
            velocity.x = 1;
            if (area.x + area.w >= SCREEN_WIDTH) {
                velocity.x = 0;
                area.x = SCREEN_WIDTH - area.w;
            }
        }
    }

    void update() {
        area.x += velocity.x;
        if (area.x <= 0) {
            area.x = 0;
        }
        if (area.x + area.w >= SCREEN_WIDTH) {
            area.x = SCREEN_WIDTH - area.w;
        }
    }
};

struct Ball {
    v velocity = {0, 0, 0, 0};
    p position = {395, 295, 10, 10};
    p r = position;

    void start(SDL_Keycode key) {
        if (key == SDLK_SPACE && velocity.x == 0 && velocity.y == 0)
            velocity = {1, 1};
    }

    bool update(const Paddle &paddle) {
        if (SDL_GetTicks() % 2 == 0) {
            if (position.x <= 0 || position.x >= SCREEN_WIDTH - position.w) {
                velocity.x *= -1;
            }
            if (position.y <= 0) {
                velocity.y *= -1;
            }

            if (position.y >= SCREEN_HEIGHT - position.h) {
                velocity = {0, 0};
                position = {395, 295, 10, 10};
                return false;
            }
            if (position.y == (paddle.area.y)) {
                if ((position.x >= paddle.area.x) && (position.x <= paddle.area.x + paddle.area.w)) {
                    velocity.y *= -1;
                }
            }

            position.x += velocity.x;
            position.y += velocity.y;
            (SDL_Point&)r = (SDL_Point&)position;
            return true;
        }
    }
};

struct Game {
    std::array bricks;
    Ball ball;
    Paddle paddle;

    Game() {
        for (auto &brick: bricks) {
            int i = std::distance(bricks.data(), &brick);
            brick.r = {1 + ((i % 16) * (BRICK_WIDTH + 2)), 1 + ((i / 16) * (BRICK_HEIGHT + 2)), BRICK_WIDTH, BRICK_HEIGHT};
            brick.is_alive = true;   
        }
    }
};

int main(int argc, char* args[]) {
    Game game;
    bool quit = false;
    SDL_Window* window = NULL;
    SDL_Surface* screenSurface = NULL;
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
    window = SDL_CreateWindow("Strongout", 50, 50, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
    while (!quit) {
        SDL_Event event;
        while (SDL_PollEvent(&event)){
            if( event.type == SDL_QUIT){
                quit = true;
                break;
            }
            if( event.type == SDL_KEYDOWN ){
                game.paddle.move(event.key.keysym.sym);
                game.ball.start(event.key.keysym.sym);
            }
            if( event.type == SDL_KEYUP ){
                game.paddle.velocity.x = 0;
            } 
        }
        if (game.lives > 0) {
            game.paddle.update();
            game.ball.update(game.paddle);
        }
        int numBricks = 160;
        for (int i = 0; i < numBricks; i++) {
            if (SDL_HasIntersection(&game.ball.r, &game.bricks[i].r) && game.bricks[i].is_alive) {
                game.ball.position.y += 2;
                game.ball.velocity.y *= -1;
                game.bricks[i].is_alive = false;
            }
        }
        screenSurface = SDL_GetWindowSurface(window);
        SDL_FillRect(screenSurface, NULL, SDL_MapRGB( screenSurface->format, 0, 0, 0));
        for (auto &brick: game.bricks) {
            if (brick.is_alive) {
                SDL_FillRect(screenSurface, &brick.r, SDL_MapRGB( screenSurface->format, 255, 0, 0));
            }
        }
        SDL_FillRect(screenSurface, &game.paddle.area, SDL_MapRGB( screenSurface->format, 0, 255, 0));
        SDL_FillRect(screenSurface, &game.ball.r, SDL_MapRGB( screenSurface->format, 255, 255, 255));
        SDL_UpdateWindowSurface(window);
    }
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

Вот что я смог накропать за полчаса

Вот что я смог накропать за полчаса

Но как видите, будет мало баллов по шкале арканоидов за написанный код. Кстати я заметил, что компании стали менее охотно давать задания на оффлайн выполнение, видимо это связано с невозможностью проверить будет ли оно выполнено сеткой или человек сделает его сам. Возвращаясь к коду арканоида, первое что пришло на ум — это поубирать все лишние строки и свернуть часть скобок и функций, чтобы получить побольше баллов.

Strongout/132 lines

сonst int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;
const int BRICK_WIDTH = 48;
const int BRICK_HEIGHT = 20;
using p = SDL_Rect; using v = p;
struct Brick {
    p r;
    bool is_alive;
} ;
struct Paddle {
    v velocity = {0, 0, 0, 0};
    p area = {350, 550, 100, 20};
    void move(SDL_Keycode key) {
        if (key == SDLK_a){
            velocity.x = -1;
            if (area.x <= 0) {
                velocity.x = 0;
                area.x = 0;
            }
        }
        if (key == SDLK_d){
            velocity.x = 1;
            if (area.x + area.w >= SCREEN_WIDTH) {
                velocity.x = 0;
                area.x = SCREEN_WIDTH - area.w;
            }
        }
    }
    void update() {
        area.x += velocity.x;
        if (area.x <= 0) {
            area.x = 0;
        }
        if (area.x + area.w >= SCREEN_WIDTH) {
            area.x = SCREEN_WIDTH - area.w;
        }
    }
};
struct Ball {
    v velocity = {0, 0, 0, 0};
    p position = {395, 295, 10, 10};
    p r = position;
    void start(SDL_Keycode key) {
        if (key == SDLK_SPACE && velocity.x == 0 && velocity.y == 0)
            velocity = {1, 1};
    }
    bool update(const Paddle &paddle) {
        if (SDL_GetTicks() % 2 == 0) {
            if (position.x <= 0 || position.x >= SCREEN_WIDTH - position.w) {
                velocity.x *= -1;
            }
            if (position.y <= 0) {
                velocity.y *= -1;
            }
            if (position.y >= SCREEN_HEIGHT - position.h) {
                velocity = {0, 0};
                position = {395, 295, 10, 10};
                return false;
            }
            if (position.y == (paddle.area.y)) {
                if ((position.x >= paddle.area.x) && (position.x <= paddle.area.x + paddle.area.w)) {
                    velocity.y *= -1;
                }
            }
            position.x += velocity.x;
            position.y += velocity.y;
            (SDL_Point&)r = (SDL_Point&)position;
            return true;
        }
    }
};
struct Game {
    std::array bricks;
    Ball ball;
    Paddle paddle;
    Game() {
        for (auto &brick: bricks) {
            int i = std::distance(bricks.data(), &brick);
            brick.r = {1 + ((i % 16) * (BRICK_WIDTH + 2)), 1 + ((i / 16) * (BRICK_HEIGHT + 2)), BRICK_WIDTH, BRICK_HEIGHT};
            brick.is_alive = true;   
        }
    }
};
int main(int argc, char* args[]) {
    Game game;
    bool quit = false;
    SDL_Window* window = NULL;
    SDL_Surface* screenSurface = NULL;
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
    window = SDL_CreateWindow("Strongout", 50, 50, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
    while (!quit) {
        SDL_Event event;
        while (SDL_PollEvent(&event)){
            if( event.type == SDL_QUIT){
                quit = true;
                break;
            }
            if( event.type == SDL_KEYDOWN ){
                game.paddle.move(event.key.keysym.sym);
                game.ball.start(event.key.keysym.sym);
            }
            if( event.type == SDL_KEYUP ){
                game.paddle.velocity.x = 0;
            } 
        }
        if (game.lives > 0) {
            game.paddle.update();
            game.ball.update(game.paddle);
        }
        int numBricks = 160;
        for (int i = 0; i < numBricks; i++) {
            if (SDL_HasIntersection(&game.ball.r, &game.bricks[i].r) && game.bricks[i].is_alive) {
                game.ball.position.y += 2;
                game.ball.velocity.y *= -1;
                game.bricks[i].is_alive = false;
            }
        }
        screenSurface = SDL_GetWindowSurface(window);
        SDL_FillRect(screenSurface, NULL, SDL_MapRGB( screenSurface->format, 0, 0, 0));
        for (auto &brick: game.bricks) {
            if (brick.is_alive) {
                SDL_FillRect(screenSurface, &brick.r, SDL_MapRGB( screenSurface->format, 255, 0, 0));
            }
        }
        SDL_FillRect(screenSurface, &game.paddle.area, SDL_MapRGB( screenSurface->format, 0, 255, 0));
        SDL_FillRect(screenSurface, &game.ball.r, SDL_MapRGB( screenSurface->format, 255, 255, 255));
        SDL_UpdateWindowSurface(window);
    }
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

Получилось в итоге 132 строки. Убирать больше особо нечего, до границы в 100 строк дальше, чем до Эдинбурга. Тут я понимаю, что надо либо кардинально сворачивать код, что конечно же снижает читаемость и ведет к штрафным баллам, либо добивать игру фичами. За пять минут получилось набросать отображение жизней для шарика и логику их убывания если шарик оказался под планкой. Хотел еще добавить отображения очков, но пришлось бы тащить отображение шрифтов, для 10 оставшихся минут задача непосильная. В итоге сосредоточился на сворачивании кода уже не особо заботясь о читабельности.

57f32ed89a40e1d816be3c736f1e0ee4.png

Получился все еще читаемый код, но уже ближе к сотне строк. Спрашиваю у собеседника, во сколько уложился он — оказалось в 90 строк.

Strongout/100 lines

using p = SDL_Rect; using v = p;
const p SW{800, 600, 48, 20};
struct Brick : public p { bool is_alive = true; };
struct Paddle : public p {
    v velocity = {0, 0, 0, 0};
    void move(SDL_Keycode key) {
        if (key == SDLK_a){
            velocity.x = -1;
            if (x <= 0) { velocity.x = x = 0; }
        }
        if (key == SDLK_d){
            velocity.x = 1;
            if (x + w >= SW.x) {
                velocity.x = 0;
                x = SW.x - w;
            }
        }
    }
    void update() {
        x = std::max(x += velocity.x, 0);
        if (x + w >= SW.x) { x = SW.x- w; }
    }
};
struct Ball {
    v velocity = {0, 0, 0, 0};
    p position = {395, 295, 10, 10};
    p r = position;
    void start(SDL_Keycode key) {
        if (key == SDLK_SPACE && velocity.x == 0 && velocity.y == 0)
            velocity = {1, 1};
    }
    bool update(const Paddle &paddle) {
        if (SDL_GetTicks() % 2 == 1) { return true; }
        velocity.x *= (position.x <= 0 || position.x >= SW.x - position.w) ? -1 : 1;
        velocity.y *= (position.y <= 0) ? -1 : 1;
        if (position.y >= SW.y - position.h) {
            velocity = {0, 0};
            position = {395, 295, 10, 10};
            return false;
        }
        velocity.y *= (position.y == (paddle.y) && (position.x >= paddle.x) && (position.x <= paddle.x + paddle.w)) ? -1 : 1;
        position.x += velocity.x;
        position.y += velocity.y;
        (SDL_Point&)r = (SDL_Point&)position;
        return true;
    }
};
int main(int argc, char* args[]) {
    std::array bricks;
    Ball ball;
    Paddle paddle = {350, 550, 100, 20};
    int lives = 3;
    bool quit = false;
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
    auto window = SDL_CreateWindow("Strongout", 50, 50, SW.x, SW.y, SDL_WINDOW_SHOWN );
    for (auto &brick: bricks) {
        int i = std::distance(bricks.data(), &brick);
        brick = {1 + ((i % 16) * (SW.w + 2)), 1 + ((i / 16) * (SW.h + 2)), SW.w, SW.h};
    }
    while (!quit) {
        SDL_Event event;
        while (SDL_PollEvent(&event)){
            if(event.type == SDL_QUIT){
                quit = true;
                break;
            }
            if(event.type == SDL_KEYDOWN){
                paddle.move(event.key.keysym.sym);
                ball.start(event.key.keysym.sym);
            }
            if (event.type == SDL_KEYUP) { paddle.velocity.x = 0; } 
        }
        if (lives > 0) {
            paddle.update();
            bool alive = ball.update(paddle);
            lives -= alive ? 0 : 1;
        }
        for (auto &brick: bricks) {
            if (SDL_HasIntersection(&ball.r, &brick) && brick.is_alive) {
                ball.position.y += 2;
                ball.velocity.y *= -1;
                brick.is_alive = false;
            }
        }
        auto screenSurface = SDL_GetWindowSurface(window);
        SDL_FillRect(screenSurface, NULL, SDL_MapRGB( screenSurface->format, 0, 0, 0));
        for (auto &brick: bricks) {
            brick.is_alive && SDL_FillRect(screenSurface, &brick, SDL_MapRGB( screenSurface->format, 255, 0, 0));
        }
        SDL_FillRect(screenSurface, &paddle, SDL_MapRGB( screenSurface->format, 0, 255, 0));
        SDL_FillRect(screenSurface, &ball.r, SDL_MapRGB( screenSurface->format, 255, 255, 255));
        for (int i = 0; i < lives; i++) {
            SDL_Rect rrect{5 + (i * 15), (SW.y - 15), 10, 10};
            SDL_FillRect(screenSurface, &rrect, SDL_MapRGB( screenSurface->format, 255, 255, 255));
        }
        SDL_UpdateWindowSurface(window);
    }
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

Чем я хуже потомственного британца? Свернуть можно все, разворачивать потом будет больно. Оставшиеся 10 минут я потратил на «художества», кодом получившийся результат назвать язык не поворачивается, но оно работает. Заменил все возможные if на тернарники и операции логического AND. Свернул получившиеся однострочные функции в одну строку. Подменил, где можно, функции на операторы выполнения, и константы на прямые значения. И «это» все еще оставалось относительно читаемым. Рекорд британца по шкале арканодов я побил.

Встречайте, Breakfunoid в 32 строчках.

using P = SDL_Rect; struct Brick : public P { bool a = true; }; auto FillRect = &SDL_FillRect; auto MapRGB = &SDL_MapRGB;
struct Paddle : public P { P v{0, 0, 0, 0};
    void operator()() { x = std::max(x += v.x, 0); (x + w >= 800) && (x = 800 - w); }
    void operator()(SDL_Keycode k) { (v.x = (k == 97) ? -1 : v.x); (k == 97) && (x <= 0) && (v.x = x = 0); (v.x = k == 100 ? 1 : v.x); (k == 100) && (x + w >= 800) && (x = 800 - w) && (v.x = 0); }
};
struct Ball : public P { P p{395, 295, 10, 10}, v{0, 0, 0, 0};
    void operator()(SDL_Keycode k) { if (k == SDLK_SPACE && v.x == 0 && v.y == 0) { v = {1, 1}; } }
    bool operator()(const Paddle &paddle) { if (!(SDL_GetTicks() % 2)) { return true; }
        v.x *= (p.x <= 0 || p.x >= 800 - p.w) ? -1 : 1; v.y *= (p.y <= 0) ? -1 : 1;
        if (p.y >= 600 - p.h) { v = {0, 0}; p = {395, 295, 10, 10}; return false; }
        v.y *= (p.y == (paddle.y) && (p.x >= paddle.x) && (p.x <= paddle.x + paddle.w)) ? -1 : 1;
        p.x += v.x; p.y += v.y;
        (SDL_Point &)*this = (SDL_Point &)p;
        return true;
    }
};
int main(int, char**) { SDL_Init(0x30);
    Brick bricks[160]; Ball ball{395, 295, 10, 10}; Paddle pad{350, 550, 100, 20}; int lives = 3; SDL_Event e;
    auto window = SDL_CreateWindow("brkt.cpp", 50, 50, 800, 600, 4); auto s = SDL_GetWindowSurface(window);  auto f = s->format; 
    for (int i = 0; i < 160; ++i) { *(bricks+i) = {1 + ((i % 16) * (48 + 2)), 1 + ((i / 16) * (20 + 2)), 48, 20}; }
    while (true) { 
        while (SDL_PollEvent(&e)) { auto t = e.type; auto sym = e.key.keysym.sym;
          switch (t) { case 256: return 0;
                       case 768: pad(sym); ball(sym); break;
                       case 769: pad.v.x = 0; break; }
        } if (lives > 0) { pad(); lives -= ball(pad) ? 0 : 1; }
        FillRect(s, 0, MapRGB( f, 0, 0, 0)); FillRect(s, &pad, MapRGB( f, 0, -1, 0)); FillRect(s, &ball, MapRGB( f, -1, -1, -1));
        for (auto &br: bricks) { SDL_HasIntersection(&ball, &br) && br.a && (ball.p.y += 2) && (ball.v.y *= -1) && (br.a = 0); br.a && FillRect(s, &br, MapRGB(f, -1, 0, 0)); }
        for (int i = 0; i < lives; i++) { P r{5 + (i * 15), 585, 10, 10}; FillRect(s, &r, MapRGB(f, -1, -1, -1)); }
        SDL_UpdateWindowSurface(window);
    } return 0;
}

В итоге я получил 120 баллов за количество строк плюс 10 за фичу с жизнями и минус 30 (максимум) за читаемость кода. Думаю, они подымут максимальный штраф за читаемость после этого случая.

В общем посмеялись — разошлись.

Но это еще не конец истории, дома меня не отпускала мысль что можно свернуть и больше. Я точно видел код рендера на визитке.

abe9bb5a3d2ab464200f6a0838fabfd1.png

Пускай будет код арканоида на визитке. Поколдовав еще немного за ноутом получилось что-то в этом стиле. Нечитабельно, непрактично, отладить не выйдет, но прикольно и работает.

#include 
#define RE return
#define OO operator
using R=SDL_Rect;using P=SDL_Point;struct B:public R{bool a=1;};auto $= &SDL_FillRect;
using I=int;I i=0;struct Pad:public R{R v{0}; void OO()(){x=std::max(x+=v.x,0);(x+w >=
800)&&(x=800-w);} void OO()(I k){(v.x=(k==97)?-1:v.x);(k==97)&&(x<=0)&&(v.x=x=0);(v.x=
k==100?1:v.x);(k==100)&&(x+w>=800)&&(x=800-w)&&(v.x=0);}};struct BL:public R{R p {395,
295,10,10},v{0};void OO()(I k){if(k==32&&!v.x&&!v.y){v={1,1};}}bool OO()(Pad &pd){if(!
(SDL_GetTicks()%2)){RE 1;}v.x*=(p.x<=0||p.x>=800-p.w)?-1:1;v.y*=(p.y<=0)?-1:1;if(p.y>=
600-p.h){v={0};p={395,295,10,10};RE 0;}v.y *=(p.y==(pd.y)&& (p.x>=pd.x)&&(p.x<=pd.x+pd
.w))?-1:1;p.x+=v.x;p.y+=v.y;(P&)*this=(P&)p;RE 1;}};I main(I,char**){SDL_Init(0x30); B
bricks[160];BL ball{395,295,0xA,10};Pad pad{350,550,100,20};I l=3;SDL_Event e; auto w=
SDL_CreateWindow("brk",50,50,800,600,4);auto s=SDL_GetWindowSurface(w); for(i=0;i<160;
++i){*(bricks+i)={1+((i%16)*50),1+((i/16)*22),48,20};}while(1){while(SDL_PollEvent(&e)
){I s=e.key.keysym.sym;switch(e.type){case 256:return 0;case 768:pad(s);ball(s);break;
case 769:pad.v.x=0;break;}}if(l>0){pad();l-=ball(pad)?0:1;}$(s,0,0xff000000);$(s,&pad,
0xff00ff00); $(s,&ball,0xffffffff); for(auto&br:bricks){SDL_HasIntersection(&ball,&br)
&&br.a&&(ball.p.y+=2)&&(ball.v.y*=-1)&&(br.a=0);br.a &&$(s,&br,0xffff0000);}for(i=0;i<
l;i++){R r{5+(i*15),585,10,10};$(s,&r,-1);}SDL_UpdateWindowSurface(w);} RE 0;}

https://godbolt.org/z/896s6j883

Подумываю сделать себе такую

Подумываю сделать себе такую

Всех с наступающим!

З.Ы. NDA на тестовое я никакой не подписывал, да и про это задание к «светлячкам» было известно, потому что они его раздавали на игровых конференциях, сами понимаете для чего :) Традиция

© Habrahabr.ru