みなくんの日記

やる気が皆無で自由気ままに生きてる人のブログ

プログラムよ、とにかく動け ~4桁のヒット&ブローゲーム「4Numbers」を作ろう~その4

今まで作ってきたパーツを組み立て、メイン関数を作ってプログラムを完成させる総集編です!ここまで来れば、後は楽ちんですね!

今回の目標

  • [Chapter.4]「4Numbers」を完成させる
  • [Chapter.EX] タイマーの実装

部品(プログラム)を組み立てる→メイン関数を作る

過去3回分までで、以下のようなことをやってきましたね。

  • 正解の4桁の数字の生成
  • ユーザに4桁の数字を入力してもらう処理の実装
  • 正解桁とユーザの解答桁との比較

今回作成するゲームは、これらの機能を使うわけです。ということは、今までやってきたのは例えるならば「自動車の部品作り」であり、今回行う作業は「自動車の組み立て」なんですね。今まで苦労して作って来たものを良い感じに組み合わせ、ゲームとして完成させてしまいましょう!
ただし、ただ作った部品をくっつける…だけではうまくいきません。部品を部品として使えるようにしてあげる必要があります。
その準備は実は今までの回でやっていたのですが、気付いていましたか?

クラス・メソッド→オブジェクト指向

今まで作成してきたプログラムはすべて、クラスを作成していましたよね。
第2回のユーザ入力プログラムをもう一度見返してみましょう。(クラスが分からん…という人は調べてみてください)

class input_number:
    def input_num(self):
        while True: #4桁の数字かGive upが入力されるまでループ処理
            try:
                num=input("正解だと思う正の数字4桁を入力してください:")
                if len(num)==4: #4桁であった場合
                    check=int(num) #それが文字列であった場合、ここでValueErrorに飛ばす
                    if num[0]=="-":
                        raise ValueError #入っていたらそれはエラーとして処理
                    return num #何もなければユーザの入力を返り値にする
                else: #4桁以上の数字であった場合
                    raise ValueError
            except ValueError: #文字列が入力された場合(今回は4桁以上の数字もこのエラー処理になる)
                if num=="Give up": #Give upと入力された場合
                    print("諦めます。また挑戦してくださいね!")
                    exit()
                else: #他の文字列もしくは4桁以上の数字が入力された場合
                    print("4桁の正の数字かGive upのみを入力してください。")
    def start_init(self):
        input("開始するにはEnterキーを押してください...")

ここでは、input_numberクラスを作成して、その中でユーザの入力を受け付けるinput_num()と、「開始するにはEnterキーを押してください…」と表示してEnterキーの入力を促すstart_init()メソッドを定義しています。
このように、クラスの中に処理を定義することでユーザ入力を受け付けたければinput_num()メソッドを使えばよいなど、新しいプログラムに組み込む際に組み込む側の人が詳細な処理内容を理解することなく部品として使えるようになるんですね。
このように操作手順よりも操作対象に重点を置いた考え方を「オブジェクト指向」と言います。
私もこの考え方を学んだのは最近のことで、説明に間違いがあるかもしれないので…もっとよく知りたい人は「オブジェクト指向」で調べてみてください!(丸投げ)

長々と説明しましたが、今まで作成してきたプログラムはそれぞれ
* 正解桁生成処理→ create_randomクラス
* ユーザ入力処理→ input_numberクラス
* 正解桁とユーザ桁比較処理→compare_numberクラス
と定義し、使える状態にしています。あとはこれを利用して、メイン関数を作っていきましょう!

部品を組み合わせてゲームを作る(メイン関数の作成)

ではここからメイン関数を作成して、完成させていきます。
先ほどまで述べてきたように、今まで作ったものを組み合わせて…ドーンと作ります。作ったものが以下の通り。
なお、「デバッグ用」とコメントされたものはあくまで自分がテストとして使うものであり、実際のプログラムコードには使わないため予めコメントアウトしています。コメントアウトしなかった場合、正解桁が表示されてしまうので…ゲームにならないです。

import random_number #ランダム生成クラス
import input_user2 #ユーザー入力クラス
import compare #正解との比較クラス
import time #timeモジュール

Answer=random_number.create_random() #正解桁生成クラスのインスタンス化
Ans=Answer.create_num() #正解桁生成
#print(Ans) #デバッグ用
user=input_user2.input_number() #ユーザー入力クラスのインスタンス化
compare=compare.compare_number() #比較クラスのインスタンス化
user.start_init() #始めるには…
End=False #End変数の初期化
Start = time.time() #計測開始
while End==False: #継続条件:EndがFalseである間 終了条件:EndがTrue(Falseでなくなる)のとき
    Num=user.input_num()
    #print(Num,Ans) #デバッグ用
    End=compare.cmp_num(Num,str(Ans)) 
Stop = time.time() #計測終了
print("正解するまでにかかった時間:{0}秒".format(round(Stop-Start,2)))
print("また挑戦してくださいね!")

多分正解桁生成プログラムの次ぐらいにコード数が少ないプログラムなのでは…?
パッと見たとき、「これだけでゲームできるのかよ!」と思ってしまうほどですね。
コードを見てもらえばわかるように、importで今まで作成したパーツを読み込んできて、それを適宜必要なタイミングで利用している…といった感じです。何をしているのか、何となくわかるのではないでしょうか。
でもただ部品を並べただけではなく、少し手間を加えたり追加機能を付けたりしているので順に説明していくことにしましょう。

クラスの呼び出しとインスタンス

まずメイン関数の1~4行目に書いてある「import」ですが、これはモジュールや他プログラムを呼び出すのに使用しています。
4行目に関しては後々使うことになるtimeモジュールです。現在時刻の取得などに使える、便利なモノです。それ以外の3行はそれぞれのプログラムを呼び出している感じですね。
メイン関数でほかのプログラムを呼び出す際はこれでいいのですが、注意すべきことが一つ。
それは、メインプログラムとimportで呼び出したプログラムは同じディレクトリ内に入っている必要があるということです。
設定したら違うディレクトリでも行けたかもしれませんが…同一ディレクトリ内ですることをお勧めします(何となく面倒そうなので)。
そして、importで呼び出した後に以下のコードが載っていると思います。

Answer=random_number.create_random() #正解桁生成クラスのインスタンス化
…
user=input_user2.input_number() #ユーザー入力クラスのインスタンス化
compare=compare.compare_number() #比較クラスのインスタンス化

これがインスタンスです。 クラスは言わば「仕様書」であり、インスタンスは「実体」になります。今回のコードでいえば「user」という名前のインスタンス(実体/部品)を、inout_user2.pyに記載されているinput_numberというクラス(仕様書)をもとに作成した、といった意味になります。
今回は3つのプログラムを組み込んで一つのプログラムにするので、インスタンスは3つ、それぞれ違うものが必要になります。よって、Answerとuserとcompareというインスタンスを用意しているんですね。

プログラムの利用…手順通りに使っていく

インスタンスの作成によりパーツができたので、あとは利用していくだけになります。
使い方は以下のような感じ。

Ans=Answer.create_num() #正解桁生成

何と言いますか…私たちが無意識に「print("Hello, Python world!")」と打って画面上に"Hello, Python world!"を表示させているときと同じ感覚です。これは正解桁を生成してくれるメソッドで、変数に代入させれば正解桁を得られる…という仕様さえ知っていれば、これは簡単にかけると思います。
あのような書き方で、正解桁生成、スタートコール、ユーザ入力処理、正解比較…を行っていきます。
スタートコール担当は"user.start_init()"です。これを実行すれば「開始するには…」という文章が出てEnterキーの入力を促します。
ユーザ入力処理担当は"user.input_num()"です。これを実行し、返り値を変数に代入させることでユーザが入力した4桁が得られます。
正解比較担当は"compare.cmp_num(Num,str(Ans))"です。引数としてユーザ入力桁のNumと正解桁をString型に変換したstr(Ans)を与え、返り値を変数に代入させることでTrueかFalseを得ることができます。
この順番で並べるだけで、基本的にはゲームが成立するのですが…。
ユーザ入力処理と正解比較処理はクリアするまでループさせる必要があり、ループ判定条件として正解比較処理の返り値であるTrueとFalseが使えるので、この2処理をwhileループで挟んでいるわけなんですね。

while End==False: #継続条件:EndがFalseである間 終了条件:EndがTrue(Falseでなくなる)のとき
    Num=user.input_num()
    #print(Num,Ans) #デバッグ用
    End=compare.cmp_num(Num,str(Ans)) 

そして、whileループを抜ける=ユーザが正解したということで、最後に「正解です!」と文字を表示させてこのプログラムを終了するようにしています。これで、ゲームとして動作するようになりました!

追加機能「正解するまでにかかった時間」の実装

このままでも十分楽しいゲームだと思うのですが、やはりやり込み要素は欲しいところ。
というわけで、正解するまでにかかった時間を計測して表示させる機能をつけましょう。
そこで登場するのが先ほどimportで取り込んだtimeモジュールです。
「time.time()」とすることで現在時刻を取得することができます。これを用いて、ゲーム開始時刻をStart変数に格納、終了時刻をStop変数に格納して、この差を用いて正解するまでにかかった時間を求めて表示させます。

print("正解するまでにかかった時間:{0}秒".format(round(Stop-Start,2)))

解答にかかった時間=(終了時刻)-(開始時刻) で求められますが、あまりに細かい時間を表示させても意味がないので、無難に小数点第2位まで表示させるために四捨五入を行ってくれるround()関数を利用しています。
print()関数にformatが使われていますね。これは文字列に変数を組み込む際に使う形式です。print("文字列{0}",format(変数))とすることで、{0}内にformat()内の変数を組み込むことができます。

実際に動かしてみる

さあ、プログラムが完成しました!実際に起動して動作を試してみましょう。
プログラムを実行するには、以下のようにコマンドを打つ必要があります。

$ python3 main.py

python2系だと恐らく動きません。(3系で今までプログラムを作成してきたため)
で、遊んでみた結果がこちら。

開始するにはEnterキーを押してください...
正解だと思う正の数字4桁を入力してください:1234
HIT= 1 BLOW= 1
正解だと思う正の数字4桁を入力してください:1111
HIT= 0 BLOW= 0
正解だと思う正の数字4桁を入力してください:2222
HIT= 0 BLOW= 0
正解だと思う正の数字4桁を入力してください:333
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:3333
HIT= 1 BLOW= 0
正解だと思う正の数字4桁を入力してください:4111
HIT= 0 BLOW= 1
正解だと思う正の数字4桁を入力してください:1411
HIT= 0 BLOW= 1
正解だと思う正の数字4桁を入力してください:3111
HIT= 0 BLOW= 1
正解だと思う正の数字4桁を入力してください:1311
HIT= 1 BLOW= 0
正解だと思う正の数字4桁を入力してください:5678
HIT= 0 BLOW= 2
正解だと思う正の数字4桁を入力してください:5555
HIT= 0 BLOW= 0
正解だと思う正の数字4桁を入力してください:6666
HIT= 0 BLOW= 0
正解だと思う正の数字4桁を入力してください:7384
正解です!
正解するまでにかかった時間:66.03秒
また挑戦してくださいね!

難しい…!
しかし、特に問題もなく動いていることが分かりますね。
ちなみに、変なことばかりをするユーザになりきってもう一度遊んでみました…。

開始するにはEnterキーを押してください...aaaaaa
正解だと思う正の数字4桁を入力してください:dete
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:hoge
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:ギブアップ
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:GiGive up
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:111122
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:djawjdawlpdmwapdaw@da
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:1234
HIT= 1 BLOW= 1
正解だと思う正の数字4桁を入力してください:nwopdawpodnapodaw
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:amdwapampoa
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:Give up
諦めます。また挑戦してくださいね!

Enterキーを押せと言っているのに"aaaaa"と入力する、日本語も入れる、デタラメを入れる、数字が4桁以上…など、あらゆる動作を盛り込んでみましたがすべて弾かれていますね。問題なく例外処理ができている証拠です。

今後の課題(改善検討案)

このゲーム「4Numbers」を最大限に楽しむコツとしては、「1111」などをデタラメに入れて正解の4つの数字を当てて行くのではなく「いかに手数を少なく、かつ早く解答まで持っていけるか」にあります。
よって、今後検討すべき追加要素の一つとして「解答までの手数を表示する」というのも良いかもしれませんね!
さらに、現在はpython3で.pyファイルを指定してゲームを起動していますが、世の中のゲームというのは.「4Numbers.exe」といった実行形式のファイルになっているものです。なので実行形式にしてダブルクリックで起動できるようにする、というのも検討してみても良いかもしれません!

まとめ

  • 今まで作ったプログラムを組み合わせてゲームを作成できた!
  • ゲームのやり込み要素としてタイマー機能を実装することに成功した

今まで長々とやってきましたが、ここで【プログラムよ、とにかく動け ~4桁のヒット&ブローゲーム「4Numbers」を作ろう~】は終了になります。お疲れさまでした!最後までご覧いただいた皆様、ありがとうございました!
また、なんとかブログとして形に残せた自分を褒めたいと思います。よく頑張った、自分。

もしかしたらEX編で何かするかもしれません(改善検討案を実装する、など)。その時はどうぞよろしくお願いします!
以上、みなくんの「とにかく動け!プログラミング講座」でした!

Apple Watchデビュー

普段iPhoneを使っている私にとって、新しい腕時計型デバイスであるApple Watchは凄く気になっていました。
iPhoneと連携することで通知を素早く確認できたり、Apple Payで便利に支払いができたり…メリットが多くあるようで、腕時計なのにあれこれ出来るって近未来感凄いですよね。
友人が随分前に購入して着けはじめ、便利だと聞いていたので欲しいなと思っていたのですが…決して安いものではないので、1年ほど悩んでいたわけです。
で…遂に今日。

手に入れちゃったんですねー、Apple Watch
実は何度か買おうと思ってたんですが、学生には決して安い金額ではなかったので…機能性についてネットで見てても、あまりいい声を聞かなかった(自分にとって魅力的と感じるものが少なかった)ので、購入とまでは行かなかったのです。
でも使わないと便利かどうかも分からないですし、友人も使いやすそうにしていたので…思い切ってみました。
これからこのApple Watchと共に暮らす訳ですが、少しでも便利で、もっと効率的で…豊かになってくれるといいですね。(買って良かった、と思えたらいいな…)
便利なApple Watchの使い方とか探してみなきゃ。
そんなこんなで、報告的な日記でした。

プログラムよ、とにかく動け ~4桁のヒット&ブローゲーム「4Numbers」を作ろう~その3

更新が大幅に遅れました。私がサボっていたせいです。許して。

今回の目標

  • [Chapter.3] 正解桁との比較処理の実装

[Chapter.3]正解桁との比較処理

前回までの処理で「ユーザが正解だと思う4桁の数字」を取得できるようになったところで、Chapter.1で実装した正解の4桁との比較処理を行いましょう。
あれこれ説明しようかと悩んだのですが、これは実際にコードを見てもらって解説を加えた方が読みやすいかなと思ったのでいきなりコードを載せたいと思います。

class compare_number:
    def cmp_num(self,number,answer):
        hit = 0
        blow = 0 #ヒット・ブロー数
        if(answer == number): #完全ヒットであった場合
            print("正解です!")
            return True #ループ終了の合図
        else: #違う場合
            for i in range(4):
                if(answer[i] == number[i]): #まず部分ヒットかどうか調べる
                    hit += 1
                else: #部分ヒットではなかった場合
                    j=0
                    while(j<4):
                        if(i == j): #部分ヒット判定済箇所は除外するためjをカウントアップ
                            j += 1
                        if(j == 4): #カウントアップによるセグメンテーションフォルトの回避
                            break
                        if(answer[i] == number[j]):#ブローを発見した場合
                            blow += 1
                            break
                        j += 1
        print("HIT=", hit,"BLOW=",blow) #結果表示
        return False #ループ継続の合図

プログラムの流れを簡単に示します。

  • hit=ヒット数,blow=ブロー数として変数を設定
  • 以下に示す手順に従って判定処理を行う
    (1) 正解であった場合
     - 「正解です!」と表示してゲーム終了フラグを「True(終了)」として処理終了
    (2) 不正解であった場合
     - 条件(1桁目が部分ヒットであるか)→部分ヒットの場合は変数hitをカウントアップして2桁目を見る
     - 1桁目が部分ヒットではない→whileループによるブロー処理開始
     - ブロー処理が終了→ヒット数とブロー数を表示、ゲーム終了フラグを「False(継続)」として処理終了

流れとしては実に簡単ではありますが、もう少し具体的な処理についてみていくことにしましょう。

ヒット・ブローをどうやって判定していくか

正解?不正解?

そもそもユーザが導き出した答えが正解かどうか、これはもちろん最初に判断すべきことだと思います。コードでは以下の部分になりますね。

if(answer == number): #完全ヒットであった場合
    print("正解です!")
    return True #ループ終了の合図

answer=機械が出した数字、number=ユーザが出した数字 になっています。つまりこの条件は「機械が出した答え=ユーザが出した答え(完全一致)」となる時に処理される内容なんですね。
return文で「True」を返り値としています。これは設計図に書いた「3.正解桁との比較」から「2.ユーザが任意の4桁を入力」に戻る(ループする)ために利用する返り値で

  • Trueならループしない(ゲーム終了)
  • Falseならループする(ゲーム継続)

という仕様になっています。(これは次回のプログラミング講座「メイン関数を作ってゲームを完成させよう!」で使う値になります)
ちなみに不正解であれば完全一致とはならないので、この条件内の処理は行われません。

不正解→部分ヒット?ブロー?

機械が出した答えと違った時、このゲームではヒントとして「部分ヒット数=2、ブロー数=1」のような情報を提示するため、その判定処理が必要になります。一番今回で難しいところはここでしょうね。今回実装したアルゴリズムもまだ改善できるのかな…などまだ悩みどころは多いのですが、とりあえず今回は動いているので良しとします。
ブロー処理の具体的な処理の流れを、実例とコードを用いて説明していきましょう。見ただけで大体分かるわ、って人はここから少し長いので次の章へ進んでください。

(例) 正解=1234でユーザの答え=2537の場合(HIT=1 BLOW=1)
(1) 1桁目の比較(正解 "1" ユーザ "2")→部分ヒットではないのでブローがあるか調べる(if文の条件が満たされないためif文中処理は実行されない)
コード(answer[i]=1,number[i]=2,i=0)

for i in range(4): # i=0,1,2,3の順で増加
    if(answer[i] == number[i]): #まず部分ヒットがあるかどうか調べる(条件を今回は満たさない)
        hit += 1 #この処理は行われない!

(2) 正解1桁目(桁変数は"i")とユーザ1桁目(桁変数は"j")のブロー探索→既にi=j=0のパターンは部分ヒットの際に比較済みなのでユーザ2桁目から探索することにする(jをインクリメントする)
(i=0,j=0 "j"はユーザが答えた数字を格納する配列number[]に用いる変数)

j=0
while(j<4): 
    if(i == j): #部分ヒット判定済箇所は除外するためjをカウントアップ
        j += 1

(3) 1桁目「1」と2桁目「5」の比較→ブローではない→if文「ブローを発見した場合」の処理は行わず、ユーザ3桁目の比較に移る(jをインクリメント)

if(answer[i] == number[j]):#ブローを発見した場合
    blow += 1
    break
j += 1 #←この処理だけを行う!

(4) (3)を最後まで繰り返す→ブローは存在しなかった

(5) 正解の2桁目の比較(answer[i]=2 number[i]=5 i=1)→部分ヒットではないのでブローがあるか調べる
(6) i=1,j=0のとき…answer[i]=2 number[j]=2となり条件を満たす!→blowをインクリメントする

if(answer[i] == number[j]):#ブローを発見した!
    blow += 1 #blow=1にする
    break #これ以上他の桁と比較する必要はないのでjのwhileループを抜ける
j += 1 

(7) これ以上他の桁と比較する必要はないのでjのwhileループを抜け、正解の3桁目の比較へ移る
(8) 正解の3桁目の比較(answer[i]=3 number[i]=3 i=2)→部分ヒットなのでhitをインクリメント
(9) 部分ヒットしたためブローチェックの必要なし。次の桁の比較に移る
(10) 正解の4桁目の比較→部分ヒットなし→ブローチェック→ブローなし
(11) ヒット数とブロー数を表示、返り値をFalseとして処理終了

ちなみに、変数"i"のforループで正解桁を動かし、変数"j"のwhileループでユーザ桁を動かすようにすることで、以下の動きを実現しています。

  • whileループを抜ける→ブローチェックの終了
  • forループを抜ける→正解桁との比較終了

また、変数"j"の値をforループではなくwhileループにしているのには訳があります。
変数"j"のforループ中に変数"j"の値を累計代入しても、ループ時に変数"j"の値は変更されるという仕様が存在したためです。この仕様が適用されていると、変数"j"をインクリメントしても次のforループで値が上書きされてしまうので、比較処理ができなくなるんです。だからwhileループを用いたわけです。

言葉じゃ見にくいわ!って人へ

先ほどの(1)~(11)までの動きが説明が下手だから分からない…という人はこちらの図を参考にしてみてください。
f:id:minamint:20180418225825p:plain

プログラムの実行結果

最初に提示したプログラムの下に以下を加えて実行してみます。

#---test seciton---

hoge=compare_number()
print(hoge.cmp_num("2537","1234")) #hit=1,blow=1
print(hoge.cmp_num("1235","1234")) #hit=3,blow=0
print(hoge.cmp_num("4321","1234")) #hit=0,blow=4
print(hoge.cmp_num("1234","1234")) #hit=4

#---test end----

実行結果は以下の通り。

HIT= 1 BLOW= 1
False
HIT= 3 BLOW= 0
False
HIT= 0 BLOW= 4
False
正解です!
True

正しく動作していることが分かりますね。また、hit数とblow数が重複していないということも確認できます。でも少し心残りですね…例題ではユーザが真面目な方なので、重複しない4桁の数字を入力してくれているのですが…。

「1111」がユーザの解答だった時、プログラムはどう動く?

テスト実行だと私が期待する値を入力値に設定できるのですが…実際に他の人に動かしてもらう際、そのユーザは私の期待通りに動いてくれるとは限りません(別に信頼してないわけじゃないですよ!)。そのため、予想される入力値とは全然違った値が入力されることもあります。
「-123」や「aaaa」などは前回の入力処理で受け付けないようにできたのですが、まだもうひとパターンだけ怪しいものがあります。
それは「1111」といったゾロ目や「1212」といった数字が重複している4桁の場合。
恐らく前回のプログラムを読んだ方は疑問に思っていたのではないでしょうか。
で、この重複パターンを入力した際に今回作成した比較プログラムがどういった動きをするのか…少し考えてみましょう。
とはいっても考えるより行動した方が早い、ということでtest sectionを以下のように書き換えて実行してみましょう!

#---test seciton---

hoge=compare_number()
print(hoge.cmp_num("2525","1234")) #hit=0,blow=1 2がblow、しかも2が重複している。
print(hoge.cmp_num("1212","1234")) #hit=2,blow=0 "12"がhitだが、後ろの12もblowになってしまう…?
print(hoge.cmp_num("4422","1234")) #hit=0,blow=2 4と2がblow、でも両方重複している。2と4が解答にあると分かってれば使える位置特定手段。
print(hoge.cmp_num("2244","1234")) #hit=2,blow=0 さっきの逆。これで2と4がどちらにあるか分かるが、大まかにしか分からないのであまり良い手段とも言えない。
print(hoge.cmp_num("1111","1234")) #hit=1,blow=0 全部1。その桁が正解に含まれるかどうかを調べるのに使える戦法だったりする。ズルいけどアリ。

#---test end----

気になる実行結果はこちら…

HIT= 0 BLOW= 1
False
HIT= 2 BLOW= 0
False
HIT= 0 BLOW= 2
False
HIT= 2 BLOW= 0
False
HIT= 1 BLOW= 0
False

なんと!予想通りの結果になっている…!
それもそのはず。処理中に行った以下の動作が、重複カウントを防いでくれてたんですね。
* 最初に部分ヒットか調べる→ヒットしていれば次の正解桁の比較を開始してブロー処理を行わないので、正解桁以外にその数字が書いてあっても判定されない * ブローを発見したら次の正解桁との比較に移る→ブローの重複カウントが行われない * 部分ヒットで調べたユーザ桁はインクリメントでスキップ→ヒット・ブローの重複カウントが行われない 実装の際はここまでの配慮をしないとユーザの動きに対応できないんですね…簡単そうなゲームなのに、実は難しかったわけだ…。

まとめ

  • 正解桁との比較処理の実装ができた
  • 重複した数字との比較でも正しい動作をすることが確認できた

次回で最後となります、メイン関数の実装・プログラムの完成編です!

補足(whileとforループの使い分けについて)

変数"j"のforループ中に変数"j"の値を累計代入しても、ループ時に変数"j"の値は変更されるという仕様について、実際にどうなってしまうのかをインタプリタ上でテストして検証してみます。

>>> for i in range(4):
...     print("for:",i)
...     i=i+2
...     print("i+2=",i)
...     print(" ")
...

実行結果はこちら。

for: 0
i+2= 2

for: 1
i+2= 3

for: 2
i+2= 4

for: 3
i+2= 5

といった具合に、(forでカウントされた値)+2になっている&forループの値が演算後の値になっていないことが分かります。恐らくループする度に変数"i"にrange()の値が代入されていくから演算後の値が上書きされているのでは…。
これのせいで、今回の処理だと変数"j"がforループの時に正しく処理してくれないというわけです。よって、whileループを今回は用いることにしました。

Twitter依存な私はタスクもリプで教えてもらう

目次

背景

  • とにかく面倒臭がり
  • 忘れっぽい
    こんな性格の男が悩んでいることが一つある。それは「タスク管理が面倒すぎる(タスクを確認することすら忘れる)」ということ。
    もっと簡単に予定やタスクが追加できて、しかも向こうから「今日これがあるよ!」と通知してくれればよいのに。
    そこで、今回自分の愛用ツールをうまく利用したら便利にならないかと考え、こんなアイデアが浮かんだわけです。 「そうだ、Twitterで全部管理できたら楽じゃん!」と。
    目次に戻る

ゴール

  • Twitterでツイートした特定の内容を、明日リプで通知してもらう
  • Google home くんに「明日は~がある」と言えば、明日リプで通知してくれる
    目次に戻る

    なぜTwitterなのか

    LINEでもいいじゃないか、LINEならIFTTTのNotifyで通知できるわけだし。
    わかる。でもTwitterの方が開くから目につきやすいんだよ。だから私はTwitterがいい。
    目次に戻る

Twitterでタスク管理を行う

  • Twitterでツイートした特定の内容を、明日リプで通知してもらう

    理想

    今日:「#明日通知して 図書館の本を返す」とツイート(タスク追加)
    明日:「今日のタスク: 図書館の本を返す」とリプが飛んでくる(本日のタスクを通知)
    目次に戻る

    実装に使うもの

  • IFTTT (一番使うもの)
  • Twitterアカウント2つ (リプを送ってくれる奴、リプを受け取る奴)
  • Googleカレンダー (タスク管理に使う)
    目次に戻る

    実装してみよう

    この機能を実現するには、以下のような動きをする必要があるわけですね。

  • まずツイートを検知(#(ハッシュタグ)がキーワード)
  • ツイート内容をタスクとして保存(カレンダーに登録する)
  • 指定日時になったらリプを飛ばしてもらう (カレンダーから情報取得)

    なぜカレンダーでタスク管理をするのか。それは簡単で、IFTTTならGoogle カレンダーを使うと「いつイベントが始まるか」というのを指定でき、イベント開始時にその情報を用いてリプを飛ばすことが出来るからです。
    他にもいいのがあればそっちを使ったんだろうけど、サーっと調べて簡単だと思ったのがこっちだったので今回はこういう方針で実装することに。
    目次に戻る

    まずはIFTTTの設定を

    さまざまなサービスが連携できる「IFTTT(イフト)」の使い方を参考にサインアップ。使い方もここで学習しました。 IF ●● Then ■■ 「もし●すれば、■をする」という、一連の命令を簡単に作成することができます。例えば、「もし明日が雨なら、LINEに通知する」といった感じに。これはとても直感的にできるので便利ですね。プログラミング不要というのが私にとって嬉しかったです。
    目次に戻る

    TwitterからGoogle カレンダーに

    ここからは実際の画像と共に、具体的な実装を行っていきましょう。 まずはNew Appletから「this」を選択。今回「this」(トリガー)はTwitter、「that」(アクション)はGoogleカレンダーになります。
    thisでTwitterを選択し、「New tweet by you with hashtag」を選択しましょう。
    これは特定のハッシュタグ付きツイートを自分がしたらそれがトリガーになるというものです。
    まずは「#明日通知して」のタグをトリガーにして、設定しましょう。これでタグ付きツイートが送信されるとこのコマンドが動き出すことになります。

    f:id:minamint:20180214050200p:plain
    特定のハッシュタグをトリガーにすると便利が良い

    次にthatでGoogleカレンダーを選択。時間指定をしたいので、「Create a detailed event」を選択してください。 ずらずらーっと項目が。それぞれ順番に設定していきましょう。

f:id:minamint:20180214050740p:plain

f:id:minamint:20180214050745p:plain
全て英語だけど、調べればある程度分かるので問題はない

  • Which calendar?
    「どのカレンダーに登録するの?」というもの。今回「タスク通知用」というカレンダーを作って登録してますが、実際にこうした方が便利だと思います(本来使ってるGoogleカレンダーが変なメモで汚れないから)。
    カレンダー作成はGoogleカレンダーから直接行ってください。そうすれば選択肢に出てくるはずです。
  • Start time
    開始時刻。この機能が欲しくてGoogleカレンダーを使ったんです。ここに図のように「Tomorrow, 8AM」と入れると、明日の朝8時に予定が入ります。後で通知させるのに重要になります。
  • End time
    終了時刻。ここは別に通知で使わないので適当に1時間後にしましょう。
  • All day?
    そのイベントが終日かどうかを設定。Google homeくんが終日スケジュールを読み上げてくれない事件があったので思わず「No」を選んでしまいました。何となくこれ「Yes」にするとイベント通知来なさそう(未検証)。
  • Title
    ここには「Add ingredient」から「TextNoHashtag」を選択しましょう。これは「ハッシュタグなしの本文」を出すもので、ツイートした内容そのものをタイトルにできるという優れもの。
    「#明日通知して 図書館の本を返す」と通知すれば、タイトル名は「図書館の本を返す」となるわけですね。
    あとの設定は好きなようにして構いません。というのも、通知に影響しないからです。完成したらCreate Actionでアクションを作ります。
    そしてこのIFTTTのアプレットのタイトル名を下図のように好きに弄ってあげれば…
    f:id:minamint:20180214053817p:plain
    セットが完成するわけですね。「On」になっている限りこのアプレットは動作するため、私のツイートを監視してくれていることになります。
    試しに「#明日通知して 家の用事を済ませる」とツイートしてみます。

    f:id:minamint:20180214192420p:plain
    呟いているのが鍵垢故にユーザ名やIDなどは隠しています

    そしてGoogleカレンダーには…
    f:id:minamint:20180214054458p:plain
    オレンジ色のイベントが「タスク通知用」のカレンダー

    きちんと反映されていますね。これでTwitterでタスクを追加することができるようになりました。
    目次に戻る

    GoogleカレンダーからTwitter

    お次は一番大事な「通知してもらう」という機能の実装です。
    「this」(トリガー)はGoogleカレンダー、「that」(アクション)はTwitterになります。さっきと逆ですね。
    Googleカレンダーの設定から。
    いっぱいトリガーの選択肢が出てきますが、「Any event starts」(任意のイベントが開始される時)を選択しましょう。以下の画面が出るはずです。
    f:id:minamint:20180214055336p:plain

  • Which calendar?
    さっきと同様、タスクを入れたところを選択しましょう。

  • Time before event starts
    何分前から通知するか。さっき「朝8時」に開始時刻を設定したので、別に0分でいいでしょう。もっと前にしたかったら最大45分前まで設定できます。
    これでトリガーのセットは完了。次にアクションのTwitterの設定をしましょう。
    アクションの選択肢は最も単純な「Post a tweet」を選択しましょう。
    f:id:minamint:20180214055838p:plain
    @{ユーザ名}にはちゃんとID名を入れましょう

    上図のように設定してあげれば…あとは指定時刻に通知がリプとして来るはずです。確認してみましょう。
    先ほどカレンダーに登録した「家の用事を済ませる」が来るのを確認してもいいんですが…待つのは面倒なので、「タスク通知用」のカレンダーに「テスト通知」という予定を登録して、この通知が来るかを試しましょう。
    f:id:minamint:20180214165715p:plain
    すぐ通知が来るよう、直前でセットしました。この通知が飛んできていることを確認してみます…
    f:id:minamint:20180214170433p:plain
    指定した形式通りに届いていることが確認できる

    ばっちりIFTTTを通じて届いていますね。これで向こうから通知してくれるようになったので、忘れることもなくなりそう!
    目次に戻る

Google homeくんにもタスク管理してもらう

  • Google home くんに「明日は~がある」と言えば、明日リプで通知してくれる

    理想

    私:「ねえ,Google。明日は『テストがある』」
    Google home miniくん:「わかりました。明日は『テストがある』ということをTwitterでお知らせします。」
    目次に戻る

    実装に使うもの

  • IFTTT (一番使うもの)
  • Google assistant (Google home miniくんの中身)
  • Googleカレンダー (タスク追加にはやっぱりこれ)
  • Google home (miniなども含む) (これがないと始まらない)
  • GoogleカレンダーからTwitterにで作成したアプレット 目次に戻る

    実装してみよう

    この機能実現のために必要な動作は以下の通り。

  • Google assistantに「明日は~」というフレーズを認識させる
  • 「(明日は)~」をGoogleカレンダー「タスク通知用」に登録する
  • Google assistantにその行動が完了したことを言わせる
    つまり、Google assistantが私の代わりにカレンダーに予定を入れてくれたらOKというわけです。
    というか、Google homeくんって実はGoogle assistantだったんですね…(今知った)。だから、assistantくんを弄ればGoogle homeくんも反映してくれると。これは使いこなせたらもっと便利になりそうな予感。
    目次に戻る

    Google assistantからGoogleカレンダー

    「this」(トリガー)がGoogle assistant 「that」(アクション)がGoogleカレンダーになります。
    いくつかあるトリガーの中から、「Say a phrase with a text ingredient」(テキストを含むフレーズを言った時)を選択しましょう。以下の項目が出てきます。順に設定していきましょう。
    f:id:minamint:20180214173031p:plain

  • What do you want to say?
    「何と言った時?」ここにはフレーズを入れます。今回認識させたいのは「明日は~」。なので、「明日は $ 」と入れましょう。「$」は予定の内容が入ります。これで音声をテキスト文に認識・変換してくれるわけです。
  • What's another way to say it? (optional)
    「他にどんなこと言ったら反応させようか?」別フレーズを登録したい場合はここに。今回は漢字での「明日は」と、Google homeくんがひらがなで認識したときのことを考えて「あしたは」を別フレーズに設定しています。必要かどうかは分かりません。
  • And another way? (optional)
    「他には?」3フレーズ目を入れたい人はここに。合言葉みたいなフレーズを入れて遊ぶと面白いかもしれない。
  • What do you want the Assistant to say in response?
    Google assistantくんはなんとお返事したら?」言わせたいことをここに。ここで「$」を入れると、自分が言った音声文が読み上げられるので確認しやすいです。
  • Language
    迷わず日本語を選択。
    次にアクション側のGoogleカレンダーの設定…なんですが、実はこれ、Twitterでやった設定(TwitterからGoogle カレンダーに)とほとんど変わらないんです。
    変わるのはTitleの項目だけ。ここで、「Add ingredient」を押して「Text Field」を選択しましょう。これで「$」の内容がタイトル名になります。
    で、設定が終わったら名前を決めて保存するだけ…。これで、Google homeくんに話しかけるとカレンダーに予定を追加してくれるわけです。実際の動作をマイアクティビティの会話履歴とカレンダーを参照して確認してみましょう。(動画は割愛させていただきます)
    f:id:minamint:20180214180406j:plain
    マイアクティビティでGoogle homeくんとの会話履歴が確認できる

    f:id:minamint:20180214180735p:plain
    このように、「(明日は)テストがある」と言うと、「テストがある」という予定が翌朝8時に登録されているのが確認できます。
    目次に戻る

    GoogleカレンダーからTwitter

    Twitterの方のGoogleカレンダーからTwitterにで作成したアプレットをそのまま使いましょう。「タスク通知用」カレンダーに予定を追加さえできれば、あとはこのアプレットが動作して通知してくれるわけですからね。
    これで、Google homeくんに話しかけたらタスクを翌日教えてくれるようになりました!
    目次に戻る

まとめ

ゴールに設定したものはIFTTTを用いることで全て実装でき、その動作も確認することができました。これでTwitterから離れる必要がなくなるし、通知も来るから忘れることもなくなりそう!
IFTTTは今回紹介したもの以外にも、様々なサービスを使ってより便利にすることが出来るので、気になった方は調べてみてください。
目次に戻る

余談

今の所実装したのは明日通知するというものだけ。これを曜日指定で出来たら便利だと思って追加実装をしたんですが…月曜から金曜の5パターンをTwitterGoogle assistantそれぞれに設定する必要があるため、合計10個もアプレットを追加で作る必要があり非常に面倒でした。途中で「こんなことするならタスク管理アプリちゃんと使った方が楽そう」と思ったほどです。もう少し手間を省けないものか…。
それに、IFTTTではまだ「カレンダーの予定を消す・変更する」に対応はしていないらしく、間違えたら手動で修正が必要なんだとか。少し面倒だけど対応してないから仕方ないですね。
でもこういう風に便利にしていくのは好きなので、これからも思いついたらとりあえず実装できないか考えて手を動かしてみようと思います。

メリークリスマスとよいお年を!

メリークルシミマス クリスマス

お久しぶりです。みなくんです。メリークリスマスでした。本当はクリスマスの日にブログを書こうと思ってたのですが、すっかり忘れてましたね。すまない。
しかしまあ、クリスマスというのは不思議なものです。皆クリスマスパーティーや高級料理店などで盛大に楽しい夜を明かして。えぇ。お金ないんじゃなかったの?
私もクリスマス当日は午前中のみ大学で勉学に励んで夜KFCさんの美味しいチキンとケーキを食べましたけどね。クリスマスぐらい美味しいもの食べてもいいんじゃない?お金はないけど。
でも、クリスマスイブにパーティーする人も多いらしく、イブの日にバイトをしていた私はかなり苦しめられていました。忙しいのはある意味良い事なのかもしれませんけど…バイトの身で言わせてもらえば、歩いてるだけでお金がもらえることほど嬉しいものはないので、可能な限り暇でありたいですね。 イブの日にバイトなんて可哀想だ、恋人と遊びに行けばいいのに――そういいたい気持ちは分かりますが落ち着きましょう。イブでも働く人が居ないとあの店この店全部閉まります。特別な日に何事もなく開いているあのお店には、ちゃんと感謝しましょうね(何様)。

クリスマスに行われていた「とある技術」を用いた生放送

クリスマス、パーティーしてた人が多いと思うのでテレビやネットを見ていた人って案外少ないのかもしれませんが、クリスマスに最近話題沸騰中のある方々が生放送をやっていました。
割と最近有名になりましたよね。私は数か月ほど前から一人だけ存在は知ってたんですけど、正直ここまで話題になって自分のTwitterまでもを賑やかにするとは思ってなかったです。(流行らず消えるんじゃね?とか思ってたのは内緒)
そう、最近話題といえば、「バーチャルユーチューバー」!
代表的な方といえば、某インテリジェントなAIさんはじめ、イルカさんや「見るストロングゼロ」とあだ名がつく方、はたまた声がどう聴いても「おじさん」なあの方などなど…。
特にここ数か月で一気に増えたような気がします。(一気に増えたというか、注目されて表に出てきた人たちなのかな?)
マイナーなところからいけば、かなり前からバーチャルユーチューバーというのは存在していたようです。恐らく最初は某インテリジェントゥなAIさんでしょうけども。
私も追える限りバーチャルユーチューバーの生放送を見て回ったのですが、どれもこれも面白いですね。新ジャンルというかなんというか…あれです。何か話をしてるだけで面白いっていう。ちょっと羨ましい… 。というか、あのコミュ力が欲しい…。
あらゆる形で皆を楽しませ、元気を届ける職業にはとても憧れます。私自身もかなり楽しませてもらってる身なので、精一杯応援していきます。自分が提供する側には、ちょっとコミュ力的に厳しいけども。 それに、多くの人が最新技術のことを「知り・興味をもって・触れてくれる」ようになる――そうすれば、もっとクオリティの高いものが出来て皆がハッピーになるので、彼女たちが流行ってる今って非常に良い流れなんじゃないかなーって思ってます。そういう意味でも頑張って、バーチャルユーチューバー!(だから何様)

年明けも間近

お正月?んなもんねぇよバイトだよコノヤロー!
頑張って働いて、Viveとかそういうの欲しいなあ…。PSVRは持ってるんだけど、PC用のVRデバイスも欲しいかなって思います。でもグラボの性能が足りない…まずはそこから改善せねば…。
欲しいものがいっぱいある上にどれもこれも高価なので、ただ眺めることしかできないみなくんなのでした。

皆さん良いお年を!

私のPCに起きた変なトラブル

このブログを書いたり、Twitterを眺めたり、ゲームをしたり…またはプログラミングまでもやってのける、我が家の愛用しているデスクトップPCがあります。購入当初はWindows8.1でやってたんですが、無料につられてWin10に変更。特に差支えがなかったので、毎日楽しくパソコンで遊ばせてもらってました。そんな私のPC、Windows10に変えてしばらくしてから変なトラブルが起こるようになりました…。そのトラブルをやっと対処できたので、今回は対処できるまでの経緯とか原因をパラパラと。

シャットダウン後、何故か勝手に起動する

もう寝ようとシャットダウンをして、デスクトップモニタの電源を消す。そして寝ようとパソコンから離れた瞬間、電源ボタンも押していないのにパソコンが再度勝手に起動を始めちゃう。原因がさっぱり分からず、ウイルスなんじゃないかと思ってウイルススキャンしたりあれこれ手を尽くしたけど、異常なし。数か月謎のままでした。最初は放置してたんだけど…やっぱり勝手に起動するのは電気食うし、何か不気味。家族にも怒られるので、なんとかしたかったわけです。なので、何とかならないか、とあれこれ記事を調べることに。

そこで出会ったのが、この記事でした。 Windowsが勝手に起動する場合の原因追及 - 岩崎仁の俺は語りたい!
ほう、イベントビューアーを見れば解決するかもしれない…。以前一度だけイベントビューアーというものが存在するという話を聞いたことがあったのですが、実際に見たことがなかったのでその方法は思いつきませんでした。
やり方を見ながら早速ログを見てみよう!ということで暫くログを眺めていると…。
「(Power-Troubleshooter) システムは低電力状態から再開しました。」という文章が。さらにそこに、「スリープ状態の解除元:Realtek PCIe GBE Family Controller」とも書かれていました。

な ん だ こ れ

Realtek…?どこかで聞いたことあるぞ、と思って調べたらコンピュータ機器の会社名でした。そういえばウチのスピーカーって「Realtek High Definition Audio」だったなあ。じゃあこれは何か機器が悪さをしているのでは…?と思い、解除元の名前をそのまま検索してみました。

Realtek PCIe GBE Family ControllerはLANのドライバだった

検索すると、LANドライバであることが判明。ということは、これはインターネット上から起動できるようになっている…?と思い、さらにこのドライバとシステム起動まわりの関係性を詳しく調べることに。
すると、知恵袋に同じような状態になっている人が。今回の症状、私だけの問題ではなかったようです。
PCが自動でスリープ解除になります。 - 1~2分後にすぐに電源が切れ... - Yahoo!知恵袋

この質問の回答を見ていると、Wake On Lan誤認識が原因なのでは?と。Wake On Lanとはインターネットに繋がったコンピュータの電源を遠隔でつけることができる行為のことのようで、私の予想通りの原因でした。LANドライバというよりは、LANケーブルから伝わったインターネット信号に何らかの原因で外部からノイズが入り、その信号がWake On Lanの信号と誤検出されたために発生した可能性があるかも…と。それなら、Wake On Lanを停止すればいいだけです。この回答者の方の言う通り、設定を弄って接続しているLANから勝手に電源を付けられないようにしてみました。

見事解決

それ以降、あれだけ困っていた勝手に起動する現象も全く起こらず。我が家のPCくんは私の指示に従い、大人しくしてくれるようになりました。あんなに悩んでたのに、たった少しのことで解決してしまうとは。この数か月は一体なんだったんだろう…と、嬉しさ半分悲しさ半分の中、我が家のPCのトラブルは解決してしまったのでした。まあ、まだ解決して間がないので…もしかしたら、再発するかもしれませんが。そのときはまたトラブルシューティング頑張りましょう。お兄さん頑張る。

今回の一件を振り返って

PCって、使いこなしているように見えてもやっぱり一般人である以上知らないことも多いし、普段触れないような機能もあったりするわけで。改めてPCを扱うのは難しいなあ、と思いました。今この時代だからこそ調べればなんとかなるようにはなりましたけど。
「君情報系の学校出身でしょ、このPC壊れてない?直して」なんて言われたとき。ちゃんと直してあげられるんでしょうか。
スマホを見せられて同じことを言われたときもそう。情報系ってだけでデジタル機器を使いこなしてると思われがちなので、そこは地味に気にしています。
正直Andr〇id直してって言われても知らない…使ったこともない、だって私iPh〇neだもの…。
プログラミングとか、ターミナル操作の勉強だけじゃなくて、今回みたいなPCで起こるトラブル周りの対処法とか、そういう勉強も大事だな…と今回の一件で強く感じたのでした。

プログラムよ、とにかく動け ~4桁のヒット&ブローゲーム「4Numbers」を作ろう~その2

今回の目標

  • [Chapter.2] ユーザ入力処理の実装
  • 例外処理によるバグの対策をしよう

[Chapter.2]ユーザ入力処理

今回作成する4Numbersゲームでは、ユーザが正解だと思う数字4桁を入力し、それを正解と比較してヒントを出すという流れになっている以上、ユーザ入力を受け付けなければなりません。今回はこの部分の実装を行うことにします。
ユーザからの入力を得るためには、input()を用いる必要があります。しかし、これ1行書くだけでユーザが入力できるので、コードを書く側からすれば非常に便利であるといえます。
そして、input()の()内には入力する際のコメントを書くことができるため、input("何か入力してください")なんてしておけば、ユーザに入力を求めるコメントを表示することが出来ちゃうわけです。
このinput文の返り値はユーザが入力した文字列であるため、何か変数に入れておけばユーザが考えた4桁の数字を取り出すことができそうです。
ということでインタプリタを使って軽く試してみましょう。

>>> User_number=input("正解だと思う4桁の数字を入力してください:")
正解だと思う4桁の数字を入力してください:1234 #ユーザが"1234"と入力した
>>> print(User_number)
1234

といった感じで、とても簡単にユーザ入力処理を行うことができるわけです。
しかし困ったことに、ユーザというのは自由気ままに動き回る、言わば決まったアルゴリズムに従っていない存在であるため、予期せぬ行動をする可能性があります。ユーザが素直に4桁の数字を入力してくれればいいのですが…。少し、インタプリタを使ってこんなテストをしてみましょう。

>>> User_number=input("正解だと思う4桁の数字を入力してください:")
正解だと思う4桁の数字を入力してください:12345566 #ユーザが4桁以上の数字を入力した
>>> print(User_number)
12345566 #そのまま出力されてしまう
>>> User_number=input("正解だと思う4桁の数字を入力してください:")
正解だと思う4桁の数字を入力してください:みなくん #数字ではない文字を入力した
>>> print(User_number)
みなくん #文字列もそのまま出力される
>>> User_number=input("正解だと思う4桁の数字を入力してください:")
正解だと思う4桁の数字を入力してください:↓ #そのままEnterキーを押した
>>> print(User_number)
 #何も表示されない 
>>> User_number=input("正解だと思う4桁の数字を入力してください:")
正解だと思う4桁の数字を入力してください:-123 #ユーザが負の4桁の数字を入力した
>>> print(User_number)
-123 #そのまま出力されてしまう
>>>

ユーザが言うことを聞かなかった場合、何もしないと4桁の数字ではないモノまでもが変数に格納されてしまうわけです。これを放置すると、ユーザが正の4桁以外の数字を入れたときに正解と比較できずエラーが発生する要因となるため無視はできない問題です。
ではどう対処するのか。そこで登場するのが例外処理です。
ある特定の例外が発生した場合の処理…ここで言うところの、4桁の数字以外が入力された場合の処理を書いてしまおう、というもの。
この例外処理をするには、Pythonではtry・except文を用います。
try文中に書く処理は「とりあえずやりたい動作」。今回で言うところの「ユーザ入力」がこれに当たります。 except文は、簡単に言うと「try文中の処理をしてて問題が発生したとき、○○の場合はこんな処理をするよ!」ということを明記するのに使います。今回は「ユーザが正の4桁の数字以外のものを入れた場合の処理」を書くわけです。
で、肝心の「例外」の指定なのですが、今回は数字を入力させるわけなので、「ValueError」という例外を用います。これは簡単に言えば文字列をintに変換しちゃった場合に発生する例外。他の例外のバンドルについて気になった人は調べてみてください。色々あって面白いです(投げやり)
言葉で説明しても「ふーん」ってなると思うので、ちょっとしたコードを書いて試してみましょう。

>>> try: #とりあえずやりたい処理を記述
...     number=int(input("Enter some number:")) #数字を入れてもらうよう頼む
... except ValueError: #例外発生時の処理(数字を入れてくれなかった場合)
...     print("Error:not a number")
...
Enter some number:a #言うことを聞かないユーザなので'a'を入れちゃう
Error:not a number #ちゃんと怒られる
>>>

ちなみに、input()を使う際にint()を使っています。これは変数やinputで入力されたものをint型に変換させるやつで、先述したようにValueErrorの発生条件は「文字列をintに変換した場合」であるため使っているわけです。
では実際にコードを…と行きたいところですが、これだけではまだ不足。もう少し、工夫が必要です。今使った例外処理は「ValueError」。文字列ならこれで処理出来ますが、4桁より上(未満)の桁の数字だったら?負の数字だったら?――この場合について、考えていきましょう。
4桁かどうかの判別だけなら簡単です。len()を用いて、"if len(number)==4:"とするだけ。4桁でないならまずここではじかれるので、4桁の数字、文字列以外はすべてこの段階ではじくことができます。問題はそれを例外として処理しなければならないこと。ifではじいたはいいけれど、4桁未満の数字であった場合…ValueErrorは動作しません。理由は先述した通り。数字はint型変換が正常に出来てしまうので、ValueErrorが動かないんです。ということは、何も手を施さなければ例外処理をしてくれないということ。それでは困るので、一文だけ手を施してあげましょう。それはraise文の使用です。これは例外を発生させるために使います。もっと本当は深く意味があると思うのですが、ここではその程度の理解で使うこととします。実際にどのような書き方をするのか、それは以下の通り。

try:
    number=int(input("enter 4 numbers:"))
    if len(number)==4:
        print("OK!")
    else:
        raise ValueError
except ValueError:
    print("Error:not a numuber or not 4 numbers")

raise文をelse文中に使うことで、「4桁じゃないならValueErrorとして例外処理するよ!」と明記することができるようになります。
次に負の数について考えてみましょう。負の4桁、といえば「-1234」です。これなら、文字数は5になるため、上記のif文上では「4桁ではない」とはじくことができるでしょう。しかし、負の3桁だったら「-123」です。これだと、文字数が4となってしまい、if文は通過してしまうわけです。そもそも、負の桁なんて正解桁に設定していないわけですから…「-」がついてたら困りますね。なので、if文として「最初に"-"がついてたら例外処理する」という風にしましょう。ユーザが入力した値は配列として扱うことができるので、「if number[0]=="-"」とするだけ。この条件を満たしていたら、"raise ValueError"で例外処理をさせてしまいましょう。これで、考え得る例外の処理を行えたことになります。
では、コードを書く前の説明はこの程度にして実際にユーザ入力と例外処理のプログラムコードを書いてみましょう。

class input_number:
    def input_num(self):
        while True: #4桁の数字かGive upが入力されるまでループ処理
            try: 
                num=input("正解だと思う正の数字4桁を入力してください:")
                if len(num)==4: #4桁であった場合
                    check=int(num) #それが文字列であった場合、ここでValueErrorに飛ばす
                    if num[0]=="-": #負の数であった場合
                        raise ValueError #それはエラーとして処理
                    return num #何もなければユーザの入力を返り値にする
                else: #4桁以上の数字であった場合
                    raise ValueError
            except ValueError: #文字列が入力された場合(今回は4桁以上の数字もこのエラー処理になる)
                if num=="Give up": #Give upと入力された場合
                    print("諦めます。また挑戦してくださいね!")
                    exit()
                else: #他の文字列もしくは4桁以上の数字が入力された場合
                    print("4桁の正の数字かGive upのみを入力してください。")
    def start_init(self):
        input("開始するにはEnterキーを押してください...")

#---test seciton---

hoge=input_number()
hoge.start_init()
print (hoge.input_num())

#---test end----

先述したものに加え、何か色々味付けをしているのが分かると思います。コメント文を観たら理解できるかもしれませんが、一応味付けしてるところの説明はここで軽くしておきます。
まず、whileループをinput_numメソッド中全体に使用しています。これはユーザが正しい値を入力するまでループさせるために使用。
そして、プログラムよ、とにかく動け ~4桁のヒット&ブローゲーム「4Numbers」を作ろう~その1で記載していた「ユーザが"Give up"と入力したら諦められるようにする」という処理をこっそり追加しています。
"Give up"は文字列であるため、処理させるのであればexcept文の例外処理中に行うのが一番楽です。なので、except文の最初にif文でその処理をさせ、プログラムを終了させています。Give up以外は再入力を求める文章を表示して終わりに。このような書き方だけで、数字/文字列の判別、桁数の判別、"Give up"機能の追加までが行えます。ちなみに、test sectionを付けた状態でこのプログラムを実行させた結果が以下の通りになります。
実行結果

正解だと思う正の数字4桁を入力してください:a 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:aaaa 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:-123 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:12-3 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:12 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:12345 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:1234
1234

実行結果(Give upを使った)

正解だと思う正の数字4桁を入力してください:GIve up 
4桁の正の数字かGive upのみを入力してください。
正解だと思う正の数字4桁を入力してください:Give up 
諦めます。また挑戦してくださいね!

ちゃんと動作していることが確認できました。これで、今回の目標は達成できたことになります!

まとめ

  • ユーザ入力処理の実装を行うことができた
  • 例外処理を使って考え得るバグの対策ができた
  • 入力処理はユーザが素直じゃないことを想定したプログラミングが必要だった!

こういうのをやってると、他のウェブサイトやソフトウェアで同じような場面を見かけたときに「あぁ…これユーザがこんな入力したらちゃんと例外処理するようにしてるんだなあ。大変だろうなあ」と思ってしまったり。僕だけなんでしょうか。街中で7セグメントLEDをみたり、ボタンを押したりするときに「これ、こういうプログラムで動いてるんだろうな…」とか考えたりするの。誰か共感してくれたら嬉しい……。

次回はユーザの回答と正解の比較を行う処理についてやっていきます。

プログラムよ、とにかく動け ~4桁のヒット&ブローゲーム「4Numbers」を作ろう~その1

今回の目標

毎回この講座的なアレは目標を最初に書いてからやって行こうと思います。というのも、自分の性格上冗長になりやすい性質があって話が無限に脱線してしまうからです。
あとは最初の所をみてもらえば何をやってるかが理解してもらえるかな、と。

目標

  • 「4Numbers」で実際に行われている処理を明確にして(または処理したいことを盛り込んで)「設計図」の作成
  • [Chapter.1] ランダムで重複のない数字の生成

はじめの一歩-設計図の作成-

まずは今回の処理を大まかに説明できるような設計図を簡単に作成したいと思います。私もこれがないとコードが書けなくなってしまうので、アイデア保管庫的な意味合いも込めてこのセクションを入れています。
今回の設計図はこちら。(汚くてすみません) f:id:minamint:20170618160330p:plain
まずは流れを書き、「どんなことをするのか」を明確に。次に「それを実現するにはどんな処理が必要なのか」を端的に。これさえ見れば、書かずとも少しはイメージが浮かぶのではないでしょうか。
今回のプログラミング講座では、この設計図を基にプログラムを作成していきたいと思います。
なんでわざわざ設計図なんて書いたんだ、とかそういう諸々については「蛇足ですが」に長々と。

Chapter.1 ランダムで重複のない数字の生成をしよう

では早速設計図の1番目に書いてある「重複のないランダム4桁の生成」を行いましょう。実はこれとても簡単で、pythonに準備されている関数を使うだけで簡単に出来ちゃうわけです。
それは、randomモジュールのsampleメソッド。これを用いるだけで簡単に重複のないランダム数字が生成できてしまいます。インタプリタで軽く例題として出力してみると…

>>>import random
>>>test=random.sample(range(10),4)
>>>test
[2,4,9,5] 
>>>#複数回ランダムに数字を生成してみる
...for i in range(5):
...    test=random.sample(range(10),4)
...    test
...
[8,2,5,4]
[6,4,8,9]
[7,4,2,1]
[8,1,7,2]
[1,4,0,8]
>>>exit()

といったように、たった1文(importするので2文ですが)だけ書けばすぐに正解桁は作れてしまうわけです。ではこれを実際にパーツとして組み込んでいきますが、ここで一つ問題が発生します。
見て分かるように、ランダムで取った値はリストに入れられており,連続する4桁の数字ではないわけです。そこで、この4つの数字を4桁にするという処理を行うことにしましょう。
操作は簡単です。4つの数字をa,b,c,dとするならば、a×1+b×10+c×100+d×1000にするだけです。これで誰もが納得する4桁の数字になりますね。
納得出来ないですよね。知ってました。
4桁の数字にならない可能性があります。それはd=0であったとき。0に何をかけても0なので、1000の桁が存在しないことになり、3桁になってしまうんですね。
さあどうしましょう。
数字のままだと0から始まる値なんて作れないので、文字列に変換して最初に0を付け足してしまえば簡単なので、今回はそうしてしまいましょう。
文章であれこれ説明したものを実際に組み込んだのが以下のコードになります。コードがクラスを用いて書かれていますが、各chapter毎に作成したものをパーツとして利用するためにやっています。この方が最後のメインプログラムを書く際に今まで作ったものをインポートして利用するだけでプログラムが完成するため、非常に便利になるわけです。

import random

class create_random:
    def create_num(self):
        data = random.sample(range(10),4) #0-9のなかで4桁をランダム&重複なしに設定
        number=0 #正解桁を返す際の変数定義
        mul=[1,10,100,1000] #掛け算に使用するリスト
        for i in range(4):
            number += data[i] * mul[i] #ランダムで取り出したバラバラの数字を4桁の数字にしていく
        number=str(number) #数値を文字列に変換(この方が都合が良いため)
        if len(number)==3: #3桁しかない,即ち0から始まる値(0123など)の場合
            number = "0" + number #0を最初に付け加える
        return number
#---test section---

#number=create_random.create_num()
#print(number)

#---test end---

実行結果(結果は毎回変わります)

8390

0から始まる値になった場合の実行結果(0以降の値は毎回変わります)

0743

test sectionはちゃんと動作してくれるかを確かめているメモ書きみたいなものなので、本作成時には使いません。このコードを動かすと、ランダムな数字が表示され、ちゃんと動作していることが確認できます。
さて、コメントで説明はしていますが、例の問題の解決策として、数値を文字列に変換した後if文で3桁の場合の処理を追加しました。3桁であるなら、最初に0を付けて4桁にする。ただそれだけです。これを数値のまま0を付け足す処理を行おうとすると、「int型を持って来られても、str型でしかその処理出来ないので無理」と怒られるので気を付けましょう。
これで正解桁の準備ができましたね!次はユーザの入力処理を行っていきましょう!

まとめ

  • 設計図を書いて大まかな動きを理解した
  • ランダム数字生成はrandom.sample()で簡単に行えた!
    以上、案外あっさり終わってしまって少し困惑したみなくんでした。

蛇足ですが

設計図の必要性について長ったらしく生意気に語ります。自分へのメッセージとして。
例えば、「Push "1" to say "hello"」という文章を表示し、ユーザーに「1」と入力された場合に「Hello!」と返す単純なプログラムを書くとしましょう。
この例題をあなたがお好きな言語でコードを書いて解くとき、どうやって解いているでしょうか。感覚で解きますか?それとも「あれがこうなって…」と脳内で考えながらその場でパパっと書きますか?
今回の例題は動かすことだけを考えた場合実に簡単です。以下にcとpythonでの例題に沿ったコードを乗せてみます。
test.c(c言語)

#include<stdio.h>

int main(void){
    int i;
    printf("Push \"1\" to say \"hello\"\n");
    scanf("%d",&i); //ユーザからのキーボード入力を受け付ける
    if(i==1) //もし1だったら
        printf("hello!\n");

    return 0;
}

test.py(python言語)

print("Push \"1\" to say \"hello\"")
test=input() #ユーザからのキーボード入力を受け付ける
if test=='1': #もし1だったら
    print("hello!")

これで半角数字「1」を入力された場合、仕様通りの動作をするプログラムが完成します。
さて、この例題は簡単だったので、感覚で解くというのは容易だったと思います。python3系使いの方で感覚で解いた方は、恐らく「「Push "1" to say "hello"」という文章を表示("print()"で一発)し、ユーザーに「1」と入力された場合("input()"で一発)に「Hello!」と返す("print()"で一発)」と脳内で考えてコードを書いたことでしょう。 もっと簡単に言えば「喋って入力させて喋ったら終わりでしょ?簡単じゃん」――仰る通りです。
じゃあ、こんな例題はどうでしょうか。「スタックを用いてユーザの入力したデータを保存し、それを最後に結果として出すプログラムを作ってよ」と言われた場合。
最初の例題と同じく動作説明は明確に行われていますが、文章を見ただけでコードが見えるでしょうか。少し難しいのではないかと思います。
つまり脳内だけで処理してしまうと記憶保持や順序がバラバラになってしまうといった事態が発生しかねないので、アウトプットする意味で設計図を書く必要があるわけです。あとは誰かに説明したいとき、手伝ってほしいときに理解してもらえる、というメリットもあります。具体的な処理内容は後から記述する際にまとめていけばいいので、まずは大まかな動作図を示しましょう。「こうしたいんだよ」というイメージが明確でないと、コードは書けません。
ではスタックの例題で設計図を作ってみましょう!
1. スタックを用いて → データ格納(pushメソッド)とデータ取り出し(popメソッド)が必要
2. ユーザの入力した → キーボード入力を受け付けないと
3. データを保存し  → キーボード入力を受け付けて、それからpushさせるのか!
4. 最後に結果で出す → すべて終わったら、全部popすれば良いわけだ

と、全4段階に区切って考えることができます。そして区切った後に「どのような処理をさせると良いのか」を書いています。これを少し書くだけで、どんなコードを書けばいいのか理解しやすいですね。 「この仕様通りの動きにしたいけど、どうやったらいいか分からない」と言いながら仕様文だけ読んでじーっと考えてる人がたまにいます。ぜひ考える前に、大まかな動作が分かる設計図を作ってください。そうすれば、コードは見えずともどういう処理をすればいいのかは分かるはずですから。
上から順番にプログラムを書くのではなく、大まかな形を作りながら書く、といったように。枠を作ってから後で中身を詳しく書いていく、というスタイルにしてみると随分書きやすくなるかと思います。今まで考えたこともなかったと言う人がいたら、是非トライしてみてください!

プログラムよ、とにかく動け ~4桁のヒット&ブローゲーム「4Numbers」を作ろう~その0

待望のプログラミング講座です

誰も待ってないし、少なくとも教えられるほどの腕はありません。ごめんなさい。

目立ったバグもなく動けば合格、とりあえずそれを目標に行う私のプログラミングですが、せっかくなのでここで備忘録的な感じで残していくことにしました。

アルゴリズムがデタラメだったり、オーダがO(n^2)だったりして滅茶苦茶なことが多く、「これ時間処理も何も考えてないだろ」ってプログラムを平気で書いてる奴なので、たくさん改善点はあると思いますが…。(優しく教えてくれる人募集中)

全何回になるか分からない上に更新頻度も謎です。ご了承ください。

プロローグ Pythonで簡単なゲームを作成してみたい

上にある通り、最近私の中でプチブームになっている「Python」言語を用いて今回はプログラミングしていきます。ちなみに、これ以外に私が触ったことがあるのは「アセンブリ言語」「C言語」「Java言語」「C#言語」なのですが、個人的に一番しっくりきたというか、興味が湧いたのがこの「Python」でした。でもヘビは嫌いです。大嫌い。

今回作成するゲームというのは、「ヒットアンドブロー」。皆さんはご存知でしょうか。私はこのゲームが実は大好きで、暇な時間にやっていることもあります。地味な頭の体操になる…らしいです。無意識にやってるので正直そんな感覚は一切ないですが。

ルールは以下の通りです。このゲームのルールはそこそこで違うみたいなので、一応オリジナルルールということで。

・4桁の答えとなる数字が設定される。その4桁は0から始まっても良いが、数字の重複をしてはいけない。(例えば0123)

・プレイヤーは正解と思う4桁の数字を提示する。こちらも数字の重複をしてはいけない。(例えば0267)

・答えと正解を以下の基準で比較する。

 (1)正解とプレイヤーの答えが完全一致の場合、正解となる。(例では[0123][0267]で一致しないため不正解)

 (2)正解とプレイヤーの答えが一致している桁(例では[0]がそれに該当)を「ヒット」とする。

 (3)プレイヤーの答えに、「正解の数字ではあるが桁が違う」という数字があった場合(例では[2]がそれに該当)を「ブロー」とする。

・不正解であったなら、「ヒット数・ブロー数」をヒントとしてプレイヤーに教え、再挑戦させる。(例では[1Hit 1blow]となる)

・正解するまで再挑戦でき、正解したらゲームは終了。正解までにかかった時間、およびトライ数が表示される。より短い時間で、試行回数が少ない方が良い、という感じ。

 

要するに数あてゲームです。当たるまでの時間と手数が短い方がベスト。今回はこのゲームを作っていきます。名付けて「4Numbers」。(実はアプリに同じ名前があるのですが、パクリスペクトです。許して)

 

というわけで、このゲームのプログラムを早速作成…といきたいのですが、何事も段階を踏むことが大切。今回はプロローグ、ということで、「こんなゲームを作るよ」という予告程度に留め、次回から本格的に入っていこうと思います。次回はどのような動作をするゲームなのかを考え、それに合わせた「どのような処理を行うのか」ということを考える段階からスタートする予定です。

今回はここまで。また次回お会いしましょう!

 

おまけ

おまけというか余談ですが、実は2年程前にこのゲームをC言語で作っています。初心者のくせにいきなりそんなプログラム組むから、それはもうとんでもない労力と時間を使いましたが、長期休暇を使って仕上げたのはいい思い出です。よかったら遊んでみてください。ただ、色々バグが残ってるので…動作が安定するかどうかは分かりません。(一部から「ウイルスの疑いがある」と警告されると言われましたが、ウイルスを入れられるほど高度な技術は持っていないので安心してください。)

あと、1桁ずつ入れなければいけない、という特殊な入力の仕方になります。複数桁入れても変な答えになるだけなのでお気をつけて。

 

ダウンロードはこちら(Windowsのみ対応、Win10動作確認済み[Dropboxで開きます])

ダウンロード配布は終了しました。ご了承ください。(2017/5/18追記)

 

bash on Ubuntu on Windowsを入れてみた

Windowsでターミナル開いてLinuxでやってるアレコレがやりたい…

大学でプログラミングなどを習っているため、自宅でもその復習をする、課された課題をこなす…といった具合に、結構ターミナルを用いた作業をすることが多いわけです。

私は大学で用意されている端末でただ「こうすればプログラミング出来るから」と言われ、言われるがままにemacsvimを使ってきたわけで、ターミナルとか、bashとかそういうものに関しては正直無知です。分からないと今後色々とまずそうだけど。

で、自宅にWindowsしかなく、Linuxと同じ環境が用意できてない人はプログラミングしようとする時に困るわけで。

UNIXライクな環境をWindowsで使えるCygwinを入れてみたり、sshを使える人はTera Termを入れたり、デュアルブートUbuntuなどのLinuxのOSを入れる…と、人によって結構その辺りの対処は違いますね。

私の場合はCygwinの導入と、cmd(コマンドプロンプト)にOpen SSH を導入して強引にsshをする…といったことをしていました。ですが諸事情によりCygwinが壊れ、再インストールが必要に…さらにcmdでsshをするとvimで謎のエラーが出てまともに文字が書けなくなるという…。これは困った。早急に別の対処法を考えるか、諦めて再インストールをするか…。

そんな悩みを抱えていた時に友人から聞いたのが、「bash on Ubuntu on Windows」でした。

これを利用すれば、Windows上でUbuntuが起動できる(Windows上にUbuntuを乗せて動かすことができる[要するに仮想的に動かせるということかな])と聞いて、即インストール作業を始めました。

インストールは以下のサイトを見ながらちまちまと…。

qiita.com

インストール完了!

インストールとアップデートが終わったので、軽く使ってみました。

 

うん、普通のターミナルだ!

 

いつものLinux端末で使うターミナルと同じで、とても扱いやすかったです。専門家から言わせれば色々と問題点はあるようですが(ネットでちらほら見かける)…最悪sshvimgccぐらい使えれば今のところ大丈夫なので、私は大満足です。

でも、あまりにも無知過ぎてターミナルでの他の操作とか、新しい何かのインストールとか…そういうの全く分からなくてちょっと悲しくなりました。プログラミングだけじゃなくて、自分の作業環境もパパッと整えられるぐらいにはならないと。

 

以上、bash on Ubuntu on Windowsを入れたという報告でした。(報告したかっただけ)