diff --git a/kadai3/micchie/typing-game/.gitignore b/kadai3/micchie/typing-game/.gitignore new file mode 100644 index 0000000..9872947 --- /dev/null +++ b/kadai3/micchie/typing-game/.gitignore @@ -0,0 +1,2 @@ +.vscode +typing-game diff --git a/kadai3/micchie/typing-game/Makefile b/kadai3/micchie/typing-game/Makefile new file mode 100644 index 0000000..4ad1876 --- /dev/null +++ b/kadai3/micchie/typing-game/Makefile @@ -0,0 +1,40 @@ +NAME := typing-game +SRCS := $(shell find ./ -type f -name '*.go') +VERSION := v0.0.1 +REVISION := $(shell git rev-parse --short HEAD) +PKGPATH := github.com/gopherdojo/dojo6/kadai3/micchie/typing-game +LDFLAGS := -ldflags \ + '-s -w \ + -X "$(PKGPATH)/cmd.Version=$(VERSION)" \ + -X "$(PKGPATH)/cmd.Revision=$(REVISION)" \ + -extldflags "-static"' + +DIST_DIRS := find * -type d -exec + +$(NAME): $(SRCS) + go build -a -tags netgo -installsuffix netgo $(LDFLAGS) -o $(NAME) + +.PHONY: install +install: + go install $(LDFLAGS) + +.PHONY: clean +clean: + rm -rf $(NAME) + +.PHONY: cross-build +cross-build: deps + for os in darwin linux windows; do \ + for arch in amd64 386; do \ + GOOS=$$os GOARCH=$$arch CGO_ENABLED=0 go build -a -tags netgo -installsuffix netgo $(LDFLAGS) -o dist/$$os-$$arch/$(NAME); \ + done; \ + done + +.PHONY: dist +dist: + cd dist && \ + $(DIST_DIRS) cp ../LICENSE {} \; && \ + $(DIST_DIRS) cp ../README.md {} \; && \ + $(DIST_DIRS) tar -zcf $(NAME)-$(VERSION)-{}.tar.gz {} \; && \ + $(DIST_DIRS) zip -r $(NAME)-$(VERSION)-{}.zip {} \; && \ + cd .. \ No newline at end of file diff --git a/kadai3/micchie/typing-game/README.md b/kadai3/micchie/typing-game/README.md new file mode 100644 index 0000000..0374c74 --- /dev/null +++ b/kadai3/micchie/typing-game/README.md @@ -0,0 +1,6 @@ +# Gopher 道場 +## 6-3-1: タイピングゲームを作ろう +- 標準出力に英単語を出す +- 標準入力から1行受け取る +- 制限時間内に何問解けたか表示する + diff --git a/kadai3/micchie/typing-game/config.yml b/kadai3/micchie/typing-game/config.yml new file mode 100644 index 0000000..fcf5610 --- /dev/null +++ b/kadai3/micchie/typing-game/config.yml @@ -0,0 +1,14 @@ +limit: 10 + +words: + - gopher + - polarbear + - blackbear + - elephant + - tiger + - camel + - lion + - panda + - leopard + - kangaroo + diff --git a/kadai3/micchie/typing-game/game/game.go b/kadai3/micchie/typing-game/game/game.go new file mode 100644 index 0000000..5ea6954 --- /dev/null +++ b/kadai3/micchie/typing-game/game/game.go @@ -0,0 +1,97 @@ +package game + +import ( + "bufio" + "context" + "fmt" + "io" + "os" + "time" +) + +// Game はtタイピングゲームのコンテキストや入出力などの情報を格納します. +type Game struct { + Context context.Context + Output io.Writer + Input io.Reader + TimeLimit time.Duration + Words []string + Score Score +} + +// Score はゲーム結果を格納します. +type Score struct { + Count int + CorrectNumber int +} + +// NewGame は Gme の構造体を新しく作ります. +func NewGame(ctx context.Context, w io.Writer, r io.Reader, t time.Duration, words []string) *Game { + return &Game{ + Context: ctx, + Output: w, + Input: r, + TimeLimit: t, + Words: words, + } +} + +// Run は TimeLimit に経過時間が達するまで Words をランダムに表示します. +// Word と同じ文字列を入力すると, correct!! 異なった文字列を入力すると, incorrect!! と表示されます. +func (g *Game) Run() { + fmt.Fprintln(g.Output, "===== Typing Game Start =====") + + scan := make(chan string) + go func() { + scanner := bufio.NewScanner(g.Input) + defer close(scan) + for scanner.Scan() { + scan <- scanner.Text() + } + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, "input:", err) + } + }() + + for { + word := g.Words[g.Score.Count] + fmt.Fprintln(g.Output, fmt.Sprintf("> %v", word)) + g.Score.Count++ + + switch { + case g.Score.Count == len(g.Words): + fmt.Fprintln(g.Output, fmt.Sprintf( + "\n===== Typing Game Finished =====\n%vwords completed. %v times corrected.\n", + g.Score.Count, + g.Score.CorrectNumber, + )) + return + } + + select { + case <-g.Context.Done(): + fmt.Fprintln(g.Output, fmt.Sprintf( + "\n===== Typing Game Finished =====\n%v have passed. %v times corrected.\n", + g.TimeLimit, + g.Score.CorrectNumber, + )) + return + case input := <-scan: + switch { + case g.Judgment(word, input): + fmt.Fprintln(g.Output, "correct!!") + default: + fmt.Fprintln(g.Output, "incorrect!!") + } + } + } +} + +// Judgment は Word と同じ文字列が入力されているかを判断し, 正解数をインクリメントします. +func (g *Game) Judgment(word, input string) bool { + if word == input { + g.Score.CorrectNumber++ + return true + } + return false +} diff --git a/kadai3/micchie/typing-game/game_test/game_test.go b/kadai3/micchie/typing-game/game_test/game_test.go new file mode 100644 index 0000000..86d0439 --- /dev/null +++ b/kadai3/micchie/typing-game/game_test/game_test.go @@ -0,0 +1,63 @@ +package game_test + +import ( + "testing" + "time" + + "github.com/gopherdojo/dojo6/kadai3/micchie/typing-game/game" +) + +func TestGame_Run(t *testing.T) { + g := &game.Game{} + g.TimeLimit = 5 * time.Second + g.Words = []string{ + "bear", + "bear", + } + + ch := make(chan string) + go func() { + time.Sleep(2 * time.Second) + ch <- "bear" + time.Sleep(2 * time.Second) + ch <- "beer" + }() + +} + +func TestGame_Judgment(t *testing.T) { + g := &game.Game{} + tests := []struct { + name string + word string + input string + beforeCorrectNumber int + afterCorrectNumber int + result bool + }{ + {name: "nomal", word: "bear", input: "bear", beforeCorrectNumber: 1, afterCorrectNumber: 2, result: true}, + {name: "blank", word: "cat", input: "", beforeCorrectNumber: 1, afterCorrectNumber: 1, result: false}, + {name: "incorrect", word: "dog", input: "d", beforeCorrectNumber: 0, afterCorrectNumber: 0, result: false}, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g.Score.CorrectNumber = test.beforeCorrectNumber + result := g.Judgment(test.word, test.input) + + if result != test.result { + t.Errorf( + "word: %v, input: %v, beforeCorrect: %v, afterCorrect: %v (want: %v, got: %v)", + test.word, + test.input, + test.beforeCorrectNumber, + test.afterCorrectNumber, + test.result, + result, + ) + } + }) + } +} diff --git a/kadai3/micchie/typing-game/go.mod b/kadai3/micchie/typing-game/go.mod new file mode 100644 index 0000000..2f7f993 --- /dev/null +++ b/kadai3/micchie/typing-game/go.mod @@ -0,0 +1,5 @@ +module github.com/gopherdojo/dojo6/kadai3/micchie/typing-game + +go 1.12 + +require github.com/jinzhu/configor v1.1.1 diff --git a/kadai3/micchie/typing-game/go.sum b/kadai3/micchie/typing-game/go.sum new file mode 100644 index 0000000..226e66d --- /dev/null +++ b/kadai3/micchie/typing-game/go.sum @@ -0,0 +1,7 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/jinzhu/configor v1.1.1 h1:gntDP+ffGhs7aJ0u8JvjCDts2OsxsI7bnz3q+jC+hSY= +github.com/jinzhu/configor v1.1.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/kadai3/micchie/typing-game/main.go b/kadai3/micchie/typing-game/main.go new file mode 100644 index 0000000..66ded73 --- /dev/null +++ b/kadai3/micchie/typing-game/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "context" + "math/rand" + "os" + "time" + + "github.com/gopherdojo/dojo6/kadai3/micchie/typing-game/game" + "github.com/jinzhu/configor" +) + +// Config is a structure of config.yml. +var Config = struct { + Limit time.Duration `default:"30"` + Words []string +}{} + +func main() { + configor.Load(&Config, "config.yml") + + t := Config.Limit * time.Second + + ctx, cancel := context.WithTimeout(context.Background(), t) + defer cancel() + + list := Config.Words + shuffle(list) + + g := game.NewGame(ctx, os.Stdout, os.Stdin, t, list) + g.Run() +} + +func shuffle(list []string) { + rand.Seed(time.Now().UnixNano()) + for i := range list { + j := rand.Intn(i + 1) + list[i], list[j] = list[j], list[i] + } +}