268 lines
5.9 KiB
Go
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)
|
||
|
}
|