В стиле ретро: 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-приложения язык, в том числе и для устаревших платформ.

ссылка на оригинал статьи http://habrahabr.ru/post/206834/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *