Simple Scheme で電卓をつくってみる(4)
Simple Schemeで電卓を作っています。データ保持の方法が一応決まったので実装しましょう。
整数部分と小数部分を表すリストを暫定的にトップレベル変数にバインドしておきます。
電卓で数値を入力すると、このリストに数値が追加されていくようにしたいわけです。整数部分と小数部分のどっちを入力しているかをwhich-part
というフラグ変数で管理します。
(define i-part '(0)) (define f-part '()) (define which-part 'int)
例えば365.2425という数が入力されたときi-part
は(5 6 3)
、f-part
は(5 2 4 2)
となります*1。
リストへの数の追加は以下のinput-number
が生成する関数によって行われます。
(define max-ndigits 8) (define (input-number n) (lambda () (and (< (+ (length i-part) (length f-part)) max-ndigits) (if (symbol=? which-part 'int) (if (and (null? (cdr i-part)) (= 0 (car i-part))) (set! i-part (list n)) (set! i-part (cons n i-part))) (set! f-part (cons n f-part))))))
(define _2 (input-number 2)) (define _3 (input-number 3)) (define _4 (input-number 4)) (define _5 (input-number 5)) (define _6 (input-number 6)) i-part f-part ;初期状態 (_3)(_6)(_5) (set! which-part 'frac) ;小数部分入力に切り替える (_2)(_4)(_2)(_5) i-part f-part ;入力後
この関数のリストを作ります。
リストの並び順はbuttons
やbutton-labels
と同じにします。こうすれば、ボタンのエフェクトの表示とリストへの数の挿入(いずれは電卓内での計算処理も)を同じ添字で行うことができます。
並び順を同じにするためにラベル(文字列)をキーとする連想リストを作っているますが、Simple Schemeにはassoc
がありません。代わりにfilter
を使っています。
(define func-alist (list (list "0" (input-number 0)) (list "1" (input-number 1)) (list "2" (input-number 2)) (list "3" (input-number 3)) (list "4" (input-number 4)) (list "5" (input-number 5)) (list "6" (input-number 6)) (list "7" (input-number 7)) (list "8" (input-number 8)) (list "9" (input-number 9)) (list "." (lambda () (set! which-part 'frac))) (list "+" ignore) (list "-" ignore) (list "×" ignore) (list "÷" ignore) (list "√" ignore) (list "=" ignore) (list "%" ignore) (list "AC" ignore) (list "C" ignore) (list "MR" ignore) (list "M+" ignore) (list "M-" ignore))) (define button-funcs (map (lambda (b) (cadar (filter (lambda (p) (string=? (car p) b)) func-alist))) button-labels))
ignore
は(lambda () #f)
と定義された関数です。何もしません。
i-part
とf-part
で表された数を画面に表示するために、数字の列のイメージを生成する関数を定義しましょう。
桁数上限いっぱいに表示されたときに左端の数字が画面の左から1/6の辺りに、常に右端の数字が画面の右から1/6の辺りに、表示されるように調節しています*3。
(define wdx (* width (/ 2/3 (-- max-ndigits)))) (define (wconv-x x) (round (- (* width 5/6) (* wdx x)))) (define (overlay-window scn) (letrec ((place-num (lambda (d lis scn part) (if (null? lis) (if (symbol=? part 'int) scn (place-num d i-part (place-image (text "." (round wdx) "black") (wconv-x (- d 0.5)) (/ width 7) scn) 'int)) (place-num (++ d) (cdr lis) (place-image (text (number->string (car lis)) (round wdx) "black") (wconv-x d) (/ width 7) scn) part))))) (place-num 0 f-part scn 'frac)))
このイメージをkeyboard
の上に重ねます。それなら最初から最後の行を(place-num 0 f-part keyboard 'frac)
とするほうが簡単な気がしますけど、まあこのままでいいや。
ここまでの機能を電卓上で使えるようにbig-bang
の呼び出しを書き替えましょう。
ボタンが押されたとき、まずbutton-funcs
で内部状態*4を変更し、そのあと、keyboard
の上にwindow、その上にボタンエフェクト、という順に重ねて表示します。
(big-bang calculator-image (on-draw (lambda (b) b)) (on-mouse (lambda (_ x y what) (if (string=? what "button-down") (let ((x (iconv-x x)) (y (iconv-y y))) (let ((i (+ x (* 6 y)))) (if (and (<= 0 x) (< x 6) (<= 0 i) (< i 23)) (let ((b (list-ref buttons i)) (x (conv-x x)) (y (conv-y y))) (begin ((list-ref button-funcs i)) (set! calculator-image (overlay-window keyboard)) (place-image (text (button-label b) (/ (* 4 (button-size b)) 3) "white") x y (place-image (circle 100 "solid" (button-color b)) x y calculator-image)))) calculator-image))) calculator-image))))
— brv00 (@brv00) 2019年11月2日
数値の表示部分に枠がないと落ち着きませんね😌
続きます。