Skip to content

Instantly share code, notes, and snippets.

@gautamrege
Last active April 13, 2020 05:16
Show Gist options
  • Save gautamrege/be9117bb5196297cafba94fca796b7af to your computer and use it in GitHub Desktop.
Save gautamrege/be9117bb5196297cafba94fca796b7af to your computer and use it in GitHub Desktop.
Hangman - Part 4 - interfaces and testing
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
)
const MAX_CHANCES int = 8
var dev = flag.Bool("dev", false, "Development environment")
type Hangman struct{}
type Gamer interface {
RenderGame([]string, map[string]bool) error
GetInput() string
}
func (h *Hangman) RenderGame(placeholder []string, entries map[string]bool) error {
fmt.Println(placeholder)
fmt.Printf("Chances left: %d\n", MAX_CHANCES-len(entries))
fmt.Printf("Guesses: %s\n", get_keys(entries))
fmt.Printf("Guess a letter or the word: ")
return nil
}
func (h *Hangman) GetInput() string {
str := ""
fmt.Scanln(&str)
str = strings.TrimSuffix(str, "\n")
return str
}
func get_keys(entries map[string]bool) []string {
keys := []string{}
for k, _ := range entries {
keys = append(keys, k)
}
return keys
}
func play(h Gamer, word string) bool {
entries := map[string]bool{}
placeholder := make([]string, len(word), len(word))
// initialize to '_'
for i := range word {
placeholder[i] = "_"
}
for {
// evaluate a loss!
if len(entries) == MAX_CHANCES {
return false
}
// evaluate a win!
if strings.Join(placeholder, "") == word {
return true
}
h.RenderGame(placeholder, entries)
str := h.GetInput()
if str == word {
return true
} else if str == "" {
// someone pressed enter direclty!
continue
} else if len(str) > 1 {
entries[str] = true
continue
}
input := rune(str[0]) // take input and convert to rune (similar to char)
// evalute the entry
found := false
for i, e := range word {
if input == e {
placeholder[i] = string(input)
found = true
}
}
if !found {
entries[string(str)] = true
}
}
// should never come here!
return false
}
func get_word() string {
res, err := http.Get("https://random-word-api.herokuapp.com/word?number=5")
if err != nil {
log.Fatal(err)
}
blob, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatal(err)
}
var words []string
err = json.Unmarshal(blob, &words)
if err != nil {
fmt.Println(err)
return "elephant"
}
for _, word := range words {
if len(word) > 4 && len(word) < 11 {
// return a word between 5 and 10 characters
return string(word)
}
}
return "elephant" // default
}
func main() {
flag.Parse()
word := "elephant"
if *dev == false {
word = get_word()
}
game := &Hangman{}
if play(game, word) == true {
fmt.Println("You win! You've saved yourself from a hanging")
} else {
fmt.Println("Damn! You're hanged!!")
fmt.Println("Word was: ", word)
}
}
package main
import (
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)
type MockGamer struct {
mock.Mock
}
func (m *MockGamer) RenderGame(placeholder []string, entries map[string]bool) error {
args := m.Called(placeholder, entries)
return args.Error(0)
}
func (m *MockGamer) GetInput() (str string) {
args := m.Called()
randomStringFunc, ok := args.Get(0).(func(charset string, length int) string)
if ok {
str = randomStringFunc(args.String(1), args.Int(2))
return
}
return args.String(0)
}
type HangmanTestSuite struct {
suite.Suite
word string
mockGamer *MockGamer
}
func (suite *HangmanTestSuite) SetupTest() {
rand.Seed(time.Now().UnixNano())
suite.mockGamer = &MockGamer{}
suite.word = "elephant"
}
func TestHangmanTestSuite(t *testing.T) {
suite.Run(t, new(HangmanTestSuite))
}
func (suite *HangmanTestSuite) TestPlaySuccess() {
suite.mockGamer.On("RenderGame", mock.Anything, mock.Anything).Return(nil)
// whenever GetInput will be called
// it will return a random character from suite.word => "elephant"
suite.mockGamer.On("GetInput").Return(randomString, suite.word, 1)
result := play(suite.mockGamer, suite.word)
assert.Equal(suite.T(), result, true)
}
func (suite *HangmanTestSuite) TestPlayWhenUserOutOfChances() {
suite.mockGamer.On("RenderGame", mock.Anything, mock.Anything).Return(nil)
// whenever GetInput will be called
// it will return a random character from cdxzoafbnqj
// after sometime user will get out of chances
suite.mockGamer.On("GetInput").Return(randomString, "cdxzoafbnqj", 1)
result := play(suite.mockGamer, suite.word)
assert.Equal(suite.T(), result, false)
}
func (suite *HangmanTestSuite) TestPlayWhenUserEntersCompleteWord() {
suite.mockGamer.On("RenderGame", mock.Anything, mock.Anything).Return(nil)
// whenever GetInput will be called
// it will return the complete word => elephant
// user will be saved from hanging in the first attempt
suite.mockGamer.On("GetInput").Return(suite.word)
result := play(suite.mockGamer, suite.word)
assert.Equal(suite.T(), result, true)
}
func randomString(charset string, length int) string {
index := randomInt(0, len(charset))
return string(charset[index])
}
func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment