...

понедельник, 23 декабря 2013 г.

В стиле ретро: J2ME на TCL



После того, как я не смог ответить на звонок в дочкином телефоне, я решил что что-то надо сделать. Специалисты утверждают, что еще не все потеряно и с помощью специальных технологий можно не отстать от подрастающего поколения. Одним из таких средств является N-Back. Так как с сотовым телефоном с точскрином я не справлюсь (замкнутый круг получается), я попытался найти такое приложение под J2ME. Не нашел и решил написать сам. Но вот проблема — Scala и Clojure не поддерживают J2ME, а выучить Java не потренировавшись на еще не написанной программе мне будет тяжело. После некоторого гугуления решение было найдено — HECL, слегка переработанный TCL.



Надо сказать, что программировал на TCL я очень давно — тогда я работал на компьютере SGI O2 и завидовал тем, кто могут играть в «lines» (они же «шарики»). К внешнему виду приложений я не очень притязателен, и с помощью TCL с библиотекой Tk эту проблему я решил.

TCL — простой императивный скриптовой язык, синтаксисом напоминающий Unix Shell (и иногда использовавшийся в его качестве). По расширяемости его можно сравнить с Fort и Lisp. Простота встраивания его в приложения на языке C сделало его популярным среди разработчиков САПР.

HECL унаследовал многие черты TCL, только интерируется с приложениями на Java, а не на C. Разработу упрощает наличие REPL, в отличие от классического tclsh поддерживающего историю команд и редактирование строки.

На сайте HECL есть готовый MIDlet с примерами и даже минимальной средой разработки. В jar-архиве есть файл script.hcl — достачно его подменить (не забыв подправить .jad), что бы запустить свой скрипт на J2ME-платформе.

И так, начнем. Нам надо получить случайный символ



set alph {A C G T}
set alphsize [llen $alph]

proc rand {} {
global alph alphsize
lindex $alph [* [random] $alphsize]
}




Присваивание переменной выполняется командой set. Имя изменяемой переменной пишется просто, как в Python, а при использовании надо добавлять '$', как в Perl. При желании можно написать

set aaa bbb
set $aaa ccc


и переменной bbb присвоется значение «ccc».

Строки, как и в Unix Shell, обычно не надо заключать в кавычки. Если все таки придется, кроме обычных двойных кавычек можно использовать сбалансированные фигурные скобки ({}) — строки могут быть «вложенными».

Строка разбивается по пробелам (получатся список) и первое слово трактуется как имя процедуры — здесь TCL немного напоминает Lisp. Вложенные в команду вызовы других команд заключаются в квадратные скобки ([]).

Команды + и * — нововведение HECL. В оригинальном TCL приходилось вызывать специальный DSL

expr 1 + 2 * 3

почти как в Shell (только * не надо ескейпить).
Немного психологии и тервера

В данной реализации N-Back используются символы из небольшого алфавита с равной вероятностью. Если увеличить алфавит, то совпадения станут очень редки и играть станет слишком скучно. Можно сделать генерацию символов более сложным марковским процессом (вероятность зависит от меняющегося состояния), который сделает совпадения достаточно частыми. Здесь есть еще один интересный момент — на сколько мозг умеет анализировать скрытые марковские модели и учитывать баесову вероятность при распознавании совпадений. Вместо символов можно использовать картинки (китайские иероглифы или химические формулы — совмещаем тренировку с заучиванием) или звуки.




set nback {aa aa}
set last ""

proc getnext {} {
global nback last
set nbach [lappend [lset $nback 0] $last]
set last [rand]
return $last
}

proc first {} {
global nback last
eq $last [lindex $nback 0]
}




N в названии N-Back задается начальной длиной списка. Мне N==2 хватает :-).

Начальные значения истории выбраны не совпадающими с символами алфавита для упрощения. Так как за красивостями я не гнался, первые N-1 символов, генерящихся для заполнения буфера, пойдут в счетчик угаданных.

set stats {0 0 0 0}
proc update k {
global stats
lset $stats $k [1+ [lindex $stats $k]]
set stats
}




Процедура подсчета статистики. 1+ — название функции прибавления единицы. В массиве stats хранятся число успешно замеченных несовпадений, ошибочно замеченных несовпадений, ошибочно замеченных совпадений и успешно замеченных совпадений. Такая последовательность была выбрана из удобства реализации, но оказалась удобной и для восприятия.

Следующий код я скаргокультил из оригинального script.hcl



proc PrintLn {g txt} {
global MIDL
$g clear
$g string [list 4 $MIDL] $txt nw
}

proc EventHandler {c e} {
global last MIDL
set reason [$e cget -reason]
if {!= 5 $reason} {return}
set MIDL [/ [$c cget -height] 2]
set keycode [$e cget -keycode]
set res [first]
set sym [getnext]

set g [$c graphics]
set k [eq 49 $keycode]
if {eq $k $res} {
PrintLn $g "[update [+ $res $res $k]] Ok $last"
} else {
PrintLn $g "[update [+ $res $res $k]] Fail $last"
}
}

set c [lcdui.canvas -title "N-Back" -fullscreen 1 -eventhandler EventHandler]
$c setcurrent




Здесь можно наблюдать объектно-ориентированные свойства TCL — объект маскируется под процедуру, сохраненную в переменной, а сообщения маркируются строковыми константами в аргументах. «Конструктор» lcdui.canvas создает «объект canvas» и вешает на него обработчик событий. Мой обработчик распознает события с причиной 5 (нажание на клавишу). Клавиша «1» обрабатывается как замеченное совпадение, остальные — как замеченное несовпадение. Не уверен, что выбор удачный, но экспериментировать лень — процедура загрузки этого на телефон самое сложное во всей разработке :-).

В общем HECL можно рекомендовать для быстрой разработки/прототипирования и как встраиваемый в Java-приложения язык, в том числе и для устаревших платформ.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Комментариев нет:

Отправить комментарий