こんにちは。ヤマヤタケシです。
プログラマになるにはどうすればいいですか?でプログラマになる第一歩は、
電卓を作るべし!
わからないなら写経すべし!
言語はとりあえず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がプラスのときとマイナスのときで場合分けをしました。
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; } }バグっていましたが、-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マスターみたいな人から見れば、無駄が多いかもしれません。
ぜひ、無駄のない電卓プログラムを提示してください!
ありがとうございます。
無駄の多いスクリプトですね
おおソース付きでプログラムをですか。いいですねぇ。
ピンバック: プログラマになるにはどうすればいいですか? | ヤマヤタケシのブログ