※2020/05/05 コメントのアドバイスを元にコード修正
きっかけ
前回のつづき。
じゃんけんプログラムを作っていきます。
いつもの如くプログラム初心者へのご指摘、コメントお待ちしています。
今回の仕様
- じゃんけんをし、コンピュータの出した手と勝敗を出力します
- じゃんけん以外の三竦み対決にも使えるよう、勝敗判定は数的処理にします
- いつものコップ本、7章まで読み終えたので、なるだけ色々なテクニックを使っていきます
完成したコード
まずは完成したコードから。 ※修正済みコード
import scala.util.Random
import io.StdIn.readLine
// 人間の出す手を入力
def inputHand(handsMap: Map[String, Int]): String = {
print("じゃんけん...[グー/チョキ/パー]: ")
val inputHand: String = readLine()
// 条件に満たない文字が出てきたら再入力を求める(フールプルーフ)
if (handsMap.contains(inputHand)) inputHand
else this.inputHand(handsMap) // 定義中の自分自身を呼び出すときはthis
}
// コンピュータの出す手を生成
def generateComHand(): String = {
val comHand: String = Random.shuffle(List("グー", "チョキ", "パー")).apply(0)
comHand
}
// 出力に必要なデータをまとめる(MapとTuple2で頑張る)
def createHandsList(hands: Map[String, Int], yourHand: String, comHand: String): Map[String, Tuple2[String, Int]] = {
val handsMap = Map(
"yours" -> Tuple2(yourHand, hands(yourHand)),
"coms" -> Tuple2(comHand, hands(comHand))
)
handsMap
}
// 勝負
def judgement(yourHandParam: Int, comHandParam: Int): String = {
val judge: Int = (yourHandParam - comHandParam + 3) % 3 // 人間目線の結果を出したいので、yourHandが被演算子(引かれる数)
val result: String = judge match {
case 0 => "draw"
case 1 => "lose"
case 2 => "win"
}
result
}
// メイン処理 //
def battleMessage(): String = {
// グー、チョキ、パーの各文字と数字のMap(順番と数字はなんでもOK)
val hands: Map[String, Int] = Map("グー" -> 0, "チョキ" -> 1, "パー" -> 2)
val versus: Map[String, Tuple2[String, Int]] = createHandsList(hands, inputHand(hands), generateComHand())
val result: String = judgement(versus("yours")._2, versus("coms")._2)
val message: String = result match {
case "win" => "コンピュータの手: " + versus("coms")._1 + " -> あなたの勝ちです!"
case "lose" => "コンピュータの手: " + versus("coms")._1 + " -> あなたの負けです..."
case "draw" => "コンピュータの手: " + versus("coms")._1 + " -> あいこです。"
}
message
}
println(battleMessage())
詳細
inputHand関数
def inputHand(handsMap: Map[String, Int]): String = {
print("じゃんけん...[グー/チョキ/パー]: ")
val inputHand: String = readLine()
// 条件に満たない文字が出てきたら再入力を求める(フールプルーフ)
if (handsMap.contains(inputHand)) inputHand
else this.inputHand(handsMap) // 定義中の自分自身を呼び出すときはthis
}
io.StdIn.readLineメソッドを使って、ユーザ入力を受け付けることができます。
ライブラリのインポートも忘れずに。
この関数上で引数チェックを実装していますが、battleMessage関数とどちらに入れるべきか迷いました。
MapをbattleMessageで定義しているので、そちらで処理した方がいいかもしれません。
whileで正しい値が入力されるまで無限ループさせたいですが、
Scalaの考えでは関数を再起的に呼び出す方が関数型プログラミングに沿っていて良いとされるようです。
自分自身を呼び出すので、thisオブジェクトを指定。
generateComHand関数
def generateComHand(): String = {
val comHand: String = Random.shuffle(List("グー", "チョキ", "パー")).apply(0)
comHand
}
Random.shuffleメソッドでグーチョキパーをランダムに返します。
前回の復習感が強いですね。
createHandsList関数
def createHandsList(hands: Map[String, Int], yourHand: String, comHand: String): Map[String, Tuple2[String, Int]] = {
val handsMap = Map(
"yours" -> Tuple2(yourHand, hands(yourHand)),
"coms" -> Tuple2(comHand, hands(comHand))
)
handsMap
}
/* 修正前
def createHandsList(handsMap: Map[String, Int], yourHand: String, comHand: String): Map[String, List[Any]] = {
val handsDict: Map[String, List[Any]] = Map(
"yours" -> List(yourHand, handsMap(yourHand)), "coms" -> List(comHand, handsMap(comHand))
)
handsDict
}
*/
複数の型が入り込むとき、Listの要素がAny型になるのがポイントです。
呼び出すときにInt型に変換されません。ここで苦労しました。
→ここでもTupleを使いました。型指定ありがたい!
結果型のイメージは(実際はyamlじゃないけど)、
handsDict:
yours:
- "グー"
- 0
coms:
- "パー"
- 2
というデータを作って返してます。
Mapの場合は、「変換前 -> 変換後」と書いて、「Map(変換前)」で呼び出します。
Array同様メソッドで処理していきます。
judgement関数
三竦みの戦いはあと一歩のところで答えが出なかったので、ここを参考にしました。
mod3の世界だと-1 = 2, -2 = 1となることを利用していますね。
両者引くか掛けるか、ってところで私の思考は止まりました。
def judgement(yourHandParam: Int, comHandParam: Int): String = {
val judge: Int = (yourHandParam - comHandParam + 3) % 3 // 人間目線の結果を出したいので、yourHandが被演算子(引かれる数)
val result: String = judge match {
case 0 => "draw"
case 1 => "lose"
case 2 => "win"
}
result
}
Pythonにはcase文がないですが、Scalaにはmatch文があるのでこれを使います。
代わりにPythonはelse ifをelifと書けるので、書きやすさはそこまで差がないかもしれません。
2行目のコメントとなるように、計算順によって数字の意味するものが変わるので注意です。
コンピュータ目線なら、yourHandParamとcomHandParamを入れ替えます。
battleMessage関数
def battleMessage(): String = {
// グー、チョキ、パーの各文字と数字のMap(順番と数字はなんでもOK)
val hands: Map[String, Int] = Map("グー" -> 0, "チョキ" -> 1, "パー" -> 2)
val versus: Map[String, Tuple2[String, Int]] = createHandsList(hands, inputHand(hands), generateComHand())
val result: String = judgement(versus("yours")._2, versus("coms")._2)
val message: String = result match {
case "win" => "コンピュータの手: " + versus("coms")._1 + " -> あなたの勝ちです!"
case "lose" => "コンピュータの手: " + versus("coms")._1 + " -> あなたの負けです..."
case "draw" => "コンピュータの手: " + versus("coms")._1 + " -> あいこです。"
}
/* 修正前
val versus: Map[String, List[Any]] = createHandsList(hands, inputHand(hands), generateComHand())
val result: String = judgement(versus("yours")(1).asInstanceOf[Int], versus("coms")(1).asInstanceOf[Int]) // Listの要素がAny型だったので、.asInstanceでInt型に置き換え(キャスト)
val message: String = result match {
case "win" => "コンピュータの手: " + versus("coms")(0) + " -> あなたの勝ちです!"
case "lose" => "コンピュータの手: " + versus("coms")(0) + " -> あなたの負けです..."
case "draw" => "コンピュータの手: " + versus("coms")(0) + " -> あいこです。"
}
*/
message
}
judgement関数の結果がMap[String, Array[Any]]型になっていることに注意します。
型変換はAnyクラスのasInstanceOfメソッドを使用して、Int型にしてから使いましょう。
タプルの要素は、「タプル._(1始まりの添字)」で呼び出すことに注意しましょう。
頭の整理:MapとTuple
Mapに指定する要素はTuple2です。
Tupleの定義方法は、
scala> Tuple3("foo", "bar", "buz")
val res0: (String, String, String) = (foo,bar,buz)
scala> res0.getClass
val res3: Class[_ <: (String, String, String)] = class scala.Tuple3
こんな方法と、
scala> "a" -> 1 //要素数が2の場合のみ
val res1: (String, Int) = (a,1)
scala> res1.getClass
val res2: Class[_ <: (String, Int)] = class scala.Tuple2
が書けます(他にもあるようです)。なので、下記は同義です。
scala> val map1 = Map("a" -> 1)
val map1: scala.collection.immutable.Map[String,Int] = Map(a -> 1)
scala> val map2 = Map(Tuple2("a", 1))
val map2: scala.collection.immutable.Map[String,Int] = Map(a -> 1)
課題
これをクラスに仕立てたいですが、どのような設計にすれば良いかで試行錯誤中です。
もう少し頑張ります。