TDL/generator.go
2020-11-04 22:27:06 +01:00

268 lines
5.9 KiB
Go

package main
import (
"bufio"
"fmt"
"io"
"os"
"strconv"
"strings"
)
type Generator interface {
Generate(TDL)
}
func semanticError(v ...interface{}) {
v = append([]interface{}{"semantic error"}, v...)
fmt.Fprintln(os.Stderr, v...)
os.Exit(1)
}
func formatFloat(f float64) string {
return strconv.FormatFloat(f, 'f', 2, 64)
}
func minOne(i int) int {
if i < 1 {
return 1
} else {
return i
}
}
type tableLine struct {
cols [3]string
}
func newTableLine(first, second, third string) tableLine {
return tableLine{[3]string{first, second, third}}
}
func columnLengths(lines []tableLine) [3]int {
max := [3]int{0, 0, 0}
for _, line := range lines {
for i := 0; i < len(line.cols); i++ {
if n := len([]rune(line.cols[i])); n > max[i] {
max[i] = n
}
}
}
return max
}
func getTableFormat(lengths [3]int) string {
format := "|"
for _, length := range lengths {
format += " %-" + strconv.Itoa(length) + "s |"
}
return format + "\n"
}
type StdGenerator struct {
schedule TDL
plates map[float64]int
writer *bufio.Writer
}
func NewStdGenerator(writer io.Writer) *StdGenerator {
return &StdGenerator{writer: bufio.NewWriter(writer)}
}
func (g *StdGenerator) write(v ...string) {
for _, s := range v {
g.writer.WriteString(s)
}
}
func (g *StdGenerator) writeLine(v ...string) {
g.write(v...)
g.write("\n")
}
func (g *StdGenerator) writeTable(heading tableLine, lines []tableLine) {
lengths := columnLengths(append(lines, heading))
formatString := getTableFormat(lengths)
fmt.Fprintf(g.writer, formatString, heading.cols[0], heading.cols[1], heading.cols[2])
fmt.Fprintf(g.writer, formatString, strings.Repeat("-", lengths[0]), strings.Repeat("-", lengths[1]), strings.Repeat("-", lengths[2]))
for _, line := range lines {
fmt.Fprintf(g.writer, formatString, line.cols[0], line.cols[1], line.cols[2])
}
}
func (g *StdGenerator) init(schedule TDL) {
g.schedule = schedule.Clone()
g.plates = make(map[float64]int)
for _, plate := range schedule.Plates {
if _, ok := g.plates[plate.Weight]; ok {
g.plates[plate.Weight] += plate.Amount
} else {
g.plates[plate.Weight] = plate.Amount
}
}
if len(g.plates) == 0 {
g.plates = map[float64]int{
20.0: 26,
10.0: 2,
5.00: 2,
2.50: 2,
1.25: 2,
}
}
for i := range g.schedule.Lifts {
if g.schedule.Lifts[i].Bar == 0 {
g.schedule.Lifts[i].Bar = 20
}
}
}
func (g *StdGenerator) Generate(schedule TDL) {
g.init(schedule)
g.printMaxes()
g.writeLine()
g.printTrainingDays()
g.printMaxes()
g.writer.Flush()
}
func (g *StdGenerator) getLift(name string) *Lift {
for i := range g.schedule.Lifts {
if lift := &g.schedule.Lifts[i]; lift.Name == name {
return lift
}
}
semanticError("unknown lift '", name, "'")
return nil
}
func (g *StdGenerator) increaseLift(lift *Lift, amount float64, relative bool) {
if amount == 0 {
amount = lift.Increment
relative = lift.IncrementPercent
}
if relative {
lift.Max *= amount / 100.0
} else {
lift.Max += amount
}
}
func (g *StdGenerator) getSetTemplate(name string) SetTemplate {
for _, template := range g.schedule.SetTemplates {
if template.Name == name {
return template
}
}
semanticError("unknown set template '", name, "'")
return SetTemplate{}
}
func (g *StdGenerator) printMaxes() {
g.writeLine("Maxes")
g.writeLine("=====\n")
for _, lift := range g.schedule.Lifts {
g.writeLine(lift.Name, ": ", formatFloat(lift.Max))
}
}
func (g *StdGenerator) printTrainingDays() {
for i, day := range g.schedule.TrainingDays {
g.writeLine("Day ", strconv.Itoa(i+1))
g.writeLine("======\n")
for _, item := range day.Items {
g.printTrainingDayItem(item)
}
}
}
func (g *StdGenerator) printTrainingDayItem(item TrainingDayItem) {
if item.Raw != "" {
g.writeLine(item.Raw)
} else {
g.printLiftSchedule(item.LiftSchedule)
}
g.writeLine()
}
func (g *StdGenerator) printLiftSchedule(ls LiftSchedule) {
lift := g.getLift(ls.LiftName)
g.writeLine(lift.Name)
g.writeLine("--------\n")
heading := newTableLine("Set", "Plates", "Resulting")
lines := []tableLine{}
for _, item := range ls.Items {
g.linesSetTemplateItem(&lines, lift, item)
}
g.writeTable(heading, lines)
if ls.Increase {
g.increaseLift(lift, ls.IncreaseAmount, ls.IncreasePercent)
}
}
func (g *StdGenerator) linesSetTemplate(lines *[]tableLine, lift *Lift, template SetTemplate) {
for _, item := range template.Items {
g.linesSetTemplateItem(lines, lift, item)
}
return
}
func (g *StdGenerator) linesSetTemplateItem(lines *[]tableLine, lift *Lift, item SetTemplateItem) {
for i := 0; i < minOne(item.Amount); i++ {
if item.ReferenceName != "" {
g.linesSetTemplate(lines, lift, g.getSetTemplate(item.ReferenceName))
} else {
g.linesSet(lines, lift, item.Set)
}
}
return
}
func (g *StdGenerator) linesSet(lines *[]tableLine, lift *Lift, set Set) {
weight := lift.Max*set.Percentage/100.0 + set.PlusWeight
plates, resulting := g.searchPlates(weight, lift.Bar)
firstColumn := formatFloat(weight) + " x " + strconv.Itoa(set.Reps)
if set.Amount > 1 {
firstColumn += " x " + strconv.Itoa(set.Amount)
}
if set.Notice != "" {
firstColumn += " " + set.Notice
}
*lines = append(*lines, newTableLine(firstColumn, plates, resulting))
return
}
func (g *StdGenerator) searchPlates(weight float64, bar float64) (string, string) {
remaining := weight - bar
if remaining <= 0 {
return "", formatFloat(bar)
}
plateSequence := SearchPlates(g.plates, remaining)
resulting := bar
platesCounted := make(map[float64]int)
for _, plate := range plateSequence {
platesCounted[plate] = 0
resulting += 2 * plate
}
for _, plate := range plateSequence {
platesCounted[plate] += 1
}
plateString := ""
for _, plate := range plateSequence {
if platesCounted[plate] == 0 {
continue
}
count := platesCounted[plate]
platesCounted[plate] = 0
if plateString != "" {
plateString += ", "
}
plateString += fmt.Sprint(plate)
if count > 1 {
plateString += "x" + strconv.Itoa(count)
}
}
return plateString, formatFloat(resulting)
}