decode

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 18, 2025 License: MIT Imports: 23 Imported by: 0

Documentation

Index

Constants

View Source
const (
	PLAYING_STOP playingSignal = iota
	PLAYING_START
)

Variables

View Source
var Cmd = &cobra.Command{
	Use:   "decode",
	Short: "Drills for decoding the morse code alphabet",
	Long: `This is the subcommand for decoding the morse code alphabet, from letters to sentences.

These are the three things the user can do in here:
  - 'dihdah decode letters': Gives the user drills to decode the morse code alphabet.
  - 'dihdah decode words': Gives the user drills to be proficient on decoding morse code words.
  - 'dihdah decode quotes': Gives the user drills to be proficient on decoding morse code sentences.

Run either 'dihdah decode letters --help', 'dihdah decode words --help', or
'dihdah decode quotes --help' for more details.`,
}
View Source
var LetterCmd = &cobra.Command{
	Use:     "letter",
	Short:   "Train for decoding letters.",
	Aliases: []string{"letters"},
	RunE: func(cmd *cobra.Command, args []string) error {
		detectedNonAlphabet := false
		letters, _ := cmd.Flags().GetString("letters")
		if len(letters) != 0 {
			letters = DedupCleanLetters(letters)
		}

		if detectedNonAlphabet {
			if len(letters) == 0 {
				return fmt.Errorf("Error: --letters has effectively nothing in it.")
			} else {
				cmd.PrintErrln("Warning: Detected non-alphabet characters in --letters, removing...")
			}
		}

		if len(letters) == 0 {
			levelArg, _ := cmd.Flags().GetUint16("level")

			if int(levelArg) > len(NewLettersPerLevel) {
				cmd.PrintErrf("Warning: Level is at most %v. Will be set to max.\n", len(NewLettersPerLevel))
				levelArg = uint16(len(NewLettersPerLevel))
			}

			if levelArg == 0 {
				return fmt.Errorf("Error: --letters is empty.")
			}

			for i := range levelArg {
				letters += NewLettersPerLevel[i]
			}
		}

		dedupedLetters := DedupCleanLetters(letters)
		speed, _ := cmd.Flags().GetFloat64("speed")

		doAllLetters, _ := cmd.Flags().GetBool("recap")
		if doAllLetters {
			allLettersRand := []rune(dedupedLetters)
			rand.Shuffle(len(allLettersRand), func(i, j int) {
				allLettersRand[i], allLettersRand[j] = allLettersRand[j], allLettersRand[i]
			})

			p := tea.NewProgram(NewLetterModel(string(allLettersRand), dedupedLetters, speed, nil))
			if _, err := p.Run(); err != nil {
				return fmt.Errorf("Error running the program: %v", err)
			}

			return nil
		}

		iterations, _ := cmd.Flags().GetUint("iterations")
		if iterations == 0 {
			iterations = max(uint(len(dedupedLetters)/2), 3)
		}

		trainingLetters := ""
		for range iterations {
			randomLetter := letters[rand.Intn(len(letters))]
			trainingLetters += string(randomLetter)
		}

		p := tea.NewProgram(NewLetterModel(trainingLetters, dedupedLetters, speed, nil))
		if _, err := p.Run(); err != nil {
			return fmt.Errorf("Error running the program: %v", err)
		}

		return nil
	},
	Long: `The 'decode letters' command gives the user drills to decode the morse code alphabet.
The flags in this command should be self-explanatory.

# How it works

For each item, you will be given a sound clip to listen. Input the letter corresponding
to the sound. Pressing space or hitting enter when empty will repeat the sound.
Enter to confirm the answer.

====================================================================
Decode training (3 letters) (1 of 3)
> t

(escape/ctrl+c to go back, space to repeat sound, enter to confirm)
====================================================================


At the end of the training session, you will be presented with the correct characters
played. Use that as learning and/or feedback for the next training.

=====================================================
Decoding training results (3 letters, 3 iterations):

 #    Character   Correct?  Answered
 1    t           yes       t
 2    e           yes       e
 3    e           yes       e

(all correct!) (escape / ctrl+c / enter to go back)
=====================================================

# Extras

This is the default letter pool if you specify --level/-l:
    ========================================
    | Level | Letter Pool                  |
    | ----- | ---------------------------- |
    | 1     | the                          |
    | 2     | thedog                       |
    | 3     | thedogbrown                  |
    | 4     | thedogbrownjumps             |
    | 5     | thedogbrownjumpsfoxover      |
    | 6     | thedogbrownjumpsfoxoverquick |
    | 7+    | (everything)                 |
    ========================================

NOTES:
  - If the user is having difficulty differentiating letters, it is recommended
    to run this command with --letters.
  - After being comfortable with a certain --level, it is also recommended to
    run --level with --recap before proceeding with the next --level.
  - For the convenience and the challenge for the user, --speed can be used to
	slow down or speed up the sound being played.`,
}
View Source
var NewLettersPerLevel = [...]string{
	"the",
	"dog",
	"brown",
	"jumps",
	"foxover",
	"quick",
	"lazy",
}
View Source
var QuoteCmd = &cobra.Command{
	Use:     "quote",
	Short:   "Train for decoding quotes.",
	Aliases: []string{"quotes"},
	RunE: func(cmd *cobra.Command, args []string) error {
		quotesFile, _ := cmd.Flags().GetString("quotes")
		fileReader := io.Reader(strings.NewReader(assets.Quotes))

		if len(quotesFile) != 0 {
			file, err := os.Open(quotesFile)
			if err != nil {
				return fmt.Errorf("Error reading %v: %v", quotesFile, err)
			}

			defer file.Close()
			fileReader = io.Reader(file)
		} else {
			quotesFile = "(the default quotes file)"
		}

		quotes := []string(nil)
		scanner := bufio.NewScanner(fileReader)

		for scanner.Scan() {
			quote := strings.TrimSpace(scanner.Text())

			hasContent := false
			for _, r := range quote {
				if r >= 'a' && r <= 'z' {
					hasContent = true
					break
				}

				if r >= 'A' && r <= 'Z' {
					hasContent = true
					break
				}
			}

			if hasContent {
				quotes = append(quotes, quote)
			}
		}

		if err := scanner.Err(); err != nil {
			return fmt.Errorf("Error scanning %v: %v", quotesFile, err)
		}

		if len(quotes) == 0 {
			return fmt.Errorf("Error reading %v: File is empty", quotesFile)
		}

		randomQuote := quotes[rand.Intn(len(quotes))]

		speed, _ := cmd.Flags().GetFloat64("speed")
		if speed == 0 {
			return fmt.Errorf("Speed must not be zero.")
		}

		p := tea.NewProgram(NewQuoteModel(randomQuote, speed, nil))
		if _, err := p.Run(); err != nil {
			return fmt.Errorf("Error running the program: %v", err)
		}

		return nil
	},
	Long: `The 'decode quotes' command gives the user drills to decode sentences.
The flags should be self-explanatory.

# How it works

You will be given a long sound clip, which is an encoded morse code sentence. Ctrl+l
will either stop or play the clip. Ctrl+c will either clear your input or go back.
Ctrl+s will confirm your input.

===========================================================================================
Decode quote training

┃  1 ?????
┃
┃
┃
┃
┃

(ctrl+l to stop/restart playing, ctrl+s to confirm answer, ctrl+c/esc to clear or go back)
===========================================================================================

At the end of the training session, you will be presented with the correct quote
played. Use that as learning and/or feedback for the next training session.

=========================================
Decode quote training results

   Do all things with love. - Og Mandino
      ?         ?
>> do nll things_with love    og mandino

(1/28 mistakes) (ctrl+c/esc to go back)
=========================================

NOTE:
- For the convenience and challenge, --speed can be used to slow down or speed
up the sound being played.`,
}
View Source
var WordCmd = &cobra.Command{
	Use:     "word",
	Short:   "Train for decoding words.",
	Aliases: []string{"words"},
	RunE: func(cmd *cobra.Command, args []string) error {
		iterations, _ := cmd.Flags().GetUint16("iterations")
		if iterations == 0 {
			return fmt.Errorf("--iterations is set to zero.")
		}

		levelArg, _ := cmd.Flags().GetUint16("level")
		wordLength, _ := cmd.Flags().GetUint16("w-length")
		charGap, gapGetErr := cmd.Flags().GetUint16("gap")

		if wordLength == 0 {
			if levelArg == 0 {
				return fmt.Errorf("--level and --w-length are both set to zero.\n")
			}

			if int(levelArg) > len(WordOptPerLevel) {
				cmd.PrintErrf("Warning: Max level for decoding words is %v.\n", len(WordOptPerLevel))
				levelArg = uint16(len(WordOptPerLevel))
			}

			wordLength = WordOptPerLevel[levelArg-1].MaxLen
			if gapGetErr != nil {
				charGap = WordOptPerLevel[levelArg-1].CharGap
			}
		}

		speed, _ := cmd.Flags().GetFloat64("speed")
		charGapInSeconds := float64(time.Duration(charGap)*commons.DefaultDitDuration) * speed

		if charGapInSeconds > float64(time.Second*10) || charGap < commons.CharGapMinimum {
			return fmt.Errorf("--gap is invalid (either too long or is zero)\n")
		}

		wordFile, _ := cmd.Flags().GetString("words")
		fileReader := io.Reader(strings.NewReader(assets.Words))

		if len(wordFile) != 0 {
			file, err := os.Open(wordFile)
			if err != nil {
				return fmt.Errorf("Error opening %v: %v\n", wordFile, file)
			}

			defer file.Close()
			fileReader = io.Reader(file)
		} else {
			wordFile = "(the default word file)"
		}

		wordPool := []string(nil)
		scanner := bufio.NewScanner(fileReader)

		scanner.Split(bufio.ScanWords)
		for scanner.Scan() {
			word := scanner.Text()
			word = strings.Map(func(r rune) rune {
				if r >= 'a' && r <= 'z' {
					return r
				}

				if r >= 'A' && r <= 'Z' {
					return r
				}

				if r == '-' {
					return r
				}

				return -1
			}, word)

			if len(word) <= int(wordLength) || int(wordLength) >= len(WordOptPerLevel) {
				wordPool = append(wordPool, word)
			}
		}

		if err := scanner.Err(); err != nil {
			return fmt.Errorf("Error reading through %v: %v", wordFile, err)
		}

		words := []string(nil)
		for range min(len(wordPool), int(iterations)) {
			wordIdx := rand.Intn(len(wordPool))
			words = append(words, wordPool[wordIdx])

			wordPool[wordIdx] = wordPool[len(wordPool)-1]
			wordPool = wordPool[:len(wordPool)-1]
		}

		p := tea.NewProgram(NewWordModel(words, wordLength, charGap, speed, nil))

		if _, err := p.Run(); err != nil {
			return fmt.Errorf("Error running the model: %v\n", err)
		}

		return nil
	},
	Long: `The 'decode words' command gives the user drills to decode morse code words.
The flags in this command should be self-explanatory.

# How it works

For each item, you will be given a sound clip to listen. Input the word corresponding
to the sound. Pressing space or hitting enter when empty will repeat the sound.
Enter to confirm the answer.

====================================================================
Decode training (5 letter limit) (1 of 5)
> egg

(escape/ctrl+c to go back, space to repeat sound, enter to confirm)
====================================================================

At the end of the training session, you will be presented with the correct words
together with your input. Use that as learning and/or feedback for the next training session.

================================================================
Decoding words training results (5 letter limit, 5 iterations):

 #    Word   Correct?  Input
 1    egg    yes       egg

 2    type   no        tove
                        ??
 3    smart  yes       smart

 4    loose  no        loost
                           ?
 5    part   yes       part

(2/5 mistakes) (escape / ctrl+c / enter to go back)
================================================================

# Extras

This is the default word lengths and character gaps if you specify --level/-l:
    =============================================
    | Level | Word Length Limit | Character Gap |
    | ----- | ----------------- | ------------- |
    | 1     | 5                 | 16            |
    | 2     | 5                 | 12            |
    | 3     | 5                 | 9             |
    | 4     | 5                 | 6             |
    | 5     | 5                 | 3             |
    | 6     | 7                 | 3             |
    | 7     | 10                | 3             |
    | 8+    | 16                | 3             |
    =============================================

NOTE:
- For the convenience and the challenge, --speed can be used to slow down or speed
up the sound being played.`,
}
View Source
var WordOptPerLevel = [...]wordOpt{
	{16, 5},
	{12, 5},
	{9, 5},
	{6, 5},
	{3, 5},
	{3, 7},
	{3, 10},
	{3, 16},
}

Functions

func DedupCleanLetters

func DedupCleanLetters(str string) string

func InitQuoteTrainingResults

func InitQuoteTrainingResults(userAnswerStr string, realAnswerStr string) (displayedResults string, corrects int, total int)

func NewLetterModel

func NewLetterModel(trainingLetters string, lettersUsed string, speed float64, backRef tea.Model) *letterModel

func NewQuoteModel

func NewQuoteModel(quote string, speed float64, backReference tea.Model) *quoteModel

func NewWordModel

func NewWordModel(words []string, maxLen uint16, charGap uint16, speed float64, backReference tea.Model) *wordModel

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL