tclock.cをTinyCoreLinuxのシェル（ash）で再現してみた

Tiny Core Linuxでターミナル上で動くアナログ時計「[tclock.c](https://github.com/wereHamster/ncurses/blob/master/test/tclock.c)」を動かそう思うとライブラリをインストールしたりコンパイルが必要だったりして面倒そうだったので、Tiny Core LinuxのCoreのインストール直後でも動くシェル（ash）で再現してみようという話です。

## どんな画面になるの？
実行すると以下のような画面になります。以下の画面はTinyCoreですがインストール直後のTiny Core LinuxのCoreでも動きます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/128670/bcb1560c-07d8-18b7-97c5-1bd9cf8c76dd.png)

## 課題は？
課題は私自身がシェルになれていないことと、今まで当然のように使っていたコマンドがTiny Core Linuxやash上では使えなかったりしますので、それをどう置き換えるかかなと思います。

### 課題1. 画面の大きさの取得
`tput cols`や`tput lines`を使えば画面の大きさを取得できるのですが、素のTiny Core Linuxには`tput`コマンドが入っていません。代わりに`stty`コマンドを使って取得します。

```bash:sttyで画面の大きさを取得
echo $(stty size | awk '{print $2}') #cols
echo $(stty size | awk '{print $1}') #lines
```

### 課題2. カーソルの移動
`tput`コマンドなら`tput cup $y $x`のようにカーソル移動できるのですが、その方法は使えないので今回はエスケープシーケンスを使って実現しています。

```bash:
/bin/echo -n -e "\e[$(($y+1));$(($x+1))H"
```

### 課題3. 三角関数
tclockでは文字盤や針の座標を計算するのにsin、cosを使っています。`bc`コマンドに`-l`オプションをつければ`s(90)`や`c(90)`という風に計算できるのですが素のTiny Core Linuxには`bc`コマンドがインストールされていません。代わりに`awk`コマンドを使って計算します。

```bash:sin,cosをawkコマンドで計算
echo "90" | awk '{print sin($1)}' #sin関数
echo "90" | awk '{print cos($1)}' #cos関数
```

### 課題4. for
これまでforを書く時は`for ((i=0; i < 12; i++));`のように書いていたのですが、この書き方はbashでしかできないようです。以下のように書き換える事でどちらでも動くforが書けます。

```bash:ash+TinyCoreLinuxでも動くforの書き方
for i in $(seq 0 11);
```

## とりあえず動いたという感じ
今回は検証という位置づけなのと私のパワー不足ということもあって完全再現するには至りませんでした。tclock.cはpythonにも移植されていますので、そっちを使えば簡単だったかも…と思う今日このごろです。

## ソースコード
以下がソースコードです。Tiny Core LinuxとUbuntuのbashとshでの動作することを確認しています。

```bash:tclock.sh
#!/bin/sh

#see https://github.com/wereHamster/ncurses/blob/master/test/tclock.c

a2x() {
  echo "2.2 $1 $2" | awk '{print int($1*$3*sin($2)+0.5)}'
  return 0
}
a2y() {
  echo "$1 $2" | awk '{print int($2*cos($1)+0.5)}'
  return 0
}

sign() {
  [ $1 -lt 0 ] && echo -1
  [ $1 -ge 0 ] && echo 1
  return 0
}
plot() {
  str=$(echo -n $3 | sed 's/ /_/g')
  loc=$(($1+$2*$COLS+1))
  for i in $(seq 1 ${#str}); do
    ch=$(echo -n $str | cut -c $i)
    stdscr=$(echo -n $stdscr | sed -r "s/./$ch/$loc")
    loc=$((loc+1))
  done
  return 0
}
refresh() {
  /bin/echo -n -e "\e[1;1H$(echo -n $stdscr|sed 's/_/ /g')" #tput cup 0 0 ;echo -n "$stdscr"
}

dline() {
  pair=$1
  from_x=$2
  from_y=$3
  x2=$4
  y2=$5
  ch=$6

  dx=$(($x2 - $from_x))
  dy=$(($y2 - $from_y))

  ax=$(($dx * 2));ax=$(echo "$ax" | sed 's/^-//')
  ay=$(($dy * 2));ay=$(echo "$ay" | sed 's/^-//')

  sx=$(sign $dx)
  sy=$(sign $dy)

  x=$from_x
  y=$from_y

  if [ $ax -gt $ay ] ; then
    d=$(($ay - ($ax / 2)))
    while true; do
      plot $x $y "$ch"
      [ $x -eq $x2 ] && break
      if [ $d -ge 0 ]; then 
        y=$(($y + $sy))
        d=$(($d - $ax))
      fi
      x=$(($x + $sx))
      d=$(($d + $ay))
    done
    return 0
  else 
    d=$(($ax - ($ay / 2)))
    while true; do
      plot $x $y "$ch"
      [ $y -eq $y2 ] && break
      if [ $d -ge 0 ]; then
        x=$(($x + $sx))
        d=$(($d - $ay))
      fi
      y=$(($y + $sy))
      d=$(($d + $ax))
    done
    return 0
  fi
}
init() {
  clear #tput clear
  
  COLS=$(stty size | awk '{print $2}')
  LINES=$(stty size | awk '{print $1}')

  stdscr=$(seq -s'_'  $((COLS*LINES)) | sed 's/[0-9]//g')

  cx=$(( ($COLS - 1) / 2 ))
  cy=$(( $LINES / 2))
  cr=$cy
  [ $(($cx / 2)) -lt $cy ] && cr=$(( $cx / 2 ))
  sradius=$(echo "$cr" | awk '{print (5 * $1) / 6}') #/* 10 */
  mradius=$(echo "$cr" | awk '{print (3 * $1) / 4}')    #/* 9 */
  hradius=$(echo "$cr" | awk '{print $1 / 2}')  #/* 6 */

  for i in $(seq 0 11); do
    sangle=$(echo "$i" | awk '{print ($1 + 1) * (2.0 * 3.14) / 12.0}')
    sdx=$(a2x $sangle $sradius)
    sdy=$(a2y $sangle $sradius)
    szChar=$(printf "%d" $(($i+1)))
    plot $(($cx + $sdx)) $(($cy - $sdy)) $szChar
  done

  plot 0 0 "ASCII Clock by Howard Jones (ha.jones@ic.ac.uk),1994"

  sradius=$(echo "$sradius" | awk '{print (4 * $1) / 5}')

}

init

trap '/bin/echo -e "\e[?12;25h";exit' 2 #trap "tput cnorm;exit" SIGINT
/bin/echo -e "\e[?25l" #tput civis

trap 'init' 28 #SIGWINCH

while true; do
  date=$(date)
  hours=$(date +'%H')
  min=$(date +'%M')
  sec=$(date +'%S')
  [ $hours -gt 12 ] && hours=$(($hours - 12))
  hours=$(echo "$hours $min" | awk '{print $1 + $2/60}')

  mangle=$(echo "$min $sec" | awk '{print (($1 + $2 / 60) * (2.0 * 3.14) / 60.0)}')
  mdx=$(a2x $mangle $mradius)
  mdy=$(a2y $mangle $mradius)

  hangle=$(echo "$hours" | awk '{print $1 * (2.0 * 3.14) / 12.0}')
  hdx=$(a2x $hangle $hradius)
  hdy=$(a2y $hangle $hradius)

  sangle=$(echo "$sec" | awk '{print $1 * (2.0 * 3.14) / 60.0}')
  sdx=$(a2x $sangle $sradius)
  sdy=$(a2y $sangle $sradius)

  dline 3 $cx $cy $(($cx + $mdx)) $(($cy - $mdy)) '#'
  dline 2 $cx $cy $(($cx + $hdx)) $(($cy - $hdy)) '.'
  dline 1 $cx $cy $(($cx + $sdx)) $(($cy - $sdy)) '0'
  plot 0 $(($LINES - 2)) "$date"
  refresh
  sleep 1

  dline 0 $cx $cy $(($cx + $hdx)) $(($cy - $hdy)) '_'
  dline 0 $cx $cy $(($cx + $mdx)) $(($cy - $mdy)) '_'
  dline 0 $cx $cy $(($cx + $sdx)) $(($cy - $sdy)) '_'
done
```