Skip to content

Commit c802c02

Browse files
authored
Add pac-man classic chomp and colored dominoes indeterminate indicators (#370)
1 parent 18e8a01 commit c802c02

File tree

2 files changed

+196
-0
lines changed

2 files changed

+196
-0
lines changed

progress/indicator.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package progress
22

33
import (
44
"strings"
5+
"sync/atomic"
56
"time"
67

78
"github.com/jedib0t/go-pretty/v6/text"
@@ -56,6 +57,91 @@ func IndeterminateIndicatorPacMan(duration time.Duration) IndeterminateIndicator
5657
return timedIndeterminateIndicatorGenerator(indeterminateIndicatorPacMan(), duration)
5758
}
5859

60+
// IndeterminateIndicatorColoredDominoes simulates a bunch of colored dominoes falling back and
61+
// forth.
62+
func IndeterminateIndicatorColoredDominoes(duration time.Duration, slashColor, backslashColor text.Color) IndeterminateIndicatorGenerator {
63+
baseGen := IndeterminateIndicatorDominoes(duration)
64+
return func(maxLen int) IndeterminateIndicator {
65+
base := baseGen(maxLen)
66+
colored := strings.Builder{}
67+
for _, ch := range base.Text {
68+
switch ch {
69+
case '/':
70+
colored.WriteString(text.Colors{slashColor}.Sprint(string(ch)))
71+
case '\\':
72+
colored.WriteString(text.Colors{backslashColor}.Sprint(string(ch)))
73+
default:
74+
colored.WriteRune(ch)
75+
}
76+
}
77+
return IndeterminateIndicator{
78+
Position: 0,
79+
Text: colored.String(),
80+
}
81+
}
82+
}
83+
84+
// IndeterminateIndicatorPacManChomp simulates a Pac-Man character chomping through the progress
85+
// bar back and forth.
86+
func IndeterminateIndicatorPacManChomp(duration time.Duration) IndeterminateIndicatorGenerator {
87+
return timedIndeterminateIndicatorGenerator(indeterminateIndicatorPacManChomp(), duration)
88+
}
89+
90+
func indeterminateIndicatorPacManChomp() IndeterminateIndicatorGenerator {
91+
var frame int64
92+
93+
return func(maxLen int) IndeterminateIndicator {
94+
i := atomic.AddInt64(&frame, 1)
95+
cycle := i / int64(maxLen-1)
96+
pos := int(i % int64(maxLen-1))
97+
98+
leftToRight := cycle%2 == 0
99+
if !leftToRight {
100+
pos = (maxLen - 1) - pos
101+
}
102+
103+
// Alternate between open and closed mouth
104+
mouthOpen := (i/3)%2 == 0
105+
pac := "c"
106+
if !leftToRight {
107+
pac = "ɔ"
108+
}
109+
if !mouthOpen {
110+
pac = "●"
111+
}
112+
113+
trail := make([]string, maxLen)
114+
for j := 0; j < maxLen; j++ {
115+
trail[j] = "·"
116+
}
117+
118+
for j := 0; j < maxLen; j++ {
119+
if (leftToRight && j < pos) || (!leftToRight && j > pos) {
120+
trail[j] = " "
121+
}
122+
}
123+
124+
trail[pos] = pac
125+
126+
var line string
127+
for j := 0; j < maxLen; j++ {
128+
switch {
129+
case j == pos:
130+
line += text.Colors{text.FgHiYellow}.Sprint(trail[j])
131+
case trail[j] == "·":
132+
line += text.Colors{text.FgWhite}.Sprint(trail[j])
133+
default:
134+
line += trail[j]
135+
}
136+
}
137+
138+
return IndeterminateIndicator{
139+
Position: 0,
140+
Text: line,
141+
}
142+
}
143+
}
144+
59145
func indeterminateIndicatorDominoes() IndeterminateIndicatorGenerator {
60146
direction := 1 // positive == left to right; negative == right to left
61147
nextPosition := 0

progress/indicator_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"testing"
77
"time"
88

9+
"github.com/jedib0t/go-pretty/v6/text"
910
"github.com/stretchr/testify/assert"
1011
)
1112

@@ -58,6 +59,61 @@ func TestIndeterminateIndicatorDominoes(t *testing.T) {
5859
}
5960
}
6061

62+
func TestIndeterminateIndicatorColoredDominoes(t *testing.T) {
63+
maxLen := 10
64+
colorize := func(s string) string {
65+
s = strings.ReplaceAll(s, "/", text.FgHiGreen.Sprint("/"))
66+
s = strings.ReplaceAll(s, "\\", text.FgHiBlack.Sprint("\\"))
67+
return s
68+
}
69+
70+
expectedTexts := []string{
71+
colorize(`\\\\\\\\\\`),
72+
colorize(`/\\\\\\\\\`),
73+
colorize(`//\\\\\\\\`),
74+
colorize(`///\\\\\\\`),
75+
colorize(`////\\\\\\`),
76+
colorize(`/////\\\\\`),
77+
colorize(`//////\\\\`),
78+
colorize(`///////\\\`),
79+
colorize(`////////\\`),
80+
colorize(`/////////\`),
81+
colorize(`//////////`),
82+
colorize(`/////////\`),
83+
colorize(`////////\\`),
84+
colorize(`///////\\\`),
85+
colorize(`//////\\\\`),
86+
colorize(`/////\\\\\`),
87+
colorize(`////\\\\\\`),
88+
colorize(`///\\\\\\\`),
89+
colorize(`//\\\\\\\\`),
90+
colorize(`/\\\\\\\\\`),
91+
colorize(`\\\\\\\\\\`),
92+
colorize(`/\\\\\\\\\`),
93+
colorize(`//\\\\\\\\`),
94+
colorize(`///\\\\\\\`),
95+
colorize(`////\\\\\\`),
96+
colorize(`/////\\\\\`),
97+
colorize(`//////\\\\`),
98+
colorize(`///////\\\`),
99+
colorize(`////////\\`),
100+
colorize(`/////////\`),
101+
}
102+
103+
out := strings.Builder{}
104+
f := IndeterminateIndicatorColoredDominoes(time.Millisecond*10, text.FgHiGreen, text.FgHiBlack)
105+
for idx, expectedText := range expectedTexts {
106+
actual := f(maxLen)
107+
assert.Equal(t, 0, actual.Position, fmt.Sprintf("expectedTexts[%d]", idx))
108+
assert.Equal(t, expectedText, actual.Text, fmt.Sprintf("expectedTexts[%d]", idx))
109+
out.WriteString(fmt.Sprintf("`%v`,\n", actual.Text))
110+
time.Sleep(time.Millisecond * 10)
111+
}
112+
if t.Failed() {
113+
fmt.Println(out.String())
114+
}
115+
}
116+
61117
func TestIndeterminateIndicatorMovingBackAndForth(t *testing.T) {
62118
maxLen := 10
63119
indicator := "<=>"
@@ -286,3 +342,57 @@ func TestIndeterminateIndicatorPacMan(t *testing.T) {
286342
fmt.Println(out.String())
287343
}
288344
}
345+
346+
func TestIndeterminateIndicatorPacManChomp(t *testing.T) {
347+
maxLen := 10
348+
colorize := func(s string) string {
349+
s = strings.ReplaceAll(s, "·", text.FgWhite.Sprint("·"))
350+
s = strings.ReplaceAll(s, "●", text.FgHiYellow.Sprint("●"))
351+
s = strings.ReplaceAll(s, "ɔ", text.FgHiYellow.Sprint("ɔ"))
352+
s = strings.ReplaceAll(s, "c", text.FgHiYellow.Sprint("c"))
353+
return s
354+
}
355+
356+
expectedTexts := []string{
357+
colorize(" c········"),
358+
colorize(" c·······"),
359+
colorize(" ●······"),
360+
colorize(" ●·····"),
361+
colorize(" ●····"),
362+
colorize(" c···"),
363+
colorize(" c··"),
364+
colorize(" c·"),
365+
colorize("·········●"),
366+
colorize("········● "),
367+
colorize("·······● "),
368+
colorize("······ɔ "),
369+
colorize("·····ɔ "),
370+
colorize("····ɔ "),
371+
colorize("···● "),
372+
colorize("··● "),
373+
colorize("·● "),
374+
colorize("c·········"),
375+
colorize(" c········"),
376+
colorize(" c·······"),
377+
colorize(" ●······"),
378+
colorize(" ●·····"),
379+
colorize(" ●····"),
380+
colorize(" c···"),
381+
colorize(" c··"),
382+
colorize(" c·"),
383+
colorize("·········●"),
384+
colorize("········● "),
385+
}
386+
387+
out := strings.Builder{}
388+
f := IndeterminateIndicatorPacManChomp(time.Millisecond * 10)
389+
for idx, expectedText := range expectedTexts {
390+
actual := f(maxLen)
391+
assert.Equal(t, expectedText, actual.Text, fmt.Sprintf("expectedTexts[%d]", idx))
392+
out.WriteString(fmt.Sprintf("%#v,\n", actual.Text))
393+
time.Sleep(time.Millisecond * 10)
394+
}
395+
if t.Failed() {
396+
fmt.Println(out.String())
397+
}
398+
}

0 commit comments

Comments
 (0)