diff --git a/kadai3-1/tokunaga/README.md b/kadai3-1/tokunaga/README.md new file mode 100644 index 0000000..399505b --- /dev/null +++ b/kadai3-1/tokunaga/README.md @@ -0,0 +1,4 @@ +# 課題3-1 タイピングゲームを作ろう +- [ ] 標準出力に英単語を出す(出すものは自由) +- [ ] 標準入力から1行受け取る +- [ ] 制限時間内に何問解けたか表示する diff --git a/kadai3-1/tokunaga/main.go b/kadai3-1/tokunaga/main.go new file mode 100644 index 0000000..24db460 --- /dev/null +++ b/kadai3-1/tokunaga/main.go @@ -0,0 +1,90 @@ +package main + +import ( + "bufio" + crand "crypto/rand" + "fmt" + "io" + "math" + "math/big" + "math/rand" + "os" + "time" +) + +var word = [...]string{ + "toukyoutokkyokyokakyoku", + "akamakigamiaomakigamikimakigami", + "sushi", + "tenpura", + "kaiken", + "nisshingeppo", + "hyappatuhyakutyu", + "kumiai", + "taiiku", + "kome", +} + +const ( + limitSec = 15 + ExitAbort = 1 +) + +func init() { + if err := serRandSeed(); err != nil { + os.Exit(ExitAbort) + } +} + +func serRandSeed() error { + seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) + rand.Seed(seed.Int64()) + return err +} + +func main() { + fmt.Printf("制限時間は%d秒です\n", limitSec) + + ch := input(os.Stdin) + var correctCount int + go output(ch, &correctCount) + + <-time.After(limitSec * time.Second) + finish(correctCount) +} + +func input(r io.Reader) <-chan string { + ch := make(chan string) + go read(r, ch) + return ch +} + +func read(reader io.Reader, ch chan string) { + s := bufio.NewScanner(reader) + for s.Scan() { + ch <- s.Text() + } + close(ch) +} + +func output(typeWord <-chan string, correctCount *int) { + wordCount := len(word) + for { + answerWord := word[rand.Intn(wordCount)] + fmt.Println(answerWord) + fmt.Print("> ") + typeWord := <-typeWord + if typeWord == answerWord { + fmt.Println("correct answer!") + *correctCount++ + } else { + fmt.Println("incorrect answer...") + } + fmt.Println("-----------------------------") + } +} + +func finish(correctCount int) { + fmt.Println("\n-----------FINISH!-----------") + fmt.Printf("正解数:%d\n", correctCount) +} diff --git a/kadai3-1/tokunaga/main_test.go b/kadai3-1/tokunaga/main_test.go new file mode 100644 index 0000000..c5e6d3e --- /dev/null +++ b/kadai3-1/tokunaga/main_test.go @@ -0,0 +1,33 @@ +package main + +import ( + "bytes" + "testing" +) + +func ExampleFinish() { + finish(3) + // Output: -----------FINISH!----------- + // 正解数:3 +} + +func TestInput(t *testing.T) { + inputWord := "Hello Worlds!" + inputBuf := bytes.NewBufferString(inputWord) + ch := input(inputBuf) + received := <-ch + if inputWord != received { + t.Errorf("want: receive %s from chanel, got: receive %s from chanel", inputWord, received) + } +} + +func TestRead(t *testing.T) { + input := "Hello Worlds!" + ch := make(chan string) + inputBuf := bytes.NewBufferString(input) + go read(inputBuf, ch) + received := <-ch + if input != received { + t.Errorf("want: receive %s from chanel, got: receive %s from chanel", input, received) + } +} diff --git a/kadai3-2/tokunaga/cmd/goget/main.go b/kadai3-2/tokunaga/cmd/goget/main.go new file mode 100644 index 0000000..2a09385 --- /dev/null +++ b/kadai3-2/tokunaga/cmd/goget/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "flag" + "fmt" + "net/http" + "os" + "time" + + "github.com/gopherdojo/dojo2/kadai3-2/tokunaga" +) + +const ( + ExitOK = iota + ExitAbort +) +const timeLayout = "2006-01-02 15:04:05" + +func main() { + flag.Parse() + if len(flag.Args()) != 1 { + fmt.Fprintln(os.Stderr, "You can only download one file at a time") + os.Exit(ExitAbort) + } + downloadPath := flag.Args()[0] + + t := time.Now() + fmt.Printf("--%s-- %s\n", t.Format(timeLayout), downloadPath) + fmt.Print("HTTP による接続要求を送信しました、応答を待っています...") + + // head request + responseHead, err := http.Head(downloadPath) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(ExitAbort) + } + // response info + acceptRanges := responseHead.Header.Get("Accept-Ranges") + statusCode := responseHead.StatusCode + fileSize := responseHead.ContentLength + contentType := responseHead.Header.Get("Content-Type") + + downloadFile := tokunaga.File{Uri: downloadPath, FileSize: fileSize} + fmt.Printf("%d %s\n", statusCode, http.StatusText(statusCode)) + if responseHead.StatusCode != 200 { + fmt.Fprintln(os.Stderr, http.StatusText(responseHead.StatusCode)) + os.Exit(ExitAbort) + } + fmt.Printf("長さ: %d [%s]\n", fileSize, contentType) + fmt.Printf("`%s' に保存中\n", downloadFile.Filename()) + fmt.Println("") + + if err := downloadFile.Download(acceptRanges); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(ExitAbort) + } + + fmt.Printf("`%s' に保存完了\n", downloadFile.Filename()) + os.Exit(ExitOK) +} diff --git a/kadai3-2/tokunaga/file.go b/kadai3-2/tokunaga/file.go new file mode 100644 index 0000000..578cfb0 --- /dev/null +++ b/kadai3-2/tokunaga/file.go @@ -0,0 +1,135 @@ +package tokunaga + +import ( + "fmt" + "io" + "net/http" + "os" + "path" + "runtime" + "golang.org/x/sync/errgroup" +) + +type File struct { + Uri string + FileSize int64 +} + +// ダウンロードファイルのUriからファイル名を取得 +func (f File) Filename() string { + return path.Base(f.Uri) +} + +// acceptRangesによってダウンロード方法を切り替える +func (f File) Download(acceptRanges string) error { + if acceptRanges == "" { + return f.singleDownload() + } else { + return f.splitDownload() + } +} + +// ファイルを一括ダウンロード +func (f File) singleDownload() error { + responseGet, err := http.Get(f.Uri) + if err != nil { + return err + } + defer responseGet.Body.Close() + + file, err := os.Create(f.Filename()) + if err != nil { + return err + } + defer file.Close() + _, err = io.Copy(file, responseGet.Body) + if err != nil { + return err + } + return nil +} + +// ファイルを分割ダウンロード +func (f File) splitDownload() error { + splitNum := runtime.NumCPU() + splitBytes := f.SplitByteSize(int64(splitNum)) + ranges := formatRange(splitBytes) + createFileMap := map[int]string{} + + eg := errgroup.Group{} + for no, rangeValue := range ranges { + index := no + value := rangeValue + eg.Go(func() error { + return f.rangeDownload(index, value, createFileMap) + }) + } + if err := eg.Wait(); err != nil { + return err + } + if err := f.joinSplitFiles(createFileMap); err != nil { + return err + } + return nil +} + +func (f File) rangeDownload(fileNo int, rangeValue string, createFiles map[int]string) error { + req, _ := http.NewRequest("GET", f.Uri, nil) + req.Header.Set("RANGE", rangeValue) + + client := new(http.Client) + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + createFileName := fmt.Sprintf("%s_%s", f.Filename(), rangeValue) + file, err := os.Create(createFileName) + if err != nil { + return err + } + defer file.Close() + createFiles[fileNo] = createFileName + _, err = io.Copy(file, resp.Body) + if err != nil { + return err + } + return nil +} + +// 分割されたファイルを一つに結合させる +func (f File) joinSplitFiles(createFiles map[int]string) error { + originFile, err := os.Create(f.Filename()) + if err != nil { + return err + } + defer originFile.Close() + for i := 0; i < len(createFiles); i++ { + splitFile, err := os.Open(createFiles[i]) + if err != nil { + return err + } + _, err = io.Copy(originFile, splitFile) + if err != nil { + return err + } + splitFile.Close() + if err := os.Remove(createFiles[i]); err != nil { + return err + } + } + return nil +} + +// ファイルのバイト数と分割数から、分割ダウンロードするファイルの各バイト数の配列を返す +// SplitByteSize(1002, 8) -> [125, 125, 125, 125, 125, 125, 125, 127] +func (f File) SplitByteSize(splitNum int64) []int64 { + var response = make([]int64, splitNum) + rest := f.FileSize % splitNum // ファイルのサイズを分割数で割った余り + splitUnit := (f.FileSize - rest) / splitNum // 分割したファイルのサイズ + for i := int64(0); i < splitNum-1; i++ { + response[i] = splitUnit + } + response[splitNum-1] = splitUnit + rest + return response +} diff --git a/kadai3-2/tokunaga/format.go b/kadai3-2/tokunaga/format.go new file mode 100644 index 0000000..95c592a --- /dev/null +++ b/kadai3-2/tokunaga/format.go @@ -0,0 +1,16 @@ +package tokunaga + +import "fmt" + +// 分割ダウンロードするファイルの各バイト数の配列から、request headerのRANGEに設定する値の配列を返す +// [25 25 25 28] +// -> [bytes=0-25 bytes=26-51 bytes=52-77 bytes=78-103] +func formatRange(splitBytes []int64) []string { + var response []string + var bytePosition int64 + for _, bytes := range splitBytes { + response = append(response, fmt.Sprintf("bytes=%d-%d", bytePosition, bytePosition+bytes-1)) + bytePosition = bytePosition + bytes + } + return response +}