Просмотр исходного кода

feat: various minor improvements

- minor efficiency improvements

- refactoring

- improve test coverage

- more real game data
Alex White 7 месяцев назад
Родитель
Сommit
41e9419500
6 измененных файлов с 259 добавлено и 113 удалено
  1. 42 31
      fallout.go
  2. 6 33
      game.go
  3. 37 10
      game_test.go
  4. 68 1
      games.txt
  5. 17 35
      simulator.go
  6. 89 3
      simulator_test.go

+ 42 - 31
fallout.go

@@ -14,25 +14,25 @@ func main() {
 		debug = 1
 	}
 
-	game := NewGame()
-
-	err := game.getWordsFromStdin()
+	words, length, err := getWordsFromStdin()
 	if err != nil {
 		fmt.Printf("Error getting Words from stdin: %s\n", err)
 		os.Exit(1)
 	}
 
-	simulator := NewSimulator(*game)
-	_, err = simulator.SimulateAllPossibleGames()
-	if err != nil {
-		fmt.Printf("Error simulating all possible games: %s\n", err)
-		os.Exit(1)
-	}
+	game := NewGame()
+	game.Words = words
+	game.Length = length
 
 	for i := 0; i < 3; i++ {
-		guess, score := getGuessAndScoreFromStdin()
-		fmt.Printf("Guess: %s, Score: %d\n", guess, score)
+		simulator := NewSimulator(*game)
+		_, err = simulator.SimulateAllPossibleGames()
+		if err != nil {
+			fmt.Printf("Error simulating all possible games: %s\n", err)
+			os.Exit(1)
+		}
 
+		guess, score := getGuessAndScoreFromStdin()
 		game.FilterWords(guess, score)
 
 		if len(game.Words) == 0 {
@@ -40,32 +40,12 @@ func main() {
 			os.Exit(1)
 
 		} else if len(game.Words) == 1 {
-			fmt.Printf("Game is down to one word: %+v\n", game.Words)
 			for word := range game.Words {
 				fmt.Printf("\n\nThe answer is: %s\n\n", word)
 				os.Exit(0)
 			}
 		}
 
-		err = game.scoreWordsByCommonLetterLocations()
-		if err != nil {
-			fmt.Printf("Error scoring Words: %s\n", err)
-			os.Exit(1)
-		}
-
-		_, err = game.printSortedScores()
-		if err != nil {
-			fmt.Printf("Error printing sorted scores: %s\n", err)
-			os.Exit(1)
-		}
-
-		bestGuess, err := game.getBestGuess()
-		if err != nil {
-			fmt.Printf("Error getting best guess: %s\n", err)
-			os.Exit(1)
-		}
-		fmt.Printf("Best Guess: %s", bestGuess)
-
 	}
 }
 
@@ -82,10 +62,41 @@ func getGuessAndScoreFromStdin() (string, int) {
 		fmt.Printf("Error reading score: %s\n", err)
 		os.Exit(1)
 	}
+	fmt.Print("\n")
 
 	return guess, score
 }
 
+func getWordsFromStdin() (map[string]*Word, int, error) {
+
+	fmt.Println("Enter Words, one per line. Enter a period to end input.")
+
+	var length int
+	words := make(map[string]*Word)
+
+	for {
+		reader := bufio.NewReader(os.Stdin)
+		word, _ := reader.ReadString('\n')
+		word = word[:len(word)-1]
+		if word == "." {
+			fmt.Print("\nOK!\n\n")
+			break
+		}
+
+		if length == 0 {
+			length = len(word)
+		} else {
+			if len(word) != length {
+				fmt.Printf("Error: All words must be the same length (%d), skipping: %s\n", length, word)
+				continue
+			}
+		}
+		words[word] = &Word{Word: word}
+	}
+
+	return words, length, nil
+}
+
 func debugPrint(format string, args ...interface{}) {
 	if debug == 1 {
 		fmt.Printf(format, args...)

+ 6 - 33
game.go

@@ -1,9 +1,7 @@
 package main
 
 import (
-	"bufio"
 	"fmt"
-	"os"
 	"sort"
 )
 
@@ -18,7 +16,7 @@ func NewGame() *Game {
 	}
 }
 
-func (g Game) scoreWordsByCommonLetterLocations() error {
+func (g Game) scoreWordsByCommonLetterLocations() {
 	letterIdxScores := make(map[int]map[string]int)
 
 	var lastWord string
@@ -43,7 +41,7 @@ func (g Game) scoreWordsByCommonLetterLocations() error {
 		}
 	}
 
-	return nil
+	return
 }
 
 func (g Game) FilterWords(guess string, score int) {
@@ -76,11 +74,11 @@ func (g Game) getSortedScores() []string {
 	return sortedWordScores
 }
 
-func (g Game) getBestGuess() (string, error) {
-	return g.getSortedScores()[0], nil
+func (g Game) getBestGuess() string {
+	return g.getSortedScores()[0]
 }
 
-func (g Game) printSortedScores() (string, error) {
+func (g Game) printSortedScores() []string {
 
 	sortedWordScores := g.getSortedScores()
 
@@ -91,30 +89,5 @@ func (g Game) printSortedScores() (string, error) {
 	}
 	fmt.Println("")
 
-	return sortedWordScores[0], nil
-}
-
-func (g Game) getWordsFromStdin() error {
-
-	fmt.Println("Enter Words, one per line. Enter a period to end input.")
-
-	for {
-		reader := bufio.NewReader(os.Stdin)
-		word, _ := reader.ReadString('\n')
-		word = word[:len(word)-1]
-		if word == "." {
-			fmt.Print("\nOK!\n\n")
-			return nil
-		}
-
-		if g.Length == 0 {
-			g.Length = len(word)
-		} else {
-			if len(word) != g.Length {
-				fmt.Printf("Error: All words must be the same length (%d), skipping: %s\n", g.Length, word)
-				continue
-			}
-		}
-		g.Words[word] = &Word{Word: word}
-	}
+	return sortedWordScores
 }

+ 37 - 10
game_test.go

@@ -38,11 +38,7 @@ func TestGame_scoreWordsByCommonLetterLocations(t *testing.T) {
 				game.Words[word] = &Word{Word: word}
 			}
 
-			err := game.scoreWordsByCommonLetterLocations()
-			if err != nil {
-				t.Errorf("Error scoring Words: %s\n", err)
-				t.FailNow()
-			}
+			game.scoreWordsByCommonLetterLocations()
 
 			for word, score := range tt.want {
 				if game.Words[word].Score != score {
@@ -210,11 +206,7 @@ func TestGame_getBestGuess(t *testing.T) {
 				Words:  tt.fields.Words,
 				Length: tt.fields.Length,
 			}
-			got, err := g.getBestGuess()
-			if (err != nil) != tt.wantErr {
-				t.Errorf("getBestGuess() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
+			got := g.getBestGuess()
 			if got != tt.want {
 				t.Errorf("getBestGuess() got = %v, wantedLossCount %v", got, tt.want)
 			}
@@ -256,3 +248,38 @@ func TestGame_getSortedScores(t *testing.T) {
 		})
 	}
 }
+
+func TestGame_printSortedScores(t *testing.T) {
+	type fields struct {
+		Words map[string]*Word
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   []string
+	}{
+		{
+			name: "sort words by scores",
+			fields: fields{
+				Words: map[string]*Word{
+					"pleads": {Word: "pleads", Score: 13},
+					"crimes": {Word: "crimes", Score: 16},
+					"fierce": {Word: "fierce", Score: 12},
+					"shiner": {Word: "shiner", Score: 14},
+					"wastes": {Word: "wastes", Score: 20},
+					"tables": {Word: "tables", Score: 17},
+					"visage": {Word: "visage", Score: 15},
+				},
+			},
+			want: []string{"wastes", "tables", "crimes", "visage", "shiner", "pleads", "fierce"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			g := Game{
+				Words: tt.fields.Words,
+			}
+			assert.Equalf(t, tt.want, g.printSortedScores(), "getSortedScores()")
+		})
+	}
+}

+ 68 - 1
games.txt

@@ -99,4 +99,71 @@ nomads
 series
 proper
 .
-member
+member
+
+
+manned
+harder
+member
+matter
+murder
+mutter
+wonder
+meager
+manage
+mental
+middle
+teevee
+master
+handed
+cinder
+minute
+.
+murder
+
+
+compound
+consists
+chainsaw
+converse
+connects
+somewhat
+contains
+conflict
+confined
+computer
+contents
+concerns
+contests
+contrast
+continue
+concrete
+criminal
+jonathan
+.
+concrete
+
+
+capturing
+honorable
+fortified
+convinced
+certainly
+batteries
+repairing
+childlike
+community
+countries
+histories
+threatens
+torturing
+gossiping
+extensive
+requiring
+bartering
+listening
+committee
+.
+listening
+
+

+ 17 - 35
simulator.go

@@ -2,7 +2,7 @@ package main
 
 import (
 	"fmt"
-	"os"
+	"slices"
 )
 
 type Simulation struct {
@@ -30,7 +30,13 @@ func (s Simulation) SimulateAllPossibleGames() (string, error) {
 	totRoundCounts := make(map[string]int)
 	maxRoundCounts := make(map[string]int)
 
-	for initialWord := range s.Game.Words {
+	var words []string
+	for word := range s.Game.Words {
+		words = append(words, word)
+	}
+	slices.Sort(words)
+
+	for _, initialWord := range words {
 		fmt.Println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
 
 		simulatedGame := NewGame()
@@ -43,6 +49,7 @@ func (s Simulation) SimulateAllPossibleGames() (string, error) {
 		wordLossCounts[initialWord] = lossCount
 		totRoundCounts[initialWord] = totalRounds
 		maxRoundCounts[initialWord] = maxRounds
+		//fmt.Printf("Initial Word: %s  Loss Count: %d  Max Rounds: %d  Total Rounds: %d\n", initialWord, lossCount, maxRounds, totalRounds)
 		if lossCount < bestGuessLossCount {
 			bestGuessLossCount = lossCount
 			bestGuessTotalRounds = totalRounds
@@ -58,10 +65,7 @@ func (s Simulation) SimulateAllPossibleGames() (string, error) {
 	fmt.Println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
 	fmt.Print("Simulation completed\n\n")
 
-	err := s.Game.scoreWordsByCommonLetterLocations()
-	if err != nil {
-		return "", fmt.Errorf("Error scoring Words: %s", err)
-	}
+	s.Game.scoreWordsByCommonLetterLocations()
 
 	var bestGuess string
 	for _, word := range s.Game.getSortedScores() {
@@ -108,6 +112,11 @@ func (s Simulation) SimulateOneInitialWord(game *Game, initialWord string) (loss
 
 func (s Simulation) SimulateOneGame(simulatedGame *Game, initialWord, answer string) (bool, int) {
 
+	if initialWord == answer {
+		fmt.Printf("  Win in 1 round  =>  %s .. %s\n", initialWord, answer)
+		return true, 1
+	}
+
 	guess := initialWord
 
 	// 10 rounds max just to prevent infinite loops
@@ -124,31 +133,8 @@ func (s Simulation) SimulateOneGame(simulatedGame *Game, initialWord, answer str
 
 		} else {
 
-			//debugPrint(">>>>>>>>>>>>>>>>>>>>>>>>>>>>\n")
-			//fmt.Printf("Starting Sub Simulation: %+v\n", simulatedGame.Words)
-			//simulatedSubGame := NewGame()
-			//simulatedSubGame.Words = make(map[string]*Word)
-			//for word := range simulatedGame.Words {
-			//	simulatedSubGame.Words[word] = &Word{Word: word}
-			//}
-			//subSimulator := NewSimulator(*simulatedSubGame)
-			//
-			//var err error
-			//guess, err = subSimulator.SimulateAllPossibleGames()
-			//if err != nil {
-			//	fmt.Printf("Error simulating all possible games: %s\n", err)
-			//	os.Exit(1)
-			//}
-			//fmt.Printf("RESULT OF SUB Guess: %s\n", guess)
-
-			var err error
-			guess, err = simulatedGame.getBestGuess()
-			if err != nil {
-				fmt.Printf("Error calculating best guess: %s\n", err)
-				os.Exit(1)
-			}
+			guess = simulatedGame.getBestGuess()
 			debugPrint("Round %d, guess: %s\n", round, guess)
-
 		}
 
 		if guess == answer {
@@ -161,11 +147,7 @@ func (s Simulation) SimulateOneGame(simulatedGame *Game, initialWord, answer str
 		simulatedGame.FilterWords(guess, score)
 		debugPrint("Words remaining after filtering: %+v\n", simulatedGame.Words)
 
-		err := simulatedGame.scoreWordsByCommonLetterLocations()
-		if err != nil {
-			fmt.Printf("Error scoring Words: %s\n", err)
-			os.Exit(1)
-		}
+		simulatedGame.scoreWordsByCommonLetterLocations()
 
 		debugPrint("End round %d, not solved, %d words remaining\n", round, len(simulatedGame.Words))
 	}

+ 89 - 3
simulator_test.go

@@ -1,7 +1,10 @@
 package main
 
 import (
+	"bufio"
+	"fmt"
 	"github.com/stretchr/testify/assert"
+	"os"
 	"testing"
 )
 
@@ -124,9 +127,7 @@ func TestSimulation_SimulateOneGame(t *testing.T) {
 				game.Words[word] = &Word{Word: word}
 			}
 
-			s := Simulation{
-				Game: game,
-			}
+			s := NewSimulator(*game)
 
 			got, got1 := s.SimulateOneGame(game, tt.args.initialWord, tt.args.answer)
 			assert.Equalf(t, tt.won, got, "SimulateOneGame(%v, %v, %v)", game, tt.args.initialWord, tt.args.answer)
@@ -134,3 +135,88 @@ func TestSimulation_SimulateOneGame(t *testing.T) {
 		})
 	}
 }
+
+func TestSimulation_SimulateAllPossibleGames(t *testing.T) {
+	tests := []struct {
+		name    string
+		words   []string
+		want    string
+		wantErr assert.ErrorAssertionFunc
+	}{
+		{
+			name:    "simple game with no losses",
+			words:   []string{"aaa", "aab", "aac", "aad", "aae"},
+			want:    "aaa",
+			wantErr: assert.NoError,
+		},
+		{
+			name:    "simple game with no losses",
+			words:   []string{"hazards", "hurting", "manages", "varying", "gabbing", "packing", "warring", "cantina", "caravan", "gangers", "falling", "waiting", "screens", "happens", "largest", "lawless", "dangers", "variety"},
+			want:    "warring",
+			wantErr: assert.NoError,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+
+			game := &Game{}
+			game.Words = make(map[string]*Word)
+			for _, word := range tt.words {
+				game.Words[word] = &Word{Word: word}
+			}
+			s := NewSimulator(*game)
+
+			got, err := s.SimulateAllPossibleGames()
+			if !tt.wantErr(t, err, fmt.Sprintf("SimulateAllPossibleGames()")) {
+				return
+			}
+			assert.Equalf(t, tt.want, got, "SimulateAllPossibleGames()")
+		})
+	}
+}
+
+func TestSimulation_SimulateAllPossibleGamesFromGameLog(t *testing.T) {
+
+	file, err := os.Open("games.txt")
+	if err != nil {
+		t.Fatalf("Error opening file: %s", err)
+	}
+	defer file.Close()
+
+	var words []string
+
+	scanner := bufio.NewScanner(file)
+	for scanner.Scan() {
+		line := scanner.Text()
+		fmt.Println(line)
+
+		if line == "." {
+
+			game := &Game{}
+			game.Words = make(map[string]*Word)
+			for _, word := range words {
+				game.Words[word] = &Word{Word: word}
+			}
+			s := NewSimulator(*game)
+
+			_, err := s.SimulateAllPossibleGames()
+			assert.NoError(t, err)
+
+			words = nil
+
+			// skip the answer
+			scanner.Scan()
+			for scanner.Scan() {
+				line = scanner.Text()
+				fmt.Printf("Got line: %s\n", line)
+				if line != "" {
+					break
+				}
+			}
+
+		}
+
+		fmt.Printf("Adding word: %s\n", line)
+		words = append(words, line)
+	}
+}