diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3153f0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# GoLand +.idea/* +.env diff --git a/kadai3/simady/README.md b/kadai3/simady/README.md new file mode 100644 index 0000000..4868938 --- /dev/null +++ b/kadai3/simady/README.md @@ -0,0 +1,35 @@ +## 課題内容 +タイピングゲームを作ろう +- 標準出力に英単語を出す(出すものは自由) +- 標準入力から1行受け取る +- 制限時間内に何問解けたか表示する + +## 実行例 +```$xslt +$ typing-game +【タイピングゲーム】画面に表示される英単語をできるだけ多く入力しましょう! +制限時間は10秒です。 +1問目: apple +>apple +正解! 現在の正答率:1/1 +2問目: bake +>bake +正解! 現在の正答率:2/2 +3問目: cup +>cup +正解! 現在の正答率:3/3 +4問目: dog +>cat +不正解... 現在の正答率:3/4 +5問目: egg +> +タイムアップ! +*** +4問中3問正解 +*** +お疲れ様でした! +``` + +## その他 +- 制限時間や問題等が固定になっているので可変にしたい +- テストパッケージを分けたい \ No newline at end of file diff --git a/kadai3/simady/gamemaster/gamemaster.go b/kadai3/simady/gamemaster/gamemaster.go new file mode 100644 index 0000000..ea88b86 --- /dev/null +++ b/kadai3/simady/gamemaster/gamemaster.go @@ -0,0 +1,111 @@ +package gamemaster + +import ( + "bufio" + "context" + "fmt" + "io" + "time" +) + +type GameMaster interface { + Play() +} + +type gameMaster struct { + // 入力元 + r io.Reader + // 出力先 + w io.Writer + // 制限時間(分) + timeLimit time.Duration + // 問題 + problems []string + // 回答数 + answerNum int + // 正答数 + correctAnswerNum int +} + +// New GameMasterを生成する. +func New(reader io.Reader, writer io.Writer, timeLimit time.Duration, problems []string) GameMaster { + return &gameMaster{ + r: reader, + w: writer, + timeLimit: timeLimit, + problems: problems, + } +} + +func (gm *gameMaster) Play() { + gm.displayRule() + gm.game() + gm.displayResult() +} + +// displayRule ルールを表示する. +func (gm *gameMaster) displayRule() { + fmt.Fprintln(gm.w, "【タイピングゲーム】画面に表示される英単語をできるだけ多く入力しましょう!") + fmt.Fprintf(gm.w, "制限時間は%d秒です。\n", gm.timeLimit) +} + +// game ゲームを行う. +func (gm *gameMaster) game() { + ctx, cancel := context.WithTimeout(context.Background(), gm.timeLimit*time.Second) + defer cancel() + + ch := gm.input() + +gameLoop: + for i := 0; i < len(gm.problems); i++ { + problem := gm.problems[i] + fmt.Fprintf(gm.w, "%d問目: %s\n", i+1, problem) + fmt.Fprint(gm.w, ">") + + var in string + var ok bool + select { + case in, ok = <-ch: + if !ok { + break gameLoop + } + gm.answerNum++ + case <-ctx.Done(): + break gameLoop + } + + if in == problem { + gm.correctAnswerNum++ + fmt.Fprint(gm.w, "正解!") + } else { + fmt.Fprint(gm.w, "不正解...") + } + fmt.Fprintf(gm.w, " 現在の正答率:%d/%d\n", gm.correctAnswerNum, gm.answerNum) + } +} + +// displayResult 結果を表示する. +func (gm *gameMaster) displayResult() { + fmt.Fprintln(gm.w) + if gm.answerNum == len(gm.problems) { + fmt.Fprintln(gm.w, "全問回答しました!") + } else { + fmt.Fprintln(gm.w, "タイムアップ!") + } + fmt.Fprintln(gm.w, "***") + fmt.Fprintf(gm.w, "%d問中%d問正解\n", gm.answerNum, gm.correctAnswerNum) + fmt.Fprintln(gm.w, "***") + fmt.Fprintln(gm.w, "お疲れ様でした!") +} + +func (gm *gameMaster) input() <-chan string { + ch := make(chan string) + go func() { + s := bufio.NewScanner(gm.r) + for s.Scan() { + ch <- s.Text() + } + close(ch) + }() + return ch +} diff --git a/kadai3/simady/gamemaster/gamemaster_test.go b/kadai3/simady/gamemaster/gamemaster_test.go new file mode 100644 index 0000000..0a37b50 --- /dev/null +++ b/kadai3/simady/gamemaster/gamemaster_test.go @@ -0,0 +1,355 @@ +package gamemaster + +import ( + "bytes" + "io" + "os" + "reflect" + "testing" + "time" +) + +var ( + allCorrectReader *os.File + halfCorrectReader *os.File + timeUpReader *os.File +) + +func setup(t *testing.T) { + t.Helper() + var err error + + allCorrectReader, err = os.Open("../testdata/all_correct.txt") + if err != nil { + t.Fatalf("Failed to open file. err = %v", err) + } + + halfCorrectReader, err = os.Open("../testdata/half_correct.txt") + if err != nil { + t.Fatalf("Failed to open file. err = %v", err) + } + + timeUpReader, err = os.Open("../testdata/time_up.txt") + if err != nil { + t.Fatalf("Failed to open file. err = %v", err) + } +} + +func TestNew(t *testing.T) { + setup(t) + type args struct { + reader io.Reader + writer io.Writer + timeLimit time.Duration + problems []string + } + tests := []struct { + name string + args args + want GameMaster + }{ + { + name: "GameMasterの生成", + args: args{ + reader: allCorrectReader, + writer: &bytes.Buffer{}, + timeLimit: 10, + problems: []string{"apple", "bake", "cup", "dog", "egg", "fight", "green", "hoge", "idea", "japan"}, + }, + want: &gameMaster{ + r: allCorrectReader, + w: &bytes.Buffer{}, + timeLimit: 10, + problems: []string{"apple", "bake", "cup", "dog", "egg", "fight", "green", "hoge", "idea", "japan"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := New(tt.args.reader, tt.args.writer, tt.args.timeLimit, tt.args.problems); !reflect.DeepEqual(got, tt.want) { + t.Errorf("New() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_gameMaster_Play(t *testing.T) { + setup(t) + type fields struct { + r io.Reader + w io.Writer + timeLimit time.Duration + problems []string + } + tests := []struct { + name string + fields fields + }{ + { + name: "一連のゲーム処理", + fields: fields{ + r: allCorrectReader, + w: &bytes.Buffer{}, + timeLimit: 10, + problems: []string{"apple", "bake", "cup", "dog", "egg", "fight", "green", "hoge", "idea", "japan"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gm := &gameMaster{ + r: tt.fields.r, + w: tt.fields.w, + timeLimit: tt.fields.timeLimit, + problems: tt.fields.problems, + } + // 他のテストでパターンは網羅しているので正常終了のみ確認 + gm.Play() + }) + } +} + +func Test_gameMaster_displayRule(t *testing.T) { + setup(t) + type fields struct { + r io.Reader + w io.Writer + timeLimit time.Duration + problems []string + } + tests := []struct { + name string + fields fields + wantMessage string + }{ + { + name: "ルールの表示", + fields: fields{ + r: allCorrectReader, + w: &bytes.Buffer{}, + timeLimit: 10, + problems: []string{"apple", "bake", "cup", "dog", "egg", "fight", "green", "hoge", "idea", "japan"}, + }, + wantMessage: "【タイピングゲーム】画面に表示される英単語をできるだけ多く入力しましょう!\n制限時間は10秒です。\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gm := &gameMaster{ + r: tt.fields.r, + w: tt.fields.w, + timeLimit: tt.fields.timeLimit, + problems: tt.fields.problems, + } + gm.displayRule() + if gotWriter := tt.fields.w.(*bytes.Buffer).String(); gotWriter != tt.wantMessage { + t.Errorf("Message output by displayRule() = %v, want %v", gotWriter, tt.wantMessage) + } + }) + } +} + +func Test_gameMaster_game(t *testing.T) { + setup(t) + type fields struct { + r io.Reader + w io.Writer + timeLimit time.Duration + problems []string + } + tests := []struct { + name string + fields fields + wantMessage string + wantAnswerNum int + wantCorrectAnswerNum int + }{ + { + name: "全問正解", + fields: fields{ + r: allCorrectReader, + w: &bytes.Buffer{}, + timeLimit: 10, + problems: []string{"apple", "bake", "cup", "dog", "egg", "fight", "green", "hoge", "idea", "japan"}, + }, + wantMessage: "1問目: apple\n>正解! 現在の正答率:1/1\n" + + "2問目: bake\n>正解! 現在の正答率:2/2\n" + + "3問目: cup\n>正解! 現在の正答率:3/3\n" + + "4問目: dog\n>正解! 現在の正答率:4/4\n" + + "5問目: egg\n>正解! 現在の正答率:5/5\n" + + "6問目: fight\n>正解! 現在の正答率:6/6\n" + + "7問目: green\n>正解! 現在の正答率:7/7\n" + + "8問目: hoge\n>正解! 現在の正答率:8/8\n" + + "9問目: idea\n>正解! 現在の正答率:9/9\n" + + "10問目: japan\n>正解! 現在の正答率:10/10\n", + wantAnswerNum: 10, + wantCorrectAnswerNum: 10, + }, + { + name: "半分正解", + fields: fields{ + r: halfCorrectReader, + w: &bytes.Buffer{}, + timeLimit: 10, + problems: []string{"apple", "bake", "cup", "dog", "egg", "fight", "green", "hoge", "idea", "japan"}, + }, + wantMessage: "1問目: apple\n>正解! 現在の正答率:1/1\n" + + "2問目: bake\n>不正解... 現在の正答率:1/2\n" + + "3問目: cup\n>不正解... 現在の正答率:1/3\n" + + "4問目: dog\n>正解! 現在の正答率:2/4\n" + + "5問目: egg\n>正解! 現在の正答率:3/5\n" + + "6問目: fight\n>正解! 現在の正答率:4/6\n" + + "7問目: green\n>不正解... 現在の正答率:4/7\n" + + "8問目: hoge\n>不正解... 現在の正答率:4/8\n" + + "9問目: idea\n>正解! 現在の正答率:5/9\n" + + "10問目: japan\n>不正解... 現在の正答率:5/10\n", + wantAnswerNum: 10, + wantCorrectAnswerNum: 5, + }, + { + name: "読み込み完了", + fields: fields{ + r: timeUpReader, + w: &bytes.Buffer{}, + timeLimit: 10, + problems: []string{"apple", "bake", "cup", "dog", "egg", "fight", "green", "hoge", "idea", "japan"}, + }, + wantMessage: "1問目: apple\n>正解! 現在の正答率:1/1\n" + + "2問目: bake\n>正解! 現在の正答率:2/2\n" + + "3問目: cup\n>正解! 現在の正答率:3/3\n" + + "4問目: dog\n>不正解... 現在の正答率:3/4\n" + + "5問目: egg\n>正解! 現在の正答率:4/5\n" + + "6問目: fight\n>正解! 現在の正答率:5/6\n" + + "7問目: green\n>正解! 現在の正答率:6/7\n" + + "8問目: hoge\n>", + wantAnswerNum: 7, + wantCorrectAnswerNum: 6, + }, + { + name: "タイムアップ", + fields: fields{ + r: timeUpReader, + w: &bytes.Buffer{}, + timeLimit: 0, + problems: []string{"apple", "bake", "cup", "dog", "egg", "fight", "green", "hoge", "idea", "japan"}, + }, + wantMessage: "1問目: apple\n>", + wantAnswerNum: 0, + wantCorrectAnswerNum: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gm := &gameMaster{ + r: tt.fields.r, + w: tt.fields.w, + timeLimit: tt.fields.timeLimit, + problems: tt.fields.problems, + } + gm.game() + + if gotWriter := tt.fields.w.(*bytes.Buffer).String(); gotWriter != tt.wantMessage { + t.Errorf("Message output by game() = %v, want %v", gotWriter, tt.wantMessage) + } + if got := gm.answerNum; got != tt.wantAnswerNum { + t.Errorf("gm.answerNum = %v, want %v", got, tt.wantAnswerNum) + } + if got := gm.correctAnswerNum; got != tt.wantCorrectAnswerNum { + t.Errorf("gm.correctAnswerNum = %v, want %v", got, tt.wantCorrectAnswerNum) + } + }) + } +} + +func Test_gameMaster_displayResult(t *testing.T) { + setup(t) + type fields struct { + r io.Reader + w io.Writer + timeLimit time.Duration + problems []string + answerNum int + correctAnswerNum int + } + tests := []struct { + name string + fields fields + wantMessage string + }{ + { + name: "全問回答", + fields: fields{ + r: allCorrectReader, + w: &bytes.Buffer{}, + timeLimit: 10, + problems: []string{"apple", "bake", "cup", "dog", "egg", "fight", "green", "hoge", "idea", "japan"}, + answerNum: 10, + correctAnswerNum: 9, + }, + wantMessage: "\n全問回答しました!\n***\n10問中9問正解\n***\nお疲れ様でした!\n", + }, + { + name: "タイムアップ", + fields: fields{ + r: allCorrectReader, + w: &bytes.Buffer{}, + timeLimit: 10, + problems: []string{"apple", "bake", "cup", "dog", "egg", "fight", "green", "hoge", "idea", "japan"}, + answerNum: 8, + correctAnswerNum: 3, + }, + wantMessage: "\nタイムアップ!\n***\n8問中3問正解\n***\nお疲れ様でした!\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gm := &gameMaster{ + r: tt.fields.r, + w: tt.fields.w, + timeLimit: tt.fields.timeLimit, + problems: tt.fields.problems, + answerNum: tt.fields.answerNum, + correctAnswerNum: tt.fields.correctAnswerNum, + } + gm.displayResult() + if gotWriter := tt.fields.w.(*bytes.Buffer).String(); gotWriter != tt.wantMessage { + t.Errorf("Message output by displayResult() = %v, want %v", gotWriter, tt.wantMessage) + } + }) + } +} + +func Test_gameMaster_input(t *testing.T) { + setup(t) + type fields struct { + r io.Reader + } + tests := []struct { + name string + fields fields + wantReceivedItems []string + }{ + { + name: "入力チャネルの取得", + fields: fields{ + r: allCorrectReader, + }, + wantReceivedItems: []string{"apple", "bake", "cup", "dog", "egg", "fight", "green", "hoge", "idea", "japan"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gm := &gameMaster{ + r: tt.fields.r, + } + got := gm.input() + var i int + for m := range got { + if m != tt.wantReceivedItems[i] { + t.Errorf("gameMaster.input() = %v, want %v", m, tt.wantReceivedItems[i]) + } + i++ + } + }) + } +} diff --git a/kadai3/simady/go.mod b/kadai3/simady/go.mod new file mode 100644 index 0000000..29e6fc3 --- /dev/null +++ b/kadai3/simady/go.mod @@ -0,0 +1,3 @@ +module typing-game + +go 1.12 diff --git a/kadai3/simady/main.go b/kadai3/simady/main.go new file mode 100644 index 0000000..cd3901e --- /dev/null +++ b/kadai3/simady/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "os" + + "typing-game/gamemaster" +) + +const ( + timeLimit = 10 +) + +var ( + reader = os.Stdin + writer = os.Stdout + problems = []string{"apple", "bake", "cup", "dog", "egg", "fight", "green", "hoge", "idea", "japan"} +) + +func main() { + gamemaster.New(reader, writer, timeLimit, problems).Play() +} diff --git a/kadai3/simady/main_test.go b/kadai3/simady/main_test.go new file mode 100644 index 0000000..979ea45 --- /dev/null +++ b/kadai3/simady/main_test.go @@ -0,0 +1,17 @@ +package main + +import "testing" + +func Test_main(t *testing.T) { + tests := []struct { + name string + }{ + {name: "main関数の実行"}, + } + for _, tt := range tests { + // 他のテストでパターンは網羅しているので正常終了のみ確認 + t.Run(tt.name, func(t *testing.T) { + main() + }) + } +} diff --git a/kadai3/simady/testdata/all_correct.txt b/kadai3/simady/testdata/all_correct.txt new file mode 100644 index 0000000..c702309 --- /dev/null +++ b/kadai3/simady/testdata/all_correct.txt @@ -0,0 +1,10 @@ +apple +bake +cup +dog +egg +fight +green +hoge +idea +japan diff --git a/kadai3/simady/testdata/half_correct.txt b/kadai3/simady/testdata/half_correct.txt new file mode 100644 index 0000000..8897e13 --- /dev/null +++ b/kadai3/simady/testdata/half_correct.txt @@ -0,0 +1,10 @@ +apple +cake +cap +dog +egg +fight +blue +fuga +idea +japon diff --git a/kadai3/simady/testdata/time_up.txt b/kadai3/simady/testdata/time_up.txt new file mode 100644 index 0000000..78e776a --- /dev/null +++ b/kadai3/simady/testdata/time_up.txt @@ -0,0 +1,7 @@ +apple +bake +cup +cat +egg +fight +green diff --git a/kadai3/simady/typing-game b/kadai3/simady/typing-game new file mode 100755 index 0000000..bb680fe Binary files /dev/null and b/kadai3/simady/typing-game differ