Abnormal programming
Developmend for Java ME
December 2013 23

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

+4
5.8k 7
Comments 4
Top of the day