Игра «Виселица» на bc под «Линукс»
Я планировал заняться адаптацией «Виселицы» на bc под «Линукс» на следующий день, то есть сегодня, но не выдержал и вчера просидел над этим почти до полуночи. Линуксовый bc, конечно, гораздо скромнее по возножностям, было где поломать мозг, но больше всего времени я, кажется, потратил на рандомизацию.
Я вчера писал уже, что в документации для линуксового bc нет упоминаний функций для генерации случайных чисел, но чтение исходников показало, что документации не всегда можно верить. Функция есть, называется она random(), но не работает так как хочется — при вызове генерирует всегда одну и ту же последовательность.

Видимо, её забыли правильно проинициализировать. Непонятно почему за столько лет это нельзя исправить, ведь новые версии иногда выходят, правда в списке изменений последних версий сплошь какая-то косметика.
Так как никаких способов исправить ситуацию, не меняя исходный код bc, я не обнаружил, решил обойтись хаком — при запуске вызвать random() случайное количество раз, чтобы в коде моей программу его внутреннее состояние тоже стало случайным.
Для этого я изменил способ запуска программы — сделал его шелл-скриптом, который передаёт в bc на инициализации цикл, генерирующий случайное количество вызовов random():
#!/bin/bash
x=/*
BC_LINE_LENGTH=0 bc -l -q <(/usr/bin/seq -f "x=random()+%g" $RANDOM) "$0"
exit; */1
Обвязка с комментарием сделана так, чтобы и шелл, и bc не увидели в коде ничего странно и не выдали ошибку синтаксиса, а вся мякотка происходит в строке /usr/bin/seq -f "x=random()+%g" $RANDOM. Команда seq генерирует последовательность x=random()+число столько раз, сколько указано в переменной $RANDOM. $RANDOM — это специальная переменная шелла, которая содержит случайное значение.
Подстановка %g необходима, чтобы заработал шаблон в параметре команды seq, без этого она ругается на неправильный аргумент.
Ещё одной крупной проблемой стали строки — в этой версии bc не поддерживается присваивание их переменным. Из-за этого код пришлось сильно переделать.
Для вывода, вместо кучи переменных, пришлось сделать функцию, которая выводит букву по её коду через горку условий. Код получился похожим на то место, где я выбираю слово по номеру:
define void print_letter(c) {
if(!--c){"a"}
if(!--c){"b"}
if(!--c){"c"}
/* …тут все 26 букв */
if(!--c){"y"}
if(!--c){"z"}
}
Я не стал ставить return, чтобы сделать функцию более визуально компактной, тем более, что на производительность мне, по большому счёту, наплевать.
Кстати, ввод удалось сильно упростить, перейдя на тридцатишестиричную систему исчисления, где все английские буквы соответствую числам:
define read_letter() {
auto z, b
b = ibase
ibase = 36
z = read()
ibase = b
return z - 9
}
Переменная ibase отвечает за систему исчисления при вводе чисел.
Таким образом, численное исчисление получили не только все одиночные буквы, но и все слова. В теории, это можно использовать для ввода одиночных команд на английском языке. Я использую это свойство, чтобы распознать команду exit.
Остальные проблемы решались попроще. Ещё я немного поломал голову чем заменить ceil (округление вверх), потом догадался прибавить единицу и, выставив через специальную переменную scale нулевую точность для цифр после запятой, разделил получившееся значение на один, чтобы отбросить дробную часть:
define word_len(c) {
auto r
scale=10
r = l(c) / l(256) + 1
scale=0
return r/1
}
Функцию логарифма log() тут пришлось заменить на натуральный логарифм l() — это одна из немногих математических функций, которые поддерживаются в этой версии bc.
Остальные замены довольно тривиальны, я писал о них ещё в прошлый раз. Результат положил отдельным файлом в моём репозитории.