diff --git a/kadai3-1/akuchii/game/game.go b/kadai3-1/akuchii/game/game.go new file mode 100644 index 0000000..590241d --- /dev/null +++ b/kadai3-1/akuchii/game/game.go @@ -0,0 +1,75 @@ +package game + +import ( + "bufio" + "context" + "fmt" + "io" + "math/rand" + "time" +) + +// Game generates typing game +type Game struct { + reader io.Reader + writer io.Writer + idx int + words []string + timeout time.Duration +} + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +// NewGame generates new Game instance +func NewGame(r io.Reader, w io.Writer, words []string, timeout time.Duration) *Game { + return &Game{reader: r, writer: w, words: words, timeout: timeout} +} + +// Start starts typing game +func (g Game) Start() int { + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, g.timeout) + defer cancel() + + ch := input(g.reader) + cnt := 0 +LOOP: + for { + g.setNewIdx() + fmt.Fprintln(g.writer, "> "+g.word()) + select { + case v := <-ch: + if v == g.word() { + cnt++ + fmt.Fprintln(g.writer, "correct!") + } else { + fmt.Fprintln(g.writer, "incorrect!") + } + case <-ctx.Done(): + break LOOP + } + } + return cnt +} + +func (g *Game) setNewIdx() { + g.idx = rand.Intn(len(g.words)) +} + +func (g Game) word() string { + return g.words[g.idx] +} + +func input(r io.Reader) <-chan string { + ch := make(chan string) + go func() { + s := bufio.NewScanner(r) + for s.Scan() { + ch <- s.Text() + } + close(ch) + }() + return ch +} diff --git a/kadai3-1/akuchii/main.go b/kadai3-1/akuchii/main.go new file mode 100644 index 0000000..f57ca0a --- /dev/null +++ b/kadai3-1/akuchii/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/gopherdojo/dojo4/kadai3-1/akuchii/game" +) + +func main() { + timeout := 5 * time.Second + game := game.NewGame(os.Stdin, os.Stdout, getWords(), timeout) + cnt := game.Start() + fmt.Printf("\nfinish!\ncorrect count is %d\n", cnt) + os.Exit(0) +} + +func getWords() []string { + return []string{"csharp", "python", "perl", "cplusplus", "ruby", "golang", "scala"} +} diff --git a/kadai3-2/akuchii/downloader/downloader.go b/kadai3-2/akuchii/downloader/downloader.go new file mode 100644 index 0000000..cc7969f --- /dev/null +++ b/kadai3-2/akuchii/downloader/downloader.go @@ -0,0 +1,135 @@ +package downloader + +import ( + "fmt" + "io" + "net/http" + "os" + "strings" + + "golang.org/x/sync/errgroup" +) + +// Downloader downloads data in parallel +type Downloader struct { + url string + fileSize uint + splitNum uint + fileName string +} + +// Range defines range of data of start and end +type Range struct { + start uint + end uint + idx uint +} + +// NewDownloader generate new Downloader instance +func NewDownloader(url string, splitNum uint) *Downloader { + return &Downloader{ + url: url, + splitNum: splitNum, + } +} + +// Prepare prepares for download in parallel +func (d *Downloader) Prepare() error { + res, err := http.Head(d.url) + if err != nil { + return err + } + d.fileSize = uint(res.ContentLength) + + elems := strings.Split(d.url, "/") + d.fileName = elems[len(elems)-1] + + return nil +} + +// Download downloads in parallel +func (d Downloader) Download() error { + var eg errgroup.Group + + for i := uint(0); i < d.splitNum; i++ { + r := d.makeRange(i) + eg.Go(func() error { + return d.request(r) + }) + } + if err := eg.Wait(); err != nil { + return err + } + + return nil +} + +// GetFileName gets name of download file +func (d Downloader) GetFileName() string { + return d.fileName +} + +// MergeFiles merges files downloads in parallel +func (d Downloader) MergeFiles() error { + f, err := os.Create(d.fileName) + if err != nil { + return err + } + defer f.Close() + for i := uint(0); i < d.splitNum; i++ { + tmpFileName := fmt.Sprintf("%s.%d", d.fileName, i) + tmpFile, err := os.Open(tmpFileName) + if err != nil { + return err + } + io.Copy(f, tmpFile) + tmpFile.Close() + if err := os.Remove(tmpFileName); err != nil { + return err + } + } + return nil +} + +func (d Downloader) request(r Range) error { + req, err := http.NewRequest(http.MethodGet, d.url, nil) + if err != nil { + return err + } + req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", r.start, r.end)) + client := new(http.Client) + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + tmpFileName := fmt.Sprintf("%s.%d", d.fileName, r.idx) + file, err := os.Create(tmpFileName) + if err != nil { + return err + } + defer file.Close() + + if _, err := io.Copy(file, resp.Body); err != nil { + return err + } + + return nil +} + +func (d Downloader) makeRange(i uint) Range { + splitSize := d.fileSize / d.splitNum + start := i * splitSize + end := (i+1)*splitSize - 1 + + if i == d.splitNum { + end = d.fileSize + } + + return Range{ + start: start, + end: end, + idx: i, + } +} diff --git a/kadai3-2/akuchii/main.go b/kadai3-2/akuchii/main.go new file mode 100644 index 0000000..bc1fde2 --- /dev/null +++ b/kadai3-2/akuchii/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "os" + + "github.com/gopherdojo/dojo4/kadai3-2/akuchii/downloader" +) + +func main() { + if len(os.Args) != 2 { + fmt.Println("require url") + os.Exit(1) + } + + var splitNum uint = 4 + url := os.Args[1] + d := downloader.NewDownloader(url, splitNum) + + if err := d.Prepare(); err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := d.Download(); err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := d.MergeFiles(); err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Printf("download complete! : %v\n", d.GetFileName()) + os.Exit(0) +}