From 28532a3b3a277205099a01352caca15475139cee Mon Sep 17 00:00:00 2001 From: wi11-holdsworth <83637728+wi11-holdsworth@users.noreply.github.com> Date: Tue, 7 Oct 2025 19:39:10 +1100 Subject: [PATCH 1/6] test: create testing functions for average number of guesses over all possible targets --- Main.hs | 5 +---- Proj2.hs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Main.hs b/Main.hs index bbd5eec..711ac3f 100644 --- a/Main.hs +++ b/Main.hs @@ -39,7 +39,4 @@ toChord = fromJust . mapM toPitch . words -- | Prompt for a target and use guessTest to try to guess it. main :: IO () main = do - putStr "Target chord (3 pitches separated by spaces): " - hFlush stdout - text <- getLine - guessTest $ toChord text + putStr $ show avgGuesses diff --git a/Proj2.hs b/Proj2.hs index 1b4ba1e..5ddf200 100644 --- a/Proj2.hs +++ b/Proj2.hs @@ -51,6 +51,7 @@ module Proj2 GameState, initialGuess, nextGuess, + avgGuesses, ) where @@ -239,3 +240,22 @@ allChords = | note <- [minBound .. maxBound], octave <- [minBound .. maxBound] ] + +-- ==== TESTING =============================================================== + +guessTest :: [Pitch] -> Int +guessTest target = loop target guess other 1 + where + (guess, other) = initialGuess + +loop :: [Pitch] -> [Pitch] -> GameState -> Int -> Int +loop target guess other guesses + | answer == (3, 0, 0) = guesses + | otherwise = loop target guess' other' (guesses + 1) + where + answer = feedback target guess + (guess', other') = nextGuess (guess, other) answer + +avgGuesses = fromIntegral (sum results) / fromIntegral (length results) + where + results = map guessTest allChords From bfdcfde6c98490aea035f46f983d1026da79e7d2 Mon Sep 17 00:00:00 2001 From: wi11-holdsworth <83637728+wi11-holdsworth@users.noreply.github.com> Date: Tue, 7 Oct 2025 19:39:22 +1100 Subject: [PATCH 2/6] build: add command time to justfile --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index d0b0bdf..de6de60 100644 --- a/justfile +++ b/justfile @@ -12,4 +12,4 @@ build: # run musician run: - ./target/{{exec}} + time ./target/{{exec}} From 1f28cc7cda480430059fa9490cea239c502c16a9 Mon Sep 17 00:00:00 2001 From: wi11-holdsworth <83637728+wi11-holdsworth@users.noreply.github.com> Date: Tue, 7 Oct 2025 19:39:10 +1100 Subject: [PATCH 3/6] test: create testing functions for average number of guesses over all possible targets --- Main.hs | 5 +---- Proj2.hs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Main.hs b/Main.hs index bbd5eec..711ac3f 100644 --- a/Main.hs +++ b/Main.hs @@ -39,7 +39,4 @@ toChord = fromJust . mapM toPitch . words -- | Prompt for a target and use guessTest to try to guess it. main :: IO () main = do - putStr "Target chord (3 pitches separated by spaces): " - hFlush stdout - text <- getLine - guessTest $ toChord text + putStr $ show avgGuesses diff --git a/Proj2.hs b/Proj2.hs index 84c4274..496aba1 100644 --- a/Proj2.hs +++ b/Proj2.hs @@ -54,6 +54,7 @@ module Proj2 GameState, initialGuess, nextGuess, + avgGuesses, ) where @@ -250,3 +251,22 @@ allChords = | note <- [minBound .. maxBound], octave <- [minBound .. maxBound] ] + +-- ==== TESTING =============================================================== + +guessTest :: [Pitch] -> Int +guessTest target = loop target guess other 1 + where + (guess, other) = initialGuess + +loop :: [Pitch] -> [Pitch] -> GameState -> Int -> Int +loop target guess other guesses + | answer == (3, 0, 0) = guesses + | otherwise = loop target guess' other' (guesses + 1) + where + answer = feedback target guess + (guess', other') = nextGuess (guess, other) answer + +avgGuesses = fromIntegral (sum results) / fromIntegral (length results) + where + results = map guessTest allChords From 5bd5fb0a25f99531b095f07a2494f5f783ddb27b Mon Sep 17 00:00:00 2001 From: wi11-holdsworth <83637728+wi11-holdsworth@users.noreply.github.com> Date: Tue, 7 Oct 2025 19:39:22 +1100 Subject: [PATCH 4/6] build: add command time to justfile --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index d0b0bdf..de6de60 100644 --- a/justfile +++ b/justfile @@ -12,4 +12,4 @@ build: # run musician run: - ./target/{{exec}} + time ./target/{{exec}} From 5103cb7f0e540ce6096cc463c0a95c5e111bb383 Mon Sep 17 00:00:00 2001 From: wi11-holdsworth <83637728+wi11-holdsworth@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:11:09 +1100 Subject: [PATCH 5/6] docs: add average num guesses per initial guess for all targets --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 1490f0c..d84db88 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,12 @@ # Spec +## Best first guess +### All different +[C1, D2, E3]: 4.27 +[A1, D2, G3]: 4.25 +### Same notes +[D1, D2, D3]: 4.80 +### Same octaves +[C2, D2, E2]: 4.34 ## Tips to improve `nextGuess` The best results can be had by carefully choosing each guess so that it is most likely to leave a small remaining list of possible targets. You can do this by From 17587190a53b95f927c78228273b2a54a4c8e9f4 Mon Sep 17 00:00:00 2001 From: wi11-holdsworth <83637728+wi11-holdsworth@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:54:14 +1100 Subject: [PATCH 6/6] refactor: idiomatic best-practices --- Proj2.hs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/Proj2.hs b/Proj2.hs index 84c4274..757234d 100644 --- a/Proj2.hs +++ b/Proj2.hs @@ -57,10 +57,10 @@ module Proj2 ) where -import Data.Function (on) +import Data.Function import Data.List import Data.Map qualified as Map -import Data.Ord (comparing) +import Data.Ord import Data.Set qualified as Set -- ==== DATA STRUCTURES ======================================================= @@ -107,10 +107,9 @@ instance Show Octave where toPitch :: String -> Maybe Pitch toPitch [note, octave] = Pitch <$> charToNote note <*> charToOctave octave where - charToNote :: Char -> Maybe Note - charToNote c = - lookup - c + charToNote = + flip + lookup [ ('A', A), ('B', B), ('C', C), @@ -119,10 +118,9 @@ toPitch [note, octave] = Pitch <$> charToNote note <*> charToOctave octave ('F', F), ('G', G) ] - charToOctave :: Char -> Maybe Octave - charToOctave c = - lookup - c + charToOctave = + flip + lookup [ ('1', One), ('2', Two), ('3', Three) @@ -201,8 +199,8 @@ nextGuess (prevGuess, state) prevFeedback = (chosen, newState) chosen = fst $ minimumBy (comparing snd) scored newState = filter (/= chosen) candidates - scored = map (\x -> (x, score x candidates)) candidates - candidates = filter (\x -> prevFeedback == feedback prevGuess x) state + scored = map ((,) <*> (`score` candidates)) candidates + candidates = filter ((== prevFeedback) . feedback prevGuess) state -- average number of possible targets per candidate score candidate candidates = ((/) `on` fromIntegral) (sum l) (length l) @@ -225,7 +223,7 @@ matches :: (Eq a, Show a) => [a] -> [a] -> Int matches xs ys = maximum permutationMatches where permutationMatches = map (pairwiseMatches xs) (permutations ys) - pairwiseMatches xs = length . filter (uncurry (==)) . zip xs + pairwiseMatches xs ys = length $ filter (uncurry (==)) $ zip xs ys -- outputs a list of all possible chords, where a chord is a list of unique -- pitches. this function happens to generate chords such that the pitches are @@ -236,12 +234,11 @@ matches xs ys = maximum permutationMatches -- allChords :: [[Pitch]] allChords = - [ chord + [ [p1, p2, p3] | p1 <- allPitches, p2 <- allPitches, - p3 <- allPitches, - let chord = [p1, p2, p3], p1 < p2, + p3 <- allPitches, p2 < p3 ] where