Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kadai3-1 by execjosh #48

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions kadai3/execjosh/go-typing-game/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
WORDS_ALPHA='https://github.com/dwyl/english-words/raw/master/words_alpha.zip'

all: setup

.PHONY: setup
setup:
[ ! -f words_alpha.txt ] && wget -Owords_alpha.zip $(WORDS_ALPHA) && unzip words_alpha.zip && rm words_alpha.zip

.PHONY: coverage
coverage:
@rm coverage.out || true
go get -u github.com/haya14busa/goverage
'${GOPATH}/bin/goverage' -v -coverprofile coverage.out ./internal/game ./internal/wordbank
go tool cover -html=coverage.out
86 changes: 86 additions & 0 deletions kadai3/execjosh/go-typing-game/internal/game/game.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package game

import (
"bufio"
"fmt"
"io"
"time"

"github.com/gopherdojo/dojo1/kadai3/execjosh/go-typing-game/internal/wordbank"
)

type ctx struct {
done chan struct{}
sch, fch chan bool

wb wordbank.WordProvider

stdin io.Reader
stdout io.Writer
}

// Run runs the game
func Run(stdin io.Reader, stdout io.Writer, wb wordbank.WordProvider, timeout time.Duration) *Stats {
ctx := &ctx{
done: make(chan struct{}),
sch: make(chan bool),
fch: make(chan bool),

wb: wb,

stdin: stdin,
stdout: stdout,
}

// Keep stats out of context to force use of channels
stats := &Stats{}

go ctx.gameloop()

go func() {
time.Sleep(timeout)
close(ctx.done)
}()

OUT:
for {
select {
case _, ok := <-ctx.fch:
if ok {
stats.LogFailure()
}
case _, ok := <-ctx.sch:
if ok {
stats.LogSuccess()
}
case <-ctx.done:
close(ctx.sch)
close(ctx.fch)
break OUT
}
}

return stats
}

func (c *ctx) gameloop() {
in := bufio.NewScanner(c.stdin)
word := c.wb.NextWord()

for {
fmt.Fprintln(c.stdout, word)

if ok := in.Scan(); !ok {
break
}

if word == in.Text() {
c.sch <- true
fmt.Fprintf(c.stdout, "\u2705\n")
word = c.wb.NextWord()
} else {
fmt.Fprintf(c.stdout, "\u274c\n")
c.fch <- true
}
}
}
39 changes: 39 additions & 0 deletions kadai3/execjosh/go-typing-game/internal/game/game_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package game_test

import (
"bytes"
"testing"
"time"

"github.com/gopherdojo/dojo1/kadai3/execjosh/go-typing-game/internal/game"
)

func TestRun(t *testing.T) {
input := bytes.NewBufferString("abc\nbcd\nc\n\n")
output := new(bytes.Buffer)
wb := wordBank{}
timeout := 1 * time.Second

stats := game.Run(input, output, &wb, timeout)

actualOutput := output.String()
expectedOutput := "abc\n✅\nabc\n❌\nabc\n❌\nabc\n❌\nabc\n"
if actualOutput != expectedOutput {
t.Fail()
}

if stats.SuccessCount() != 1 {
t.Fail()
}

if stats.FailureCount() != 3 {
t.Fail()
}
}

type wordBank struct {
}

func (wb *wordBank) NextWord() string {
return "abc"
}
27 changes: 27 additions & 0 deletions kadai3/execjosh/go-typing-game/internal/game/stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package game

// Stats represents game stats
type Stats struct {
successCount int
failureCount int
}

// SuccessCount returns the success count
func (s *Stats) SuccessCount() int {
return s.successCount
}

// FailureCount returns the failure count
func (s *Stats) FailureCount() int {
return s.failureCount
}

// LogSuccess logs a success
func (s *Stats) LogSuccess() {
s.successCount++
}

// LogFailure logs a failure
func (s *Stats) LogFailure() {
s.failureCount++
}
29 changes: 29 additions & 0 deletions kadai3/execjosh/go-typing-game/internal/wordbank/wordbank.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package wordbank

import (
"math/rand"
)

// A WordProvider provides words
type WordProvider interface {
NextWord() string
}

type randomWordBank struct {
words []string
rng *rand.Rand
}

// NewRandomWordBank creates a new random word provider
func NewRandomWordBank(words []string, seed int64) WordProvider {
return &randomWordBank{
words: words,
rng: rand.New(rand.NewSource(seed)),
}
}

// NextWord implements WordProvider by returning a random word
func (rwp *randomWordBank) NextWord() string {
numWords := len(rwp.words)
return rwp.words[rwp.rng.Intn(numWords)]
}
25 changes: 25 additions & 0 deletions kadai3/execjosh/go-typing-game/internal/wordbank/wordbank_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package wordbank_test

import (
"testing"

"github.com/gopherdojo/dojo1/kadai3/execjosh/go-typing-game/internal/wordbank"
)

func TestRandomWordProvider(t *testing.T) {
seed := int64(1234567890)
words := []string{
"a", "b", "c", "d", "abc",
}
wb := wordbank.NewRandomWordBank(words, seed)

expected := []string{
"d", "b", "d", "b", "d",
}

for _, exp := range expected {
if exp != wb.NextWord() {
t.Fail()
}
}
}
64 changes: 64 additions & 0 deletions kadai3/execjosh/go-typing-game/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package main

import (
"bufio"
"errors"
"fmt"
"os"
"time"

"github.com/gopherdojo/dojo1/kadai3/execjosh/go-typing-game/internal/game"
"github.com/gopherdojo/dojo1/kadai3/execjosh/go-typing-game/internal/wordbank"
)

const (
wordsFilePath = "words_alpha.txt"
timeout = 30 * time.Second
)

func die(err error) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

func main() {
wb, err := loadWordBankFromFile(wordsFilePath)
if err != nil {
die(err)
}

fmt.Printf("You have %v!\n", timeout)

stats := game.Run(os.Stdin, os.Stdout, wb, timeout)

fmt.Println("\n---\nTime's Up, Grasshopper!")
fmt.Println("\x1b[92mSuccess count: ", stats.SuccessCount(), "\x1b[m")
fmt.Println("\x1b[31mFailure count: ", stats.FailureCount(), "\x1b[m")
}

func loadWordBankFromFile(filepath string) (wordbank.WordProvider, error) {
f, err := os.Open(filepath)
if err != nil {
if os.IsNotExist(err) {
err = errors.New(fmt.Sprint("expected ", wordsFilePath, " to exist"))
}
return nil, err
}
defer f.Close()

s := bufio.NewScanner(f)
var words []string

for s.Scan() {
w := s.Text()
words = append(words, w)
}

if err := s.Err(); err != nil {
return nil, err
}

wb := wordbank.NewRandomWordBank(words, time.Now().UnixNano())

return wb, nil
}