Однажды один мой знакомый ну просто достал меня той идеей, будто я не смогу написать калькулятор на каком-нибудь языке… Но зря он так думал — ведь я смог написать калькулятор на Icon (с учетом того, что я и визуальный интерфейс выравнивал сам, а большинство кнопок просто вручную добавлял !).
Итак, как же действует калькулятор?
В начале нужно ввести некоторое число с помощью цифровых кнопок, для чего определяем глобальную переменную curr — строку, которая шаг за шагом накапливает разряды числа, а затем выводит его на «дисплей» калькулятора. Но этого мало, нам потребуется еще одна глобальная переменная cop, которая представляет собой список и будет содержать в себе: первый аргумент для операции, саму операцию и второй аргумент операции, но эта переменная потребуется гораздо позже.
Теперь сам дисплей. В статье об эмуляции текстовых полей я уже рассказал как сделать текстовое поле, собственно говоря, этим и воспользуемся для создания дисплея, а также для его очистки перед введением очередного аргумента.
А теперь обработка кнопок. Проще всего обработать нажатие на числовые клавиши (а также нажатие на клавишу смены знака и на клавишу с десятичной точкой) для чего создадим процедуру, выполняющуюся после нажатия перечисленных клавиш и назовем ее button_cb_dig (и в коде для каждой кнопки в конце списка исправим процедуру вызова на button_cb_dig):
procedure button_cb_dig(vidget, value) case vidget.id of { "0" : curr||:="0" "1" : curr||:="1" "2" : curr||:="2" "3" : curr||:="3" "4" : curr||:="4" "5" : curr||:="5" "6" : curr||:="6" "7" : curr||:="7" "8" : curr||:="8" "9" : curr||:="9" "." : curr||:="." "neg" : { Fg("white") FillRectangle(20,20,160,60) Fg("black") if curr=="" then { Fg("gray") FillRectangle(20,20,160,60) Fg("white") FillRectangle(22,22,156,56) Fg("black") fail } } } Fg("gray") FillRectangle(20,20,160,60) Fg("white") FillRectangle(22,22,156,56) Fg("black") DrawString(80,70,curr) end
Процедура работает вполне просто: накапливает разряды числа в переменной curr и по завершении накопления выдает число на «дисплей». Если потребуется изменение знака числа, то сначала происходит сравнение curr с пустой строкой, и если строка не пуста, то дальше идет ее преобразование в тип real и изменение знака и дальше процедура выводит само число на «дисплей».
Теперь сделаем обработку операций. После введения числа, нажатия кнопки с операцией — само число и операция будут помещены в список cop, а переменная curr будет установлена в изначальное положение (т.е. в пустую строку) — и за это будет отвечать процедура обработки нажатия кнопок button_cb_op.
А теперь самое веселое — после нажатия на кнопку «Calculate!» должен вычислиться результат введенного выражения.
Как это сделать? Для этого в процедуре button_cb_op создадим локальную переменную res, в которую будем помещать результат. После нажатия кнопки «Calculate!» сначала происходит сравнение размера cop с 1 и если размер меньше 1, то ничего не произойдет (так как нечего будет вычислять, ибо аргументов в этом случае либо нет, либо их недостаточно), затем нужно проверить равна ли curr пустой строке и если да, то приравняем currк cop[1] — это необходимо для обработки операций с результатом (т.е. проведение вычислений с готовым результатом).
Также нужно предотвратить падение программы, если по какой-то причине, переменная res окажется не определена, для чего необходимо добавить строку:
if /res then fail
но добавление нужно производить только после получения результата и помещения второго аргумента операций в список cop, т.е. после вот таких действий:
put(cop,curr) res:=calc()
Ну, а далее все относительно просто: «дисплей» очищается и выводиться результат, после чего переменные curr и сop возвращаются в привычное состояние.
Весь код процедуры button_cb_op выглядит так:
procedure button_cb_op(vidget, value) local res case vidget.id of { "+" : { put(cop,curr,"+") curr:="" } "-" : { put(cop,curr,"-") curr:="" } "*" : { put(cop,curr,"*") curr:="" } "/" : { put(cop,curr,"/") curr:="" } "calc" : { if *cop
Но самое интересное это процедура calc(), которая выглядит так :
procedure calc() case cop[2] of { "+" : return real(cop[1])+real(cop[3]) "-" : return real(cop[1])-real(cop[3]) "*" : return real(cop[1])*real(cop[3]) "/" : { if cop[3]=0 then { Fg("red") DrawString(30,32,"Division by zero !!!") Fg("black") curr:="" cop:=list() } else return real(cop[1])/real(cop[3]) } } end
Суть простая: по индексу 2 в списке cop размещена строка с операцией (т.е. строки «+», «-«, «*» и «/») и при равенстве индекса списка определенной операции, происходит возврат результата самой операции над крайними элементами списка cop (т.е. первым введенным числом и вторым введенным числом). Однако, для деления необходимо предусмотреть вывод ошибки, если второй аргумент операции будет нулевым — что в принципе и сделано в процедуре calc().
В принципе основные функции обычного калькулятора реализованы, за исключением сброса (он тут на фиг не нужен, даже в случае ошибки), однако есть простор для усовершенствования!
[accordion][panel intro=»Спойлер: полный код калькулятора.»]
############################################################################ # # File: calc.icn # # Subject: Simple calculator without clear # # Author: Oleg Baharew # # Date: July 28, 2012 # ############################################################################ # # # ############################################################################ # # Requires: # ############################################################################ # # Links: vsetup # ############################################################################ # This vib interface specification is a working program that responds # to vidget events by printing messages. Use a text editor to replace # this skeletal program with your own code. Retain the vib section at # the end and use vib to make any changes to the interface. link vsetup global curr,cop procedure main(args) local vidgets, root, paused cop:=list() curr:="" (WOpen ! ui_atts()) | stop("can't open window") vidgets := ui() # set up vidgets root := vidgets["root"] Fg("gray") FillRectangle(20,20,160,60) Fg("white") FillRectangle(22,22,156,56) Fg("black") paused := 1 # flag no work to do repeat { # handle any events that are available, or # wait for events if there is no other work to do while (*Pending() > 0) | \paused do { ProcessEvent(root, QuitCheck) } # if is set null, code can be added here # to perform useful work between checks for input } end procedure button_cb_dig(vidget, value) case vidget.id of { "0" : curr||:="0" "1" : curr||:="1" "2" : curr||:="2" "3" : curr||:="3" "4" : curr||:="4" "5" : curr||:="5" "6" : curr||:="6" "7" : curr||:="7" "8" : curr||:="8" "9" : curr||:="9" "." : curr||:="." "neg" : { Fg("white") FillRectangle(20,20,160,60) Fg("black") if curr=="" then { Fg("gray") FillRectangle(20,20,160,60) Fg("white") FillRectangle(22,22,156,56) Fg("black") fail } } } Fg("gray") FillRectangle(20,20,160,60) Fg("white") FillRectangle(22,22,156,56) Fg("black") DrawString(80,70,curr) end procedure button_cb_op(vidget, value) local res case vidget.id of { "+" : { put(cop,curr,"+") curr:="" } "-" : { put(cop,curr,"-") curr:="" } "*" : { put(cop,curr,"*") curr:="" } "/" : { put(cop,curr,"/") curr:="" } "calc" : { if *cop>=== modify using vib; do not remove this marker line procedure ui_atts() return ["size=200,250", "bg=white" , "label=IconCalc"] end procedure ui(win, cbk) return vsetup(win, cbk, [":Sizer:::0,0,600,401:",], ["1:Button:regular::20,100,30,20:1",button_cb_dig], ["6:Button:regular::100,130,30,20:6",button_cb_dig], ["9:Button:regular::100,160,30,20:9",button_cb_dig], [".:Button:regular::100,190,30,20:.",button_cb_dig], ["/:Button:regular::140,100,35,20:/",button_cb_op], ["*:Button:regular::140,130,35,20:*",button_cb_op], ["-:Button:regular::140,160,35,20:-",button_cb_op], ["+:Button:regular::140,190,35,20:+",button_cb_op], ["calc:Button:regular::20,220,155,20:calculate !",button_cb_op], ["4:Button:regular::20,130,30,20:4",button_cb_dig], ["7:Button:regular::20,160,30,20:7",button_cb_dig], ["neg:Button:regular::20,190,30,20:-",button_cb_dig], ["2:Button:regular::60,100,30,20:2",button_cb_dig], ["5:Button:regular::60,130,30,20:5",button_cb_dig], ["8:Button:regular::60,160,30,20:8",button_cb_dig], ["0:Button:regular::60,190,30,20:0",button_cb_dig], ["3:Button:regular::100,100,30,20:3",button_cb_dig], ) end #===<>=== end of section maintained by vib
[/panel][/accordion]
А теперь скриншоты этого чуда:
И скриншот с ошибкой:
P.S: Естественно, кое-какую ошибку я не исправил (хотя могу это сделать) — но она не существенна, и к тому же — это своеобразная задача.
P.P.S: Ну и конечно, я делаю более навороченную версию программы, вот как она выглядит: