プログラミングの初歩で作りそうなじゃんけんゲームを作る。ただし、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
を使ったことがなかった。調べてなんとかなった。