技術memo

関数型ゴースト

基礎プログラミング演習(2)

前回の演習(基礎プログラミング演習(1) - 技術memo)と同メンバーで、また似たようなプログラミング演習問題をやってみる会を開きました。

問題

コマンドラインで動くじゃんけんゲームを作りましょう

仕様

  1. 初めに「出す手を入力してください(g:グー、c:チョキ、p:パー)。終了する場合はqを入力してください」と表示します。
  2. キーボードから入力を受け付けます。
  3. 2でg, c, pのいずれかが入力された場合、コンピュータがランダムでグー、チョキ、パーの手の1つを生成して、勝ち負けあいこを判定します。また、コンピュータが生成した手と判定結果を表示して、1に戻ります。
  4. 2でqが入力された場合は、プログラムを終了します。
  5. 2でg, c, p, q以外の文字が入力された場合は、「error.入力値が不正です」と表示して、2に戻ります。

考えたこと

  • 前回の演習では、やや複雑で難しい問題にしてしまい、解答にだいぶ時間と体力が費やされたため、いったん少し簡単めの問題としました。
  • 今回は、新要素としてランダム(擬似乱数)と、関数や列挙型の定義を解説しました。

反省

  • 演習開始から終了まで、概ね6時間程度で完了しました。前回よりはだいぶ早くなりました。
  • 一通り動かすところまで自力実装できたのは良いとして、その後のレビューにもう少し時間を取れると、進行上良かったように思います。
  • やはりそろそろリスト処理や、クラス定義にステップアップしたいところです。

お手本コード(C#)

using System;

public static class JankenModule
{
    // define janken hands
    public enum Hand
    {
        Gu,
        Chi,
        Pa
    }

    // define janken results
    public enum Result
    {
        Win,
        Aiko,
        Lose
    }

    public static Hand GetRandomHand(Random r)
    {
        switch (r.Next(0, 2) % 3)
        {
            case 0:
                return Hand.Gu;
            case 1:
                return Hand.Chi;
            default:
                return Hand.Pa;
        }
    }

    // judge
    public static Result IsHumanWin(Hand human, Hand cp)
    {
        if (human == cp)
        {
            return Result.Aiko;
        }
        else if ((human == Hand.Gu && cp == Hand.Chi) || (human == Hand.Chi && cp == Hand.Pa) || (human == Hand.Pa && cp == Hand.Gu))
        {
            return Result.Win;
        }
        return Result.Lose;
    }

    // get janken hand name
    public static string GetHandName(Hand x)
    {
        switch (x)
        {
            case Hand.Gu:
                return "グー";
            case Hand.Chi:
                return "チョキ";
            case Hand.Pa:
                return "パー";
            default:
                return "";
        }
    }

    // get janken result name
    public static string GetResultName(Result x)
    {
        switch (x)
        {
            case Result.Win:
                return "勝ち";
            case Result.Aiko:
                return "あいこ";
            case Result.Lose:
                return "負け";
            default:
                return "";
        }
    }
}

public class JankenInput
{
    public JankenModule.Hand? Hand { private set; get; }
    public bool IsExit { private set; get; }

    public static JankenInput NewHand(JankenModule.Hand h)
    {
        return new JankenInput
        {
            Hand = h,
            IsExit = false
        };
    }

    public static JankenInput NewExit()
    {
        return new JankenInput
        {
            Hand = null,
            IsExit = true
        };
    }

    public static JankenInput NewNone()
    {
        return new JankenInput
        {
            Hand = null,
            IsExit = false
        };
    }
}

public static class Program
{
    // get hand by input char
    public static JankenInput ValidateInput(char input)
    {
        switch (input)
        {
            case 'g':
                return JankenInput.NewHand(JankenModule.Hand.Gu);
            case 'c':
                return JankenInput.NewHand(JankenModule.Hand.Chi);
            case 'p':
                return JankenInput.NewHand(JankenModule.Hand.Pa);
            case 'q':
                return JankenInput.NewExit();
            default:
                return JankenInput.NewNone();
        }
    }

    // get hand by input string
    public static JankenInput ValidateInput(string input)
    {
        if (input.Length != 1)
        {
            return JankenInput.NewNone();
        }
        return ValidateInput(input[0]);
    }

    public static JankenInput GetInput()
    {
        while (true)
        {
            var input = ValidateInput(Console.ReadLine());
            if (input.IsExit || input.Hand.HasValue) return input;
            Console.WriteLine("error.入力値が不正です");
        }
    }

    // entry point
    [STAThread]
    static void Main()
    {
        var random = new Random();
        while (true)
        {
            Console.WriteLine("出す手を入力してください(g:グー、c:チョキ、p:パー)。終了する場合はqを入力してください");
            var input = GetInput();
            if (input.IsExit) break;
            var hand = input.Hand.Value;
            var cpHand = JankenModule.GetRandomHand(random);
            var result = JankenModule.IsHumanWin(hand, cpHand);
            Console.WriteLine("あなた: {0} / CP: {1}", JankenModule.GetHandName(hand), JankenModule.GetHandName(cpHand));
            Console.WriteLine("あなたの{0}です", JankenModule.GetResultName(result));
        }
    }
}

お手本コード(C)

#include <stdio.h>
#include <stdbool.h>
#include <string.h>

// Console Utility -------------------------------------------------------------------------
// 本来はモジュールごとに別ファイルに分けたい

// write string to console 
void write(char* m){
    printf("%s", m); 
}

// read string by console
char* read(char* buf, int max){
    if(fgets(buf, max, stdin) == NULL){
        return "";
    }
    if(strchr(buf, '\0') != NULL){
        buf[strlen(buf) - 1] = '\0';
    }else{
        while(getchar() != '\n');
    }
    return buf;
}

// Janken Module -------------------------------------------------------------------------

// define janken hands
typedef enum { Gu, Chi, Pa } Hand;

// define janken results
typedef enum { Win, Aiko, Lose } Result;

Hand getRandomHand(){
  switch(rand()%3){
    case 0:
      return Gu;
    case 1:
      return Chi;
    default:
      return Pa;
  }
}

// judge
Result isHumanWin(Hand human, Hand cp){
  if(human == cp){
    return Aiko;
  }
  else if((human == Gu && cp == Chi)||(human == Chi && cp == Pa)||(human == Pa && cp ==Gu)){
    return Win;
  }
  return Lose;
}

// get janken hand name
char* getHandName(Hand t){
  switch(t){
    case Gu:
      return "グー";
    case Chi:
      return "チョキ";
    case Pa:
      return "パー";
    default:
      return "";
  }
}

// get janken result name
char* getResultName(Result t){
  switch(t){
    case Win:
      return "勝ち";
    case Aiko:
      return "あいこ";
    case Lose:
      return "負け";
    default:
      return "";
    }
}

// Janken Input -------------------------------------------------------------------------

typedef struct {
  bool isHand;
  Hand hand;
  bool isExit;
} JankenInput;

JankenInput newHand(Hand h){
  JankenInput r = { true, h, false };
  return r;
}

JankenInput newExit(){
  JankenInput r = { false, 0, true };
  return r;
}

JankenInput newNone(){
  JankenInput r = { false, 0, false };
  return r;  
}

// Program -------------------------------------------------------------------------

// get hand by input char
JankenInput validateInput(char input){
  switch(input){
    case 'g':
      return newHand(Gu);
    case 'c':
      return newHand(Chi);
    case 'p':
      return newHand(Pa);
    case 'q':
      return newExit();
    default:
      return newNone();
  }
}

// get hand by input string
JankenInput validateInputString(char* xs){
  if(xs[0] == '\0' || xs[1] != '\0'){
    return newNone();
  }
  return validateInput(xs[0]);
}

// get correct input value
JankenInput getInput(){
  char buf[100];
  while(true){
    JankenInput x = validateInputString(read(buf, 100));
    if(x.isHand || x.isExit) return x;
    write("error.入力値が不正です\n");
  }
}

// entry point
int main(){
  while(true){
    write("出す手を入力してください(a:グー、b:チョキ、c:パー)。終了する場合はqを入力してください\n");  
    JankenInput input = getInput();
    if(input.isExit) break;
    Hand hand = input.hand;
    Hand cpHand = getRandomHand();
    Result result = isHumanWin(hand, cpHand);
    char buf[100];
    sprintf(buf, "あなた: %s / CP: %s\n", getHandName(hand), getHandName(cpHand));
    write(buf);
    sprintf(buf, "あなたの%sです\n", getResultName(result));
    write(buf);
  }
}