tipitypy/main.go

182 lines
3.5 KiB
Go
Raw Permalink Normal View History

2025-02-15 18:06:22 +01:00
package main
import (
"bufio"
"fmt"
"log"
"os"
2025-02-15 23:34:09 +01:00
"os/signal"
2025-02-15 23:56:53 +01:00
"strings"
2025-02-15 23:34:09 +01:00
"syscall"
2025-02-15 18:06:22 +01:00
"time"
"tipitypy/colorizer"
2025-02-15 23:34:09 +01:00
"tipitypy/db"
2025-02-15 18:06:22 +01:00
"tipitypy/reader"
"golang.org/x/term"
)
2025-02-15 23:34:09 +01:00
func finish(start time.Time, stat *db.Stat, finished bool) {
past := time.Since(start)
stat.Skipped = globalStat.Skipped
stat.Correct = globalStat.Correct
stat.False = globalStat.False
stat.Words = globalStat.Words
stat.Finished = finished
stat.CPM = float64(globalStat.Correct) / past.Minutes()
stat.WPM = float64(globalStat.Words) / past.Minutes()
2025-02-15 23:34:09 +01:00
stat.TimeTaken = past.Milliseconds()
2025-02-15 18:06:22 +01:00
fmt.Printf("\r\n%s", colorizer.Colors.Reset())
2025-02-15 23:56:53 +01:00
fmt.Print(past)
fmt.Printf("\r\n")
2025-02-15 18:06:22 +01:00
}
2025-02-15 23:34:09 +01:00
func ChooseToPrintColorized(r rune) string {
if Color {
return colorizer.Accept(r)
2025-02-15 18:06:22 +01:00
}
2025-02-15 23:34:09 +01:00
return string(r)
}
type ExitState int
const (
ExitStateUnspecified = 1 + iota
ExitStateBreak
ExitStateQuit
ExitStateSuccess
)
func SingleCycle() (*db.Stat, int, error) {
2025-02-15 23:56:53 +01:00
words, err := reader.GetSourceWords()
2025-02-15 18:06:22 +01:00
if err != nil {
2025-02-15 23:34:09 +01:00
return nil, ExitStateUnspecified, fmt.Errorf("reader.GetSourceLine: %w", err)
2025-02-15 18:06:22 +01:00
}
2025-02-15 23:56:53 +01:00
source := strings.Join(words, " ")
2025-02-15 18:06:22 +01:00
log.Print("Start of tipitypy")
2025-02-15 23:34:09 +01:00
if Color {
colorizer.PrintColorized(source)
} else {
fmt.Println(source)
2025-02-15 18:06:22 +01:00
}
2025-02-15 23:34:09 +01:00
fmt.Printf("\r\n")
2025-02-15 18:06:22 +01:00
//
2025-02-15 23:56:53 +01:00
wordIdx := 0
wordRunesI := 0
2025-02-15 18:06:22 +01:00
p := []rune(source)
i := 0
//
2025-02-15 23:34:09 +01:00
finished := true
2025-02-15 23:56:53 +01:00
stat := &db.Stat{
InHardMode: HardMode,
}
2025-02-15 18:06:22 +01:00
reader := bufio.NewReader(os.Stdin)
2025-02-15 23:56:53 +01:00
startTime := time.Now()
defer finish(startTime, stat, finished)
2025-02-15 18:06:22 +01:00
for {
r, _, err := reader.ReadRune()
if err != nil {
2025-02-15 23:34:09 +01:00
return stat, ExitStateUnspecified, fmt.Errorf("reader.ReadRune: %w", err)
2025-02-15 18:06:22 +01:00
}
log.Printf("Read %c ; %d as rune", r, r)
2025-02-15 23:34:09 +01:00
// CTRL + Q
if r == 17 {
finished = false
return stat, ExitStateQuit, nil
}
2025-02-15 18:06:22 +01:00
// CTRL + C
if r == 3 {
2025-02-15 23:34:09 +01:00
finished = false
return stat, ExitStateBreak, nil
2025-02-15 18:06:22 +01:00
}
// CTRL + D
if r == 4 {
// TODO: Delete
}
// CTRL + S
if r == 19 {
globalStat.Skipped++
2025-02-15 23:34:09 +01:00
fmt.Printf("%s", ChooseToPrintColorized(p[i]))
2025-02-15 18:06:22 +01:00
i++
continue
}
// Check
if p[i] == r {
globalStat.Correct++
2025-02-15 23:34:09 +01:00
fmt.Printf("%s", ChooseToPrintColorized(r))
2025-02-15 18:06:22 +01:00
i++
2025-02-15 23:56:53 +01:00
wordRunesI++
if r == ' ' {
globalStat.Words++
wordIdx++
wordRunesI = 0
}
2025-02-15 18:06:22 +01:00
if i == len(p) {
break
}
} else {
2025-02-15 23:56:53 +01:00
if HardMode && r != ' ' {
log.Printf("removing %d runes", wordRunesI)
fmt.Printf("%s", strings.Repeat("\b", wordRunesI))
colorizer.ClearFromCursorToEnd()
i -= wordRunesI
wordRunesI = 0
}
2025-02-15 18:06:22 +01:00
globalStat.False++
}
}
2025-02-15 23:34:09 +01:00
globalStat.Words++
return stat, ExitStateSuccess, nil
}
func main() {
logFile, err := os.OpenFile("ttt.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
defer logFile.Close()
log.SetOutput(logFile)
ParseOptions()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
// Put the terminal in raw mode to read characters as they are typed
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
fmt.Printf("term.MakeRaw: %v\n", err)
os.Exit(1)
}
defer term.Restore(int(os.Stdin.Fd()), oldState) // Restore terminal state at the end
// This way we can still defer term.Restore function
c := make(chan bool, 1)
c <- true
if IsEndless {
go func() {
for {
c <- true
}
}()
} else {
close(c)
}
//
outer:
for range c {
stat, state, err := SingleCycle()
if err != nil {
log.Printf("ERROR: %v", err)
}
switch state {
case ExitStateQuit:
break outer
case ExitStateSuccess:
if err := db.Connect().Create(stat).Error; err != nil {
log.Printf("ERROR: dbc.Create: %v", err)
}
}
globalStat.Reset()
2025-02-15 23:34:09 +01:00
}
2025-02-15 18:06:22 +01:00
}