プログラミングの初歩で作りそうなじゃんけんゲームを作る。ただし、PureScriptで作る。
方針
Jankenというモジュールを作る- グー・チョキ・パーを
Handとして定義する - じゃんけんの勝負の結果を
Judgementとして定義する - コンピュータが出す手はランダムに出したいので、ランダムな手を出す関数
randomを作っておく - 入力は文字列にしたいので、文字列から手に変換する関数
fromStringを作っておく
- グー・チョキ・パーを
- 入出力は
Mainに書く。Node.ReadLineモジュールの力で入力を受け付ける。
準備
適当なプロジェクトディレクトリを作っておいて、
$ spago init
/src/Main.pursと/src/Janken.pursを作っておく。
/src/Main.pursはとりあえず以下のようにしておく。
| |
次のコマンドでHelloが出力されることを確認する。
$ spago run
Jankenモジュールの定義
この節では/src/Janken.pursを編集していく。
| |
Handの定義
じゃんけんの手を表す型Handを定義する。
| |
余談。これは公式ではタグ付き共用体と呼ばれているもの。Haskellでは代数的データ型と呼ばれているが、正直名前はどうでもいい。データをこのように表現すれば、「データはこの値しかとりえない」という制限が得られる。制限があれば、プログラムのバグも減らせる。たとえば、「グーを0、チョキを1、パーを2」として表現すると、万が一それ以外の値が来た場合に困る。上のようなHandの定義では、「それ以外の値」が入る余地すら与えない。…この話は、Elm Guideの受け売り。
Judgementの定義
同じようにして、じゃんけんの勝敗を表す型Judgementを定義する。
| |
なぜWinとかLoseではないのかというと、これはjudge関数の都合である。Judgeは、2つの手を引数にとり、その勝負結果を返す。WinやLoseだと、どっちが勝ちでどっちが負けか分からない。なので、「judgeの左側の引数が勝ったらWinLeft、右側が勝ったらWinRight、引き分けならDraw」と定義している。
| |
REPLで遊ぶ
REPLでテストしてみたい。Showクラスのインスタンスにすることで、REPLで値が出力できるようになる。
| |
$ spago repl > import Janken > judge Rock Rock Draw > judge Rock Paper WinRight > judge Rock Scissors WinLeft
ランダムに出す手の定義
まずは乱数を扱えるパッケージを導入する。
$ spago install random
モジュールを読み込み、randomを定義する。
乱数は副作用付きなので、Effect Hand型を返す。
| |
文字列 → 手に変換する関数の定義
Rock、Scissors、Paper以外の値が入力されたら変換に失敗するため、関数の型はMaybe Handである。なので、Maybeが入ったパッケージを導入する。
$ spago install maybe
| |
REPLで遊んでみる。
> import Prelude > import Janken > judge Rock <$> fromInt "Rock" (Just Draw) > judge Rock <$> fromInt "Scissors" (Just WinLeft) > judge Rock <$> fromInt "Paper" (Just WinRight) > judge Rock <$> fromInt "aaa" Nothing > judge Rock <$> fromInt "hoge" Nothing
入出力インターフェースの作成
この節では、/src/Main.pursを編集していく。
まずreadlineが使えるパッケージを導入する。
$ spago install node-readline
このパッケージにはNode.jsのreadlineをPureScript用にラッピングしただけので、使い勝手はそれと似ている。
使う流れとしては、
createConsoleInterfaceでCUIの入力を受け付けるインターフェースを作るsetLineHandlerで、入力が確定されたときのコールバック関数を指定する。
だけ。だけなのだが、入力が不正だった場合は再度入力を促すようにするので、少しコードが複雑になる。
import文の追加
とりあえずこれだけ書いておく。
| |
インターフェースの作成
createConsoleInterfaceで、コンソール用のインターフェースを作成する。引数には入力補完のための設定を入れるのだが、詳細はNode.ReadLineのドキュメントやreadlineのドキュメントを参照。今回は補完は必要ないので、noCompletionを指定している。
runGameは次で作る。
| |
入力処理の作成
runGemeでは、入力を促し、それに応じて処理する機構を書く。
setLineHandlerで、指定されたインターフェースに入力を促す。入力した文字列はhandlerに回され、処理される。promptでプロンプトを出力する。プロンプトの内容はsetPromptで設定できる。
handlerでは、まず入力文字列が正しいものかを判定する。正しかったら、相手の手をランダムに作って、判定を行う。closeでインターフェースを閉じる。もし入力が正しくなかったら、setLineHandlerを再び呼んで再度入力を促す。
| |
printJudgementでは、じゃんけんの勝敗を出力する。
| |
完成
$ spago run > hoge Type Rock, Scissors, or Paper. > Rock You: Rock Computer: Scissors You win!
感想
PureScriptを書く良い練習になった。
JankenではなくJanken.HandとJanken.Judgementというモジュールに分割すべきか、と悩んだ。そうすれば、Janken.fromStringではなくてJanken.Hand.fromStringと書けて、より意味が明らかになる。ただ、そこまで大きなコードではないのでまとめてしまった。
今回Node.ReadLineモジュールを使ったが、そもそもNode.jsのreadlineを使ったことがなかった。調べてなんとかなった。