こんにちは。ヤマヤタケシです。
プログラマになるにはどうすればいいですか?でプログラマになる第一歩は、
電卓を作るべし!
わからないなら写経すべし!
言語はとりあえずJavascriptがいい!
と、言いたい放題やりました。
しかし!
よくよく考えてみると、俺自身電卓を作った事がなかったのです!
プログラムはゲームをベーマガ(雑誌)で写経して覚えたので・・・・。
しかし、読者にやったこともない事を勧めるのは無責任です。
やってみました。Javascript で電卓を作りました!
車輪の再発明でございます。
約5時間かかりました・・・。
2時間ぐらいはスタイルタグをいじっていました。
見た目の調整がクソめんどいですね。
height=100%の指定がどうにもうまく行かなかったのです。
結局、「高さが決まらないタグが親に含まれていると、autoになる。」ことが原因でした。
スタイルシートまで勉強しちゃいました!
さて、これが作った電卓です。
ソースはこの記事の最後にあります。
小数点に誤差が出ます。
これは、2進数をベースにしたコンピュータの宿命なので、そのまま残しました。
なんとかするときは、整数ベースでやるとか、Math.roundするとか、いろいろあるんでしょうが、学習用です。
そのままにしました。
ソースは index.html にまとめました。
普通は3ファイルに分割します。
style.cssとdentaku.jsとindex.html の3つです。
今回は写経してもらうことを想定したので1つです。
プログラムは、M-VCを意識しています。
ModelとView-Contrtolで分離しています。
Modelは論理、ロジック、コア、仕組み、概念です。
Viewは見た目です。
Controlは操作です。
このように分けると仕様変更に強くなります。
Model が独立しているとUIに依存しないテストができます。
単体の自動テストなどを取り入れる場合にとても役立ちます。
View-ControlはプラットフォームのAPIに依存しますが、Model は 純粋な Javascript のみになります。
そのため、移植するときにModelはそのままいけます。
ソースについて少し説明します。
createDentakuModel()で返すインスタンスが電卓の Model です。
createView()が返すのがView-Controlです。
Javascript は高級な言語なので、C言語にはない機能がたくさんありますね。
まあ、言語間の機能の比較などは1つの言語がある程度学習が進んでからで良いと思います。
最近は、いろんな言語を時と場合によって使い分けるようになっています。
この例を作るだけなのに、html, css, javascriptの3つの言語を行き来しました。
さて、ソースをどーんと貼ります。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>電卓js</title> <style> .top { width : 100%; height : 100%; border-color: red; border: 1px; } .row { width : 100%; height : 14%; background-color: #888; } .cell { width : 25%; background-color: #fff; } .text { width : 100%; height : 100%; text-align: right; font-size: 1.4em; padding:0px; margin:0px; border:none; } .button { width : 100%; height : 100%; font-size: 1.4em; padding: 10px; } html, body { height : 100%; margin: 0px; padding: 0px; } </style> <script> function createDentakuModel() { var memory = 0 var current = 0; var isInput = false; var point = 0; var m = {} m.toString = function() { var str = "" str += "isInput " + isInput + " "; str += "current " + current + " "; str += "memory " + memory + " "; return str; } m.all_clear = function() { memory = 0 current = 0; calc = null; isInput = false; point = 0; } m.point = function() { if( point == 0 ){ point = 1; } } m.number = function( num ) { if( isInput == false ){ isInput = true; current = 0; } if( 0 <= current ) { //plus if( 0 < point ){ current += num * Math.pow( 10, -point ); point++; } else { current = current * 10; current += num; } } else { //minus if( 0 < point ){ current -= num * Math.pow( 10, -point ); point++; } else { current = current * 10; current -= num; } } } m.equal = function() { if( m.calc != null ){ m.calc(); } m.calc = null; memory = current; isInput = false; point = 0; } m.add = function() { m.equal(); m.calc = function() { current = memory + current; } } m.minus = function() { current = -current; } m.sub = function() { m.equal(); m.calc = function() { current = memory - current; } } m.multi = function() { m.equal(); m.calc = function() { current = memory * current; } } m.getDisp = function() { return current; } m.clear = function() { current = 0; point = 0; } m.divide = function() { m.equal(); m.calc = function() { if( current == 0 ){ current = "err div 0"; return; } current = memory / current; } } return m; } dentaku_model = createDentakuModel(); function createViewControl() { //フォーム作成。 var form = document.createElement( "form" ); form.className = "top"; document.body.appendChild( form ); //top var top = document.createElement( "table" ); top.className = "top"; form.appendChild( top ); var tr = null; //テキストボックス function textbox() { var element = document.createElement( "input" ); element.type = "text"; element.value = 0; element.size = "100"; element.readOnly = true; element.className = "text" var tde = td() tde.colSpan = 4; tde.appendChild( element ); return element; } var disp = null; function row() { var element = document.createElement( "tr" ); element.className = "row"; top.appendChild( element ); tr = element; } function td( id ) { var tde = document.createElement( "td" ); tde.className ="cell"; tde.id = id; tr.appendChild( tde ); return tde; } function out() { disp = textbox(); } var debug_disp = null; function debug() { debug_disp = textbox(); } function key( ch ) { var element = document.createElement("input"); element.className = "button"; element.type = "button"; element.value = ch; td( ch ).appendChild( element ); return element; } function refresh() { disp.value = dentaku_model.getDisp(); debug_disp.value = dentaku_model.toString(); } function sp(){ tr.appendChild( td() ); } function c() { key( "C" ).onclick = function(){ dentaku_model.clear() ; refresh(); } } function ac() { key( "AC" ).onclick = function(){ dentaku_model.all_clear() ; refresh(); } } function d() { key( "/" ).onclick = function() { dentaku_model.divide(); refresh(); } } function m() { key( "*" ).onclick = function() { dentaku_model.multi() ; refresh(); } } function s() { key( "-" ).onclick = function() { dentaku_model.sub() ; refresh(); } } function a() { key( "+" ).onclick = function() { dentaku_model.add() ; refresh(); } } function e() { key( "=" ).onclick = function() { dentaku_model.equal() ; refresh(); } } function p() { key( "." ).onclick = function() { dentaku_model.point() ; refresh(); } } function n( num ) { key( num ).onclick = function() { dentaku_model.number( num ) ; refresh(); } } function pm() { key( "+/-" ).onclick = function() { dentaku_model.minus() ; refresh(); } } //レイアウト { row(); out() ; row(); c() ; ac() ; pm() ; d(); row(); n( 7 ); n( 8 ); n( 9 ); m(); row(); n( 4 ); n( 5 ); n( 6 ); s(); row(); n( 1 ); n( 2 ); n( 3 ); a(); row(); n( 0 ); p() ; e(); //row(); debug(); var zero = document.getElementById( 0 ); zero.colSpan = 2; } refresh(); } window.onload = function() { createViewControl(); } </script> </head> <body> </body> </html>
そんじゃまた。
付け焼き刃で修正しました。
isInput = false; //←追加
は意図していない動作になります。
– ,9, 1 と入力すると、 -91 になるようにしたかったのです。
currentがプラスのときとマイナスのときで場合分けをしました。
バグっていましたが、-3を入力するときは、3 , +/- の順で入力してください。
おぉ、本当ですね、意図しない値になりました。
ご指摘ありがとうございます!
修正いたします!
-3を-, 3 の順に入力することを想定されていますか?
もしそうだと、内部的に 0 - 3 と同じ入力なので、区別ができません。
区別するために、もう1個ボタンを増やすか、オールクリアの直後だけ、-3にするとか考えられますが、+/-ボタンがあるので追加をするメリットがありません。
現況だと number Key > +/- Key > number Keyと打つと、変な値が入ってしまします。例えば、 2 > +/- で-2となり、その後にユーザーが3を打つと、私の所では何故か -17 が表示されていまいます。これは、
m.minus = function()で、
{
isInput = false; //←追加
current = -current;
}
とする事で回避で来ました。
例えば-3を先に打ち込みたいときに、最初-が表示されない(計算などの結果は正しい)のは、何とか解消出来ないでしょうか?
m.calc()の使い方が興味深いです。モデルの方では、この部分が実はキモなのですね。
eval使ったら学習の意味がないですし。
Javascriptマスターみたいな人から見れば、無駄が多いかもしれません。
ぜひ、無駄のない電卓プログラムを提示してください!
ありがとうございます。
無駄の多いスクリプトですね
おおソース付きでプログラムをですか。いいですねぇ。
ピンバック: プログラマになるにはどうすればいいですか? | ヤマヤタケシのブログ