Этот сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

Игра «Виселица» на bc

После того как я написал «бутылки пива» на bc, стало интересно попробовать написать на этом языке какую-нибудь игру. На этом попроще я не первый, есть, например, очень годный пасьянс, но попробовать сделать что-то своё тоже хочется.

В силу специфики обработки ввода bc, писать нужно что-то пошаговое — фоновое выполнение не поддерживается, программа может работать только от ввода до ввода. Долго не размышляя, я как-то сразу решил написать игру «Виселица». Я её уже писал под «Флиппер», если помните.

Игровой процесс игры «Веселица», написанной на bc

На пути к успеху сразу начались трудности. Несмотря на то, что в bc есть многообещающая функция read(), оказалось, что с её помощью можно вводить только выражения языка и числа. В вышеупомянутом «пасьянсе» автор обошёлся числами, а мне нужны буквы, иначе играть будет невозможно.

К счастью, «выражения» — это ещё и переменные. Если задать значения всем однобуквенным переменным, мы сможем определить какую именно букву нажал пользователь.

К тому же можно ещё сразу сделать таблицу, которая позволит нам рисовать клавиатуру, так как других способов вывести последовательно буквы в языке нет.

a=1;b=2;c=3;d=4;e=5;f=6;g=7;h=8;i=9;j=10;k=11;l=12;m=13;n=14;o=15;p=16;q=17;r=18;s=19;t=20
u=21;v=22;w=23;x=24;y=25;z=26

co[a]="a";co[b]="b";co[c]="c";co[d]="d";co[e]="e";co[f]="f";co[g]="g";co[h]="h";co[i]="i";co[j]="j"
co[k]="k";co[l]="l";co[m]="m";co[n]="n";co[o]="o";co[p]="p";co[q]="q";co[r]="r";co[s]="s";co[t]="t"
co[u]="u";co[v]="v";co[w]="w";co[x]="x";co[y]="y";co[z]="z"

exit = -1

Заодно я задал значение переменной exit, это слово пользователь может ввести, чтобы выйти.

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

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

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

define get_word(c) {
    if(!--c) return 4328588043
    if(!--c) return 4328588820
    /* и так далее */
    if(!--c) return 437194509

    return -c
}

word = get_word(irand(get_word(0)) + 1)

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

Функция irand, используемая тут, является расширением bc, у меня на «Маке» она поддерживается, но под «Линуксом» её нет, так же как и вообще любой другой функции для работы со случайными числами.

С такими «строками» можно уже как-то работать, например, считать длину, чтобы вывести подходящее количество «окошек», в которых будут появляться буквы:

define word_len(c) {
    scale=10
    return ceil(log(c, 256), 0)
}

Или вывести на печать:

define void print_word(c) {
    auto word, i, mod

    scale=0
    for (i = 0; c; i++) {
        c = divmod(c, 256, mod[])
        word[i] = mod[0]
    }

    while (i--) {
        print co[word[i]]
    }
}

В общем, всё как с обычными строками, только чуть-чуть очень сильно неудобно.

Остальное, в общем-то, писать было просто — логика игры очень простая, пишется за несколько минут. На результат можно посмотреть в моём репозитории на «Гитхабе».

В теории, игру можно портировать и на «Линукс», надо только избавиться от хранения строк в переменных, заменить генерацию случайных чисел на внешний генератор, заменить log() на l(), а divmod() на деление и взятие модуля и избавиться от length() — массив в качестве аргумента в «Линуксе» не поддерживается.