diff --git "a/\043game.rb\043" "b/\043game.rb\043" new file mode 100755 index 0000000..31d6593 --- /dev/null +++ "b/\043game.rb\043" @@ -0,0 +1,25 @@ +#!/usr/bin/env ruby +# -*- coding: utf-8 -*- +require 'curses' +include Curses +init_screen +noecho +begin + l=[[4,5,6,8,9,10,12,13,14,16,17,18,20,21,22,24,25,26],[5,8,13,16,18,21,24],[5,8,9,10,13,16,17,18,21,24,25,26],[5,8,13,16,17,21,26],[5,8,9,10,13,16,18,20,21,22,24,25,26]] + for i in (0..4) + for j in l[i] + setpos(4+i,j*2) + addstr("##") + end + end + setpos(15,20) + addstr("press spacebar to start") + refresh + get="" + while get!=" " + get=getch + end +ensure + close_screen +end +puts "\e[101m \e[102m \e[43m \e[104m \e[45m \e[106m \e[103m " \ No newline at end of file diff --git "a/\043keisan3.rb\043" "b/\043keisan3.rb\043" new file mode 100644 index 0000000..c4871d2 --- /dev/null +++ "b/\043keisan3.rb\043" @@ -0,0 +1,332 @@ +# encoding: UTF-8 +# frozen_string_literal: true +require "curses" +include Curses + +OPS = { "+" => 1, "-" => 1, "*" => 2, "/" => 2 }.freeze + +SETSUMEI = [ + " 〜説明〜 ", + " ", + "この電卓は、キーを打って計算内容を入力します", + "すべて半角で入れてください", + "", + "まず数字は数字キーで13527などとそのまま打ちます。", + "足し算、引き算も + や - キーでそのまま打ちます。", + "しかし、掛け算は *(アスタリスク) で打ちます。", + "割り算は/(スラッシュ)で打ちます。", + "ここは気をつけてください。", + "", + "この電卓は括弧も使うことができます。", + "そのまま ( や ) で打ちます。", + "負の数も使えますが、例えば-5を入れたいときは", + "(0-5)と書く必要はありません(今の版は -5 もOK)。", + "結果を見たいときはEnterを押してください。", + "何の計算かわかりにくくなるので*や/と入力しても", + "×や÷などと表示されます。", + "", + "見終わったらEnterキーを押してください。" +].freeze + +def screen_cols + if Curses.respond_to?(:cols) + Curses.cols + elsif defined?(Curses::COLS) + Curses::COLS + else + 80 + end +end + +def screen_lines + if Curses.respond_to?(:lines) + Curses.lines + elsif defined?(Curses::LINES) + Curses::LINES + else + 24 + end +end + +def tail_str(str, width) + return "" if width <= 0 + return str if str.size <= width + str[str.size - width, width] +end + +def center(y, txt) + Curses.setpos(y, 0) + Curses.clrtoeol + w = screen_cols + x = (w - txt.size) / 2 + x = 0 if x < 0 + Curses.setpos(y, x) + Curses.addstr(txt) +end + +def show_help + Curses.clear + w = screen_cols + + SETSUMEI.each_with_index do |line, i| + break if i >= screen_lines + Curses.setpos(i, 0) + Curses.clrtoeol + Curses.addstr(line.to_s[0, w - 1]) if w > 1 + end + + Curses.refresh + + # Enterで戻る(他キーは無視) + loop do + k = Curses.getch + break if k == 10 || k == "\n" + end + + Curses.clear +end + +def tokenize(s) + t = [] + i = 0 + prev = :start + + while i < s.size + c = s[i] + + if c =~ /\s/ + i += 1 + next + end + + if c =~ /[0-9.]/ + j = i + dot = 0 + while j < s.size && s[j] =~ /[0-9.]/ + dot += 1 if s[j] == "." + raise "too many dots" if dot > 1 + j += 1 + end + t << [:n, s[i...j].to_f] + i = j + prev = :num + next + end + + if c == "(" + t << [:l, c] + i += 1 + prev = :l + next + end + + if c == ")" + t << [:r, c] + i += 1 + prev = :r + next + end + + if "+-*/".include?(c) + # 単項マイナス: -5 や -(1+2) + if c == "-" && (prev == :start || prev == :op || prev == :l) + t << [:n, 0.0] + t << [:op, "-"] + i += 1 + prev = :op + next + end + + t << [:op, c] + i += 1 + prev = :op + next + end + + raise "bad char: #{c.inspect}" + end + + t +end + +def to_rpn(tokens) + out = [] + ops = [] + + tokens.each do |k, v| + if k == :n + out << [k, v] + elsif k == :op + while !ops.empty? && ops[-1][0] == :op && OPS[ops[-1][1]] >= OPS[v] + out << ops.pop + end + ops << [k, v] + elsif k == :l + ops << [k, v] + else # :r + found = false + until ops.empty? + x = ops.pop + if x[0] == :l + found = true + break + else + out << x + end + end + raise "paren mismatch" unless found + end + end + + until ops.empty? + x = ops.pop + raise "paren mismatch" if x[0] == :l + out << x + end + + out +end + +def eval_rpn(rpn) + st = [] + + rpn.each do |k, v| + if k == :n + st << v + next + end + + b = st.pop + a = st.pop + raise "bad expr" if a.nil? || b.nil? + + if v == "+" + st << (a + b) + elsif v == "-" + st << (a - b) + elsif v == "*" + st << (a * b) + elsif v == "/" + raise "div by zero" if b == 0 + st << (a / b) + else + raise "unknown op: #{v.inspect}" + end + end + + raise "bad expr" unless st.size == 1 + st[0] +end + +def calc(expr) + eval_rpn(to_rpn(tokenize(expr))) +end + +def show_char(c) + if c == "*" + "×" + elsif c == "/" + "÷" + else + c + end +end + +def read_key + k = Curses.getch + if k == 10 || k == "\n" + return [:enter, nil] + end + if k == 8 || k == 127 || k == Curses::KEY_BACKSPACE + return [:bs, nil] + end + if k.is_a?(String) + return [:ch, k] + end + [:ig, nil] +end + +Curses.init_screen +Curses.noecho +begin + Curses.curs_set(0) +rescue +end + +begin + expr = String.new + disp = String.new + msg = String.new + + Curses.clear + center(0, "電卓: Enter/=で計算, BSで消す, c=クリア, m=説明, q=終了") + + loop do + w = screen_cols + h = screen_lines + + center(h / 2, tail_str(disp, w - 2)) + center(h / 2 + 1, tail_str(msg.to_s, w - 2)) + + Curses.refresh + + act, ch = read_key + next if act == :ig + + msg = String.new + + if act == :bs + expr.chop! + disp.chop! + next + end + + if act == :enter + begin + r = calc(expr) + msg = "= #{r}" + expr = r.to_s + disp = expr.dup + rescue => e + msg = "ERR: #{e.message}" + end + next + end + + # :ch + break if ch == "q" + + if ch == "m" + show_help + center(0, "電卓: Enter/=で計算, BSで消す, c=クリア, m=説明, q=終了") + next + end + + if ch == "c" + expr = String.new + disp = String.new + msg = String.new + next + end + + if ch == "=" + begin + r = calc(expr) + msg = "= #{r}" + expr = r.to_s + disp = expr.dup + rescue => e + msg = "ERR: #{e.message}" + end + next + end + + # / をエスケープしてあるのが重要 + if ch =~ /[0-9+\-*\/().]/ + expr << ch + disp << show_char(ch) + end + end +ensure + Curses.close_screen +end diff --git "a/\043quiz.rb\043" "b/\043quiz.rb\043" new file mode 100755 index 0000000..760a14c --- /dev/null +++ "b/\043quiz.rb\043" @@ -0,0 +1,13 @@ +#!/usr/bin/env ruby +# -*- coding: utf-8 -*- +quiz=["パンの袋を閉じる部品","視力検査のC","金魚すくいの網","切手のギザギザ","爪の白い部分"] +ans=["バッグクロージャー","ランドルト環","ポイ","口打ち","爪半月"] +srand() +random=rand(5) +puts "問題!" +printf("%sの名前は?",quiz[random]) +if gets.chomp==ans[random] + puts "正解!" +else + puts "不正解...また挑戦してね" +end diff --git "a/\043tanihenan.rb\043" "b/\043tanihenan.rb\043" new file mode 100644 index 0000000..e69de29 --- /dev/null +++ "b/\043tanihenan.rb\043" diff --git "a/\043tanihenkan.rb\043" "b/\043tanihenkan.rb\043" index 6b1d10c..3d9bfae 100755 --- "a/\043tanihenkan.rb\043" +++ "b/\043tanihenkan.rb\043" @@ -1,50 +1,112 @@ #!usr/bin/env ruby # -*- coding: utf-8 -*- + +# 説明 +# +# これは計算が苦手な人の単位変換の困りごとを解決するプログラムです。 +# 背景は、in,ft,yd,畳などの単位の感覚がわかりにくいので、 +# 自分にとってわかりやすい単位に変えたいと思ったからです。 +# これは個人研究提案発表会のプログラムを改良したものです。 +# 追加した機能は、 +# ・ cursesを使い画面を見やすくすること、 +# ・一回の実行で繰り返し変換できるようにしたこと +# です。 +# とても見やすくなり、前に表示されたテキストが残ってしまい見にくくなる問題も解決できました。 +# 改良したい点は、表示されたテキストが左にずれてしまうことがあることです。 +# +# 参考文献 +# https://qiita.com/nak435/items/6337daa651ad1f2fd421#macos以外でも +# https://docs.ruby-lang.org/ja/latest/method/String/i/=2a.html + + +system "echo -e '\e[8;48;160t'" +require 'curses' +include Curses +noecho tannilist=[nil,[[nil,"グラム","ポンド","オンス","グレーン","貫","斤","匁","カラット"],[nil,1,453.59237,28.3495,0.064799,3750,600,3.75,0.2]],[[nil,"メートル","インチ","フィート","ヤード","チェーン","ハロン","マイル","分","文","寸","尺","丈","間","町","里","海里","天文単位","光年","パーセク"],[nil,1,0.0254,0.3048,0.9144,20.117,201.17,16093,0.003,0.024,0.03,0.3,3,1.82,109,4000,1852,149597870700,9460730472580800,30856800000000000]],[[nil,"平方メートル","エーカー","歩","坪","畝","反","段","町","町歩","丁"],[nil,1,4046.9,3.3,3.3,100,1000,1000,10000,10000,10000]],[[nil,"立方メートル","リットル","ガロン","石","斗","升","合"],[nil,1000,1,3.785412,180.39,18.039,1.8039,0.18039]],[[nil,"度","ケルビン","華氏温度"],[nil,[1,0],[1,-273.15],[0.55555,-17.7777]]]] shuruilist=[nil,"重さ","長さ","面積","体積","温度"] +def clear(l) + for i in l + setpos(i,0) + addstr(" "*cols) + end +end +def center(a,text) + clear([a]) + setpos(a,cols/2-text.length) + addstr(text) + refresh +end +def input(a) + t="" + get="" + setpos(a,cols/2) + while get!=10 + if get!=8 + t+=get + else + if t.length>1 + t=t[0..(t.length-2)] + else + t="" + end + end + clear([a]) + setpos(a,(cols-t.length)/2) + addstr(t) + get=getch + end + clear([a]) + t +end def sentaku(a,b,c) kakunin=false while not kakunin - puts(a+"半角の数字で入力してenterを押してください。") + center(1,a+"半角の数字で入力してEnterを押してください。") d=[] - i=0 - for j in b - if j!=nil - d<i+num2.length - naiyou2+=naiyou[m] - m+=1 - elsif l=="*" - num3*=num4 - naiyou2+=num3.to_s - m+=num1.length+num2.length+1 - else - num3/=num4 - naiyou2+=num3.to_s - m+=num1.length+num2.length+1 - end + if str[i]=="-" + j+="-" + i+=1 end - naiyou=naiyou2 - i-=num1.length - num1=num3.to_s - i+=num1.length - l="t" - end -end -i=0 -l="n" -while ii+num2.length - naiyou2+=naiyou[m] - m+=1 - elsif l=="+" - num3+=num4 - naiyou2+=num3.to_s - m+=num1.length+num2.length+1 - else - num3-=num4 - naiyou2+=num3.to_s - m+=num1.length+num2.length+1 - end + while str[i]!="+" && str[i]!="-" && str[i]!="*" && str[i]!="/" && str[i]!=nil && str[i]!=")" + j+=str[i] + i+=1 end - naiyou=naiyou2 - i-=num1.length - num1=num3.to_s - i+=num1.length - l="t" + len=j.length end + [j.to_f,len] end -puts naiyou +def keisan(a,b,n) + i=0 + l="n" + while ii+num2[1] + n2+=n[m] + m+=1 + else + if l=="+" + num3+=num4 + elsif l=="-" + num3-=num4 + elsif l=="*" + num3*=num4 + else + num3/=num4 + end + n2+=num3.to_s + m+=num1[1]+num2[1]+1 + end + end + n=n2 + i-=num1[1] + num1[0]=num3 + num1[1]=num3.to_s.length + i+=num1[1] + l="t" + end + end + n +end +def henkan(str) + if str=="*" + str="×" + elsif str=="/" + str="÷" + end + str +end +$setsumei=[" 〜説明〜","","この電卓は、キーを打って計算内容を入力します","すべて半角で入れてください","","まず数字は数字キーで13527などとそのまま打ちます。","足し算、引き算も + や - キーでそのまま打ちます。","しかし、掛け算は *(アスタリスク) で打ちます。","割り算は/(スラッシュ)で打ちます。","ここは気をつけてください。","","この電卓は括弧も使うことができます。","そのまま ( や ) で打ちます。","負の数も使えますが、例えば-5を入れたいときは少し難しく(0-5)と書きます。","そのまま-5と入力すると結果がおかしくなるので気をつけてください。","結果を見たいときはEnterを押してください。","何の計算かわかりにくくなるので*や/と入力しても×や÷などと表示されます。","","この電卓は15桁までしか扱えないので16桁以上になると結果がおかしくなります。","","このルールは覚えにくいので、あらかじめどう入力するかメモしておくといいと思います。","見終わったらEnterキーを押してください。"] +def show_help + clear + for i in (0..($setsumei.length-1)) + setpos(i,0) + addstr($setsumei[i]) + end + refresh + while getch!=10 + end + clear +end +init_screen +keisannaiyou="" +hyoujinaiyou="" +get="" +invalidnums=[8,9,27,127] +validchars=["+","-","*","/",".","(",")"] +begin + t=0 + cursor=0 + while get!="=" && get!=10 + if (not invalidnums.include?(get)) && (validchars.include?(get) || (/\d/ =~ get && t<2)) + if keisannaiyou.length>cursor + keisannaiyou=keisannaiyou[0..(cursor-1)]+get+keisannaiyou[cursor..(keisannaiyou.length-1)] + hyoujinaiyou=hyoujinaiyou[0..(cursor-1)]+henkan(get)+hyoujinaiyou[cursor..(hyoujinaiyou.length-1)] + else + keisannaiyou+=get + hyoujinaiyou+=henkan(get) + end + t=0 + cursor+=1 + elsif get==8 && keisannaiyou.length>1 + keisannaiyou=keisannaiyou[0..(keisannaiyou.length-2)] + hyoujinaiyou=hyoujinaiyou[0..(hyoujinaiyou.length-2)] + t=0 + cursor-=1 + elsif get==8 + keisannaiyou="" + hyoujinaiyou="" + t=0 + cursor=0 + elsif get==27 + t=1 + elsif t==1 && get=="[" + t=2 + elsif t==1 + t=0 + elsif t==2 + if /\d/ =~ get + t=3 + elsif get=="A" + cursor=0 + elsif get=="B" + cursor=hyoujinaiyou.length + elsif get=="C" && cursor0 + cursor-=1 + end + t=0 + elsif t==3 && get=="~" + t=0 + elsif get=="m" + show_help + end + setpos(0,0) + addstr("これは電卓プログラムです。 使い方を見たいときはmキーを押してください") + setpos(lines/2,0) + addstr(" ") + setpos(lines/2,(cols-hyoujinaiyou.length)/2) + addstr(hyoujinaiyou) + setpos(lines/2,(cols-hyoujinaiyou.length)/2+cursor) + refresh + get=getch + end + ans=keisan("+","-",keisan("*","/",keisannaiyou)) + setpos(lines/2+1,(cols-ans.length-1)/2) + addstr("="+ans) + refresh +ensure + close_screen +end diff --git a/keisan3.rb b/keisan3.rb new file mode 100644 index 0000000..2554d9e --- /dev/null +++ b/keisan3.rb @@ -0,0 +1,332 @@ +# encoding: UTF-8 +# frozen_string_literal: true +require "curses" +include Curses + +OPS = { "+" => 1, "-" => 1, "*" => 2, "/" => 2 }.freeze + +SETSUMEI = [ + " 〜説明〜 ", + " ", + "この電卓は、キーを打って計算内容を入力します", + "すべて半角で入れてください", + "", + "まず数字は数字キーで13527などとそのまま打ちます。", + "足し算、引き算も + や - キーでそのまま打ちます。", + "しかし、掛け算は *(アスタリスク) で打ちます。", + "割り算は/(スラッシュ)で打ちます。", + "ここは気をつけてください。", + "", + "この電卓は括弧も使うことができます。", + "そのまま ( や ) で打ちます。", + "負の数も使えますが、例えば-5を入れたいときは", + "(0-5)と書く必要はありません(今の版は -5 もOK)。", + "結果を見たいときはEnterを押してください。", + "何の計算かわかりにくくなるので*や/と入力しても", + "×や÷などと表示されます。", + "", + "見終わったらEnterキーを押してください。" +].freeze + +def screen_cols + if Curses.respond_to?(:cols) + Curses.cols + elsif defined?(Curses::COLS) + Curses::COLS + else + 80 + end +end + +def screen_lines + if Curses.respond_to?(:lines) + Curses.lines + elsif defined?(Curses::LINES) + Curses::LINES + else + 24 + end +end + +def tail_str(str, width) + return "" if width <= 0 + return str if str.size <= width + str[str.size - width, width] +end + +def center(y, txt) + Curses.setpos(y, 0) + Curses.clrtoeol + w = screen_cols + x = (w - txt.size) / 2 + x = 0 if x < 0 + Curses.setpos(y, x) + Curses.addstr(txt) +end + +def show_help + Curses.clear + w = screen_cols + + SETSUMEI.each_with_index do |line, i| + break if i >= screen_lines + Curses.setpos(i, 0) + Curses.clrtoeol + Curses.addstr(line.to_s[0, w - 1]) if w > 1 + end + + Curses.refresh + + # Enterで戻る(他キーは無視) + loop do + k = Curses.getch + break if k == 10 || k == "\n" + end + + Curses.clear +end + +def tokenize(s) + t = [] + i = 0 + prev = :start + + while i < s.size + c = s[i] + + if c =~ /\s/ + i += 1 + next + end + + if c =~ /[0-9.]/ + j = i + dot = 0 + while j < s.size && s[j] =~ /[0-9.]/ + dot += 1 if s[j] == "." + raise "too many dots" if dot > 1 + j += 1 + end + t << [:n, s[i...j].to_f] + i = j + prev = :num + next + end + + if c == "(" + t << [:l, c] + i += 1 + prev = :l + next + end + + if c == ")" + t << [:r, c] + i += 1 + prev = :r + next + end + + if "+-*/".include?(c) + # 単項マイナス: -5 や -(1+2) + if c == "-" && (prev == :start || prev == :op || prev == :l) + t << [:n, 0.0] + t << [:op, "-"] + i += 1 + prev = :op + next + end + + t << [:op, c] + i += 1 + prev = :op + next + end + + raise "bad char: #{c.inspect}" + end + + t +end + +def to_rpn(tokens) + out = [] + ops = [] + + tokens.each do |k, v| + if k == :n + out << [k, v] + elsif k == :op + while !ops.empty? && ops[-1][0] == :op && OPS[ops[-1][1]] >= OPS[v] + out << ops.pop + end + ops << [k, v] + elsif k == :l + ops << [k, v] + else # :r + found = false + until ops.empty? + x = ops.pop + if x[0] == :l + found = true + break + else + out << x + end + end + raise "paren mismatch" unless found + end + end + + until ops.empty? + x = ops.pop + raise "paren mismatch" if x[0] == :l + out << x + end + + out +end + +def eval_rpn(rpn) + st = [] + + rpn.each do |k, v| + if k == :n + st << v + next + end + + b = st.pop + a = st.pop + raise "bad expr" if a.nil? || b.nil? + + if v == "+" + st << (a + b) + elsif v == "-" + st << (a - b) + elsif v == "*" + st << (a * b) + elsif v == "/" + raise "div by zero" if b == 0 + st << (a / b) + else + raise "unknown op: #{v.inspect}" + end + end + + raise "bad expr" unless st.size == 1 + st[0] +end + +def calc(expr) + eval_rpn(to_rpn(tokenize(expr))) +end + +def show_char(c) + if c == "*" + "×" + elsif c == "/" + "÷" + else + c + end +end + +def read_key + k = Curses.getch + if k == 10 || k == "\n" + return [:enter, nil] + end + if k == 8 || k == 127 || k == Curses::KEY_BACKSPACE + return [:bs, nil] + end + if k.is_a?(String) + return [:ch, k] + end + [:ig, nil] +end + +Curses.init_screen +Curses.noecho +begin + Curses.curs_set(0) +rescue +end + +begin + expr = String.new + disp = String.new + msg = String.new + + Curses.clear + center(0, "電卓: Enter/=で計算, BSで消す, c=クリア, m=説明, q=終了") + + loop do + w = screen_cols + h = screen_lines + + center(h / 2, tail_str(disp, w - 2)) + center(h / 2 + 1, tail_str(msg.to_s, w - 2)) + + Curses.refresh + + act, ch = read_key + next if act == :ig + + msg = String.new + + if act == :bs + expr.chop! + disp.chop! + next + end + + if act == :enter + begin + r = calc(expr) + msg = "= #{r}" + expr = r.to_s + disp = expr.dup + rescue => e + msg = "ERR: #{e.message}" + end + next + end + + # :ch + break if ch == "q" + + if ch == "m" + show_help + center(0, "電卓: Enter/=で計算, BSで消す, c=クリア, m=説明, q=終了") + next + end + + if ch == "c" + expr = String.new + disp = String.new + msg = String.new + next + end + + if ch == "=" + begin + r = calc(expr) + msg = "= #{r}" + expr = r.to_s + disp = expr.dup + rescue => e + msg = "ERR: #{e.message}" + end + next + end + + # / をエスケープしてあるのが重要 + if ch =~ /[0-9+\-*\/().]/ + expr << ch + disp << show_char(ch) + end + end +ensure + Curses.close_screen +end diff --git a/keisan6.rb b/keisan6.rb new file mode 100644 index 0000000..51abf2a --- /dev/null +++ b/keisan6.rb @@ -0,0 +1,399 @@ +# encoding: UTF-8 +# frozen_string_literal: true +require "curses" +include Curses + +OPS = { "+" => 1, "-" => 1, "*" => 2, "/" => 2 }.freeze + +SETSUMEI = [ + " 〜説明〜 ", + " ", + "この電卓は、キーを打って計算内容を入力します", + "すべて半角で入れてください", + "", + "数字は数字キーでそのまま打ちます。", + "足し算、引き算も + や - キーでそのまま打ちます。", + "掛け算は *(アスタリスク) 、割り算は /(スラッシュ) です。", + "", + "括弧も使えます。( や ) をそのまま打ちます。", + "負の数も使えます。-5 や -(1+2) もOKです。", + "", + "結果を見たいときは Enter か = を押してください。", + "見やすくするため * / は × ÷ と表示されます。", + "", + "見終わったら Enter キーを押してください。" +].freeze + +def screen_cols + if Curses.respond_to?(:cols) + Curses.cols + elsif defined?(Curses::COLS) + Curses::COLS + else + 80 + end +end + +def screen_lines + if Curses.respond_to?(:lines) + Curses.lines + elsif defined?(Curses::LINES) + Curses::LINES + else + 24 + end +end + +def tail_str(str, width) + return "" if width <= 0 + return str if str.size <= width + str[str.size - width, width] +end + +def center(y, txt) + Curses.setpos(y, 0) + Curses.clrtoeol + w = screen_cols + x = (w - txt.size) / 2 + x = 0 if x < 0 + Curses.setpos(y, x) + Curses.addstr(txt) +end + +def show_help + Curses.clear + w = screen_cols + h = screen_lines + + SETSUMEI.each_with_index do |line, i| + break if i >= h + Curses.setpos(i, 0) + Curses.clrtoeol + Curses.addstr(line.to_s[0, w - 1]) if w > 1 + end + + Curses.refresh + loop do + k = Curses.getch + break if k == 10 || k == "\n" + end + Curses.clear +end + +def tokenize(s) + t = [] + i = 0 + prev = :start # :start, :num, :op, :l, :r + + while i < s.size + c = s[i] + + if c =~ /\s/ + i += 1 + next + end + + if c =~ /[0-9.]/ + j = i + dot = 0 + while j < s.size && s[j] =~ /[0-9.]/ + dot += 1 if s[j] == "." + raise "too many dots" if dot > 1 + j += 1 + end + t << [:n, s[i...j].to_f] + i = j + prev = :num + next + end + + if c == "(" + t << [:l, c] + i += 1 + prev = :l + next + end + + if c == ")" + t << [:r, c] + i += 1 + prev = :r + next + end + + if "+-*/".include?(c) + if c == "-" && (prev == :start || prev == :op || prev == :l) + t << [:n, 0.0] + t << [:op, "-"] + i += 1 + prev = :op + next + end + + t << [:op, c] + i += 1 + prev = :op + next + end + + raise "bad char: #{c.inspect}" + end + + t +end + +def to_rpn(tokens) + out = [] + ops = [] + + tokens.each do |k, v| + if k == :n + out << [k, v] + elsif k == :op + while !ops.empty? && ops[-1][0] == :op && OPS[ops[-1][1]] >= OPS[v] + out << ops.pop + end + ops << [k, v] + elsif k == :l + ops << [k, v] + else # :r + found = false + until ops.empty? + x = ops.pop + if x[0] == :l + found = true + break + else + out << x + end + end + raise "paren mismatch" unless found + end + end + + until ops.empty? + x = ops.pop + raise "paren mismatch" if x[0] == :l + out << x + end + + out +end + +def run_rpn(rpn) + st = [] + + rpn.each do |k, v| + if k == :n + st << v + next + end + + b = st.pop + a = st.pop + raise "bad expr" if a.nil? || b.nil? + + if v == "+" + st << (a + b) + elsif v == "-" + st << (a - b) + elsif v == "*" + st << (a * b) + elsif v == "/" + raise "div by zero" if b == 0 + st << (a / b) + else + raise "unknown op: #{v.inspect}" + end + end + + raise "bad expr" unless st.size == 1 + st[0] +end + +def calc(expr) + run_rpn(to_rpn(tokenize(expr))) +end + +def show_char(c) + if c == "*" + "×" + elsif c == "/" + "÷" + else + c + end +end + +def read_key + k = Curses.getch + + return [:enter, nil] if k == 10 || k == "\n" + return [:bs, nil] if k == 8 || k == 127 || k == Curses::KEY_BACKSPACE + + return [:left, nil] if k == Curses::KEY_LEFT + return [:right, nil] if k == Curses::KEY_RIGHT + return [:up, nil] if k == Curses::KEY_UP + return [:down, nil] if k == Curses::KEY_DOWN + + return [:ig, nil] if k.is_a?(Integer) + return [:ch, k] if k.is_a?(String) + + [:ig, nil] +end + +Curses.init_screen +Curses.noecho +Curses.stdscr.keypad(true) rescue nil + +begin + Curses.curs_set(1) +rescue +end + +begin + expr = String.new + disp = String.new + msg = String.new + + cursor = 0 + history = [] + h_idx = -1 + + Curses.clear + center(0, "電卓: Enter/=で計算, BSで消す, c=クリア, m=説明, q=終了 ←→で移動, ↑↓で履歴") + + loop do + w = screen_cols + h = screen_lines + width = w - 2 + width = 1 if width < 1 + + view_start = disp.size - width + view_start = 0 if view_start < 0 + visible = disp[view_start, width] || "" + + y = h / 2 + center(y, visible) + center(y + 1, tail_str(msg.to_s, width)) + + x0 = [(w - visible.size) / 2, 0].max + cx = cursor - view_start + if cx >= 0 && cx <= visible.size + Curses.setpos(y, x0 + cx) + end + + Curses.refresh + + act, ch = read_key + next if act == :ig + + msg = String.new + + if act == :left + cursor -= 1 if cursor > 0 + next + end + + if act == :right + cursor += 1 if cursor < expr.size + next + end + + if act == :up + if !history.empty? + h_idx = history.size - 1 if h_idx == -1 + h_idx -= 1 if h_idx > 0 + expr = history[h_idx].dup + disp = expr.dup + disp.gsub!("*", "×") + disp.gsub!("/", "÷") + cursor = expr.size + end + next + end + + if act == :down + if !history.empty? && h_idx != -1 + h_idx += 1 + if h_idx >= history.size + h_idx = -1 + expr = String.new + disp = String.new + else + expr = history[h_idx].dup + disp = expr.dup + disp.gsub!("*", "×") + disp.gsub!("/", "÷") + end + cursor = expr.size + end + next + end + + if act == :bs + if cursor > 0 + expr.slice!(cursor - 1) + disp.slice!(cursor - 1) + cursor -= 1 + end + next + end + + if act == :enter + begin + r = calc(expr) + msg = "= #{r}" + history << expr.dup unless expr.empty? + h_idx = -1 + expr = r.to_s + disp = expr.dup + cursor = expr.size + rescue => e + msg = "ERR: #{e.message}" + end + next + end + + # :ch + break if ch == "q" + + if ch == "m" + show_help + center(0, "電卓: Enter/=で計算, BSで消す, c=クリア, m=説明, q=終了 ←→で移動, ↑↓で履歴") + next + end + + if ch == "c" + expr = String.new + disp = String.new + msg = String.new + cursor = 0 + h_idx = -1 + next + end + + if ch == "=" + begin + r = calc(expr) + msg = "= #{r}" + history << expr.dup unless expr.empty? + h_idx = -1 + expr = r.to_s + disp = expr.dup + cursor = expr.size + rescue => e + msg = "ERR: #{e.message}" + end + next + end + + if ch =~ /[0-9+\-*\/().]/ + expr.insert(cursor, ch) + disp.insert(cursor, show_char(ch)) + cursor += 1 + end + end +ensure + Curses.close_screen +end diff --git a/keisan7.rb b/keisan7.rb new file mode 100644 index 0000000..c37e4c5 --- /dev/null +++ b/keisan7.rb @@ -0,0 +1,98 @@ +require "curses" +def keisan(shiki, ichi=0, ans=0.0) + suuji,enzan = [], [] + yuusen = {'+'=>1,'-'=>1,'*'=>2,'/'=>2 } + tekiyou = ->{ + b, a, e, = suuji.pop, suuji.pop, enzan.pop + suuji << (e=='+' ? a+b : e=='-' ? a-b : e=='*' ? a*b : a/b)} + yomu_atai = lambda do + ichi += 1 while shiki[ichi]==' ' + raise "式の途中で終わりました" unless shiki[ichi] + if shiki[ichi,3] && shiki[ichi,3].downcase=="ans" + next [ans, ichi+3] + end + if shiki[ichi]=='-' + ichi += 1 + v, ichi = yomu_atai.call + next [-v, ichi] + end + if shiki[ichi]=='(' + v, ichi2 = keisan(shiki, ichi+1, ans) + raise "閉じカッコ ) がありません" unless shiki[ichi2]==')' + next [v, ichi2+1] + end + hajime=ichi + ichi += 1 while shiki[ichi] && shiki[ichi]=~/[0-9.]/ + raise "数値が見つかりません" if hajime==ichi + [shiki[hajime...ichi].to_f, ichi] + end + atai_hoshii=true + while ichi < shiki.length + ichi += 1 while shiki[ichi]==' ' + break if ichi>=shiki.length || shiki[ichi]==')' + if atai_hoshii + v, ichi = yomu_atai.call + suuji << v + atai_hoshii=false + next + end + e=shiki[ichi] + raise "演算子が不正です" unless yuusen[e] + while enzan.any? && yuusen[enzan[-1]] && yuusen[enzan[-1]] >= yuusen[e] + tekiyou.call + end + enzan << e + ichi += 1 + atai_hoshii=true + end + tekiyou.call while enzan.any? + [suuji[0], ichi] +end +include Curses +init_screen +cbreak +noecho +curs_set(0) +nyuryoku="" +messeji="Enter=計算 / BS=削除 / Ctrl+L=クリア / q=終了" +kekka, ans ="", 0.0 +begin + loop do + clear + setpos(0,0) + addstr("curses電卓(+-*/() 小数)"[0,cols-1]) + setpos(1,0) + addstr(messeji[0,cols-1]) + setpos(2,0) + addstr(("> "+nyuryoku)[0,cols-1]) + setpos(3,0) + addstr(("= "+kekka)[0,cols-1]) + key=getch + case key + when 'q','Q' then break + when 10,13 + begin + shiki = nyuryoku.strip + shiki = ans.to_s if shiki.empty? + shiki = ans.to_s + shiki if shiki.start_with?('+','*','/') + kotae, owari = keisan(shiki, 0, ans) + owari += 1 while shiki[owari]==' ' + raise "式の後ろに余計な文字があります" if owari < shiki.length + ans = kotae + kekka, nyuryoku, messeji = kotae.to_s, "", "計算しました (ans=#{ans} )" + rescue => e + kekka, nyuryoku, messeji = "", "", "エラー: #{e.message}" + end + when 12 + nyuryoku, kekka, messeji = "", "", "クリアしました" + when KEY_BACKSPACE,127,8 + nyuryoku = nyuryoku[0...-1] + when Integer + nyuryoku << key.chr if key>=32 && key<127 + when String + nyuryoku << key if key.ord>=32 && key.ord<127 + end + end +ensure + close_screen +end diff --git a/keisan8.rb b/keisan8.rb new file mode 100755 index 0000000..7d36a60 --- /dev/null +++ b/keisan8.rb @@ -0,0 +1,32 @@ +#!/usr/bin/env ruby +# -*- encoding:utf-8 -*- +require 'curses' +Curses.noecho +def numload(str) + if str[0]=="(" + str=keisan(1,keisan(2,str[1..(str.length-1)])) + elsif str[0]=="-" + str=["-"+numload(str[1..(str.length-1)])[0],numload(str[1..(str.length-1)])[1]+1] + else + i=0 + while /\d|\./=~str[i] + i=i+1 + end + str=[str.to_f,i] + end + str +end +def keisan(yuusen,str) + i,junni,str2,j=-1,{nil=>0,")"=>0,"+"=>1,"-"=>1,"*"=>2,"/"=>2},str,0 + while str2[i]!=")" && i1 + t=t[0..(t.length-2)] + else + t="" + end + end + clear([a]) + setpos(a,(cols-t.length)/2) + addstr(t) + get=getch + end + clear([a]) + t +end +def sentaku(a,b,c) + kakunin=false + while not kakunin + center(1,a+"半角の数字で入力してEnterを押してください。") + d=[] + for i in (1..(b.length-1)) + clear([i+1]) + center(i+1,i.to_s+"."+b[i]) + d<1 + t=t[0..(t.length-2)] + else + t="" + end + end + clear([a]) + setpos(a,(cols-t.length)/2) + addstr(t) + get=getch + end + clear([a]) + t +end def sentaku(a,b,c) kakunin=false while not kakunin - puts(a+"半角の数字で入力してenterを押してください。") + center(1,a+"半角の数字で入力してEnterを押してください。") d=[] - i=0 - for j in b - if j!=nil - d<