|
@@ -0,0 +1,440 @@
|
|
|
+package door
|
|
|
+
|
|
|
+import (
|
|
|
+ "log"
|
|
|
+ "math/rand"
|
|
|
+ "time"
|
|
|
+ "unicode"
|
|
|
+)
|
|
|
+
|
|
|
+/*
|
|
|
+No More Secrets - from "Sneakers"
|
|
|
+https://github.com/bartobri/no-more-secrets
|
|
|
+
|
|
|
+*/
|
|
|
+
|
|
|
+type NoMoreSecretsConfig struct {
|
|
|
+ Jumble_Sec int // in sec
|
|
|
+ Jumble_Loop_Speed int // in ms
|
|
|
+ Reveal_Loop_Speed int // in ms
|
|
|
+ Max_Time int // Max value before reveal (per character)
|
|
|
+ Color string // Color to use before reveal
|
|
|
+}
|
|
|
+
|
|
|
+// The default configuration for NoMoreSecrets
|
|
|
+var NoMoreSecretsDefault NoMoreSecretsConfig
|
|
|
+
|
|
|
+// Initialize the Defaults
|
|
|
+func init() {
|
|
|
+ NoMoreSecretsDefault = NoMoreSecretsConfig{
|
|
|
+ Jumble_Sec: 2,
|
|
|
+ Jumble_Loop_Speed: 35,
|
|
|
+ Reveal_Loop_Speed: 50,
|
|
|
+ Max_Time: 5000,
|
|
|
+ Color: ColorText("CYAN ON BLACK"),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Get random character code for jumble
|
|
|
+// We use chr 0x21 - 0xdf (excluding 0x7f)
|
|
|
+func getRandom() byte {
|
|
|
+ // 0x7f = backspace / rubout
|
|
|
+ var rb byte = 0x7f
|
|
|
+ for rb == 0x7f {
|
|
|
+ rb = byte(rand.Intn(0xdf-0x21) + 0x21)
|
|
|
+ }
|
|
|
+ return rb
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+NoMoreSecrets - render output as random, then fade in the original text.
|
|
|
+
|
|
|
+Example:
|
|
|
+
|
|
|
+func About_Example_NoMoreSecrets(d *door.Door) {
|
|
|
+ W := 60
|
|
|
+ center_x := (door.Width - W) / 2
|
|
|
+ center_y := (door.Height - 16) / 2
|
|
|
+ about := door.Panel{X: center_x,
|
|
|
+ Y: center_y,
|
|
|
+ Width: W,
|
|
|
+ Style: door.SINGLE_DOUBLE,
|
|
|
+ BorderColor: door.ColorText("BOLD YELLOW ON BLUE"),
|
|
|
+ }
|
|
|
+ about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, "About This Door"),
|
|
|
+ DefaultColor: door.ColorText("BOLD CYAN ON BLUE")})
|
|
|
+ about.Lines = append(about.Lines, about.Spacer())
|
|
|
+
|
|
|
+ about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, "Test Door written in go, using go door.")})
|
|
|
+ var copyright string = "(C) 2022 Bugz, Red Green Software"
|
|
|
+
|
|
|
+ if door.Unicode {
|
|
|
+ copyright = strings.Replace(copyright, "(C)", "\u00a9", -1)
|
|
|
+ }
|
|
|
+
|
|
|
+ about.Lines = append(about.Lines,
|
|
|
+ door.Line{Text: fmt.Sprintf("%*s", -W, copyright),
|
|
|
+ DefaultColor: door.ColorText("BOLD WHITE ON BLUE")})
|
|
|
+ for _, text := range []string{"",
|
|
|
+ "This door was written by Bugz.",
|
|
|
+ "",
|
|
|
+ "It is written in Go, understands CP437 and unicode, adapts",
|
|
|
+ "to screen sizes, uses door32.sys, supports TheDraw Fonts,",
|
|
|
+ "and runs on Linux and Windows."} {
|
|
|
+ about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, text)})
|
|
|
+ }
|
|
|
+
|
|
|
+ better := door.NoMoreSecretsDefault
|
|
|
+ better.Color = door.ColorText("BOLD CYAN ON BLUE")
|
|
|
+
|
|
|
+ door.NoMoreSecrets(about.Output(), d, &better)
|
|
|
+}
|
|
|
+*/
|
|
|
+func NoMoreSecrets(output string, Door *Door, config *NoMoreSecretsConfig) {
|
|
|
+ // Find ANSI codes, strip color (We'll handle color)
|
|
|
+
|
|
|
+ rand.Seed(time.Now().UnixNano())
|
|
|
+
|
|
|
+ if Unicode {
|
|
|
+ var original []rune = []rune(output) // original / master
|
|
|
+ var work []rune = make([]rune, 0) // work copy we modify
|
|
|
+ var workpos int = 0 // where we are in the work copy
|
|
|
+ var charpos []int // where characters are we can change
|
|
|
+ var chartime []int // character time / reveal timeout
|
|
|
+ var revealpos map[int]int // workpos to original position
|
|
|
+ var colormap map[int]string // index to color end position (workpos)
|
|
|
+ var coloridx []int // index of positions (map isn't ordered)
|
|
|
+ var lastcolor []int // LastColor tracking for keeping colors proper
|
|
|
+ var currentANSI string // ANSI Escape code we've extracted
|
|
|
+ var ANSIchar rune // Last character of the ANSI Escape code
|
|
|
+
|
|
|
+ colormap = make(map[int]string)
|
|
|
+ revealpos = make(map[int]int)
|
|
|
+ coloridx = make([]int, 0)
|
|
|
+
|
|
|
+ // default color = "reset"
|
|
|
+ lastcolor = make([]int, 1)
|
|
|
+ lastcolor[0] = 0
|
|
|
+
|
|
|
+ // Not using range, I want to be able to look ahead and modify
|
|
|
+ // x.
|
|
|
+
|
|
|
+ for x := 0; x < len(original); x++ {
|
|
|
+ var char rune = original[x]
|
|
|
+ if char == '\x1b' {
|
|
|
+ // ANSI Code
|
|
|
+ currentANSI = "\x1b["
|
|
|
+
|
|
|
+ if original[x+1] != '[' {
|
|
|
+ log.Println("NoMoreSecrets: Found \\x1b not followed by [!")
|
|
|
+ }
|
|
|
+ x += 2
|
|
|
+ for {
|
|
|
+ currentANSI += string(original[x])
|
|
|
+ if unicode.IsLetter(original[x]) {
|
|
|
+ ANSIchar = original[x]
|
|
|
+ break
|
|
|
+ }
|
|
|
+ x++
|
|
|
+ }
|
|
|
+
|
|
|
+ // log.Printf("curentANSI: END @ %d [%#v]\n", x, currentANSI[1:])
|
|
|
+
|
|
|
+ // Is this a color code?
|
|
|
+ if ANSIchar == 'm' {
|
|
|
+ // Yes, don't store in work. Process code.
|
|
|
+
|
|
|
+ Door.UpdateLastColor(currentANSI, &lastcolor)
|
|
|
+ colormap[workpos] = Color(lastcolor...)
|
|
|
+ coloridx = append(coloridx, workpos)
|
|
|
+ // log.Printf("Added %d with %s\n", workpos, colormap[workpos][1:])
|
|
|
+ } else {
|
|
|
+ // Not a color code. Add to work.
|
|
|
+ var ANSIrunes []rune = []rune(currentANSI)
|
|
|
+ work = append(work, ANSIrunes...)
|
|
|
+ workpos += len(ANSIrunes)
|
|
|
+ }
|
|
|
+ currentANSI = ""
|
|
|
+ } else {
|
|
|
+ // Not escape, so what is it?
|
|
|
+ if unicode.IsPrint(char) {
|
|
|
+
|
|
|
+ if char == ' ' {
|
|
|
+ work = append(work, char)
|
|
|
+ chartime = append(chartime, 0)
|
|
|
+ } else {
|
|
|
+ work = append(work, char)
|
|
|
+ chartime = append(chartime, rand.Intn(config.Max_Time+1))
|
|
|
+ }
|
|
|
+
|
|
|
+ charpos = append(charpos, workpos)
|
|
|
+ revealpos[workpos] = x
|
|
|
+ workpos++
|
|
|
+ } else {
|
|
|
+ // control code, CR NL.
|
|
|
+ work = append(work, char)
|
|
|
+ workpos++
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // jumble loop
|
|
|
+ var renderF func() string = func() string {
|
|
|
+ var result string
|
|
|
+ var lastcolor string
|
|
|
+ var pos int = 0
|
|
|
+
|
|
|
+ for idx, char := range work {
|
|
|
+ _, found := revealpos[idx]
|
|
|
+ if found {
|
|
|
+ for charpos[pos] != idx {
|
|
|
+ pos++
|
|
|
+ }
|
|
|
+ // This is a character
|
|
|
+ if chartime[pos] != 0 && char != ' ' {
|
|
|
+ if lastcolor != config.Color {
|
|
|
+ result += config.Color
|
|
|
+ lastcolor = config.Color
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // look up the color in the colormap
|
|
|
+ var best string
|
|
|
+
|
|
|
+ // use the coloridx, lookup in colormap
|
|
|
+ for _, cpos := range coloridx {
|
|
|
+ if cpos > idx {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ best = colormap[cpos]
|
|
|
+ }
|
|
|
+
|
|
|
+ if lastcolor != best {
|
|
|
+ result += best
|
|
|
+ lastcolor = best
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ result += string(char)
|
|
|
+ }
|
|
|
+ return result
|
|
|
+ }
|
|
|
+
|
|
|
+ for i := 0; i < (config.Jumble_Sec*1000)/config.Jumble_Loop_Speed; i++ {
|
|
|
+ for _, pos := range charpos {
|
|
|
+ if work[pos] != ' ' {
|
|
|
+ // Safe way to handle bytes to unicode
|
|
|
+
|
|
|
+ var rb byte = getRandom()
|
|
|
+ var safe []byte = []byte{rb}
|
|
|
+ var rndchar string = CP437_to_Unicode(string(safe))
|
|
|
+ work[pos] = []rune(rndchar)[0]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Door.Write(renderF())
|
|
|
+ time.Sleep(time.Millisecond * time.Duration(config.Jumble_Loop_Speed))
|
|
|
+ }
|
|
|
+
|
|
|
+ for {
|
|
|
+ var revealed bool = true
|
|
|
+ for idx, pos := range charpos {
|
|
|
+ if work[pos] != ' ' {
|
|
|
+ if chartime[idx] > 0 {
|
|
|
+ if chartime[idx] < 500 {
|
|
|
+ if rand.Intn(3) == 0 {
|
|
|
+ var safe []byte = []byte{getRandom()}
|
|
|
+ var rndchar string = CP437_to_Unicode(string(safe))
|
|
|
+ work[pos] = []rune(rndchar)[0]
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if rand.Intn(10) == 0 {
|
|
|
+ var safe []byte = []byte{getRandom()}
|
|
|
+ var rndchar string = CP437_to_Unicode(string(safe))
|
|
|
+ work[pos] = []rune(rndchar)[0]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if chartime[idx] < config.Reveal_Loop_Speed {
|
|
|
+ chartime[idx] = 0
|
|
|
+ } else {
|
|
|
+ chartime[idx] -= config.Reveal_Loop_Speed
|
|
|
+ }
|
|
|
+ revealed = false
|
|
|
+ } else {
|
|
|
+ work[pos] = original[revealpos[pos]]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Door.Write(renderF())
|
|
|
+ time.Sleep(time.Millisecond * time.Duration(config.Reveal_Loop_Speed))
|
|
|
+ if revealed {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+ // CP437
|
|
|
+ var original []byte = []byte(output) // original / master
|
|
|
+ var work []byte // work copy we modify
|
|
|
+ var workpos int = 0 // where are we in the work copy
|
|
|
+ var charpos []int // where characters are we can change
|
|
|
+ var chartime []int // character time / reveal timeout
|
|
|
+ var revealpos map[int]int // workpos to original position
|
|
|
+ var colormap map[int]string // index to color end position (workpos)
|
|
|
+ var coloridx []int // index of positions (map isn't ordered)
|
|
|
+ var lastcolor []int // LastColor tracking for keeping color proper
|
|
|
+ var currentANSI string // ANSI Escape code we've extracted
|
|
|
+ var ANSIchar byte // Last character of the ANSI Escape code
|
|
|
+
|
|
|
+ work = make([]byte, 0)
|
|
|
+ colormap = make(map[int]string)
|
|
|
+ revealpos = make(map[int]int)
|
|
|
+ coloridx = make([]int, 0)
|
|
|
+
|
|
|
+ // default color = "reset"
|
|
|
+ lastcolor = make([]int, 1)
|
|
|
+ lastcolor[0] = 0
|
|
|
+
|
|
|
+ // Not using range, I want to be able to look ahead and modify
|
|
|
+ // x.
|
|
|
+
|
|
|
+ for x := 0; x < len(original); x++ {
|
|
|
+ var char byte = original[x]
|
|
|
+ if char == '\x1b' {
|
|
|
+ // ANSI Code
|
|
|
+ currentANSI = "\x1b["
|
|
|
+
|
|
|
+ if original[x+1] != '[' {
|
|
|
+ log.Println("NoMoreSecrets: Found \\x1b not followed by [!")
|
|
|
+ }
|
|
|
+ x += 2
|
|
|
+ for {
|
|
|
+ currentANSI += string(original[x])
|
|
|
+ if unicode.IsLetter(rune(original[x])) {
|
|
|
+ ANSIchar = original[x]
|
|
|
+ break
|
|
|
+ }
|
|
|
+ x++
|
|
|
+ }
|
|
|
+
|
|
|
+ // Is this a color code?
|
|
|
+ if ANSIchar == 'm' {
|
|
|
+ // Yes, don't store in work. Process code.
|
|
|
+ Door.UpdateLastColor(currentANSI, &lastcolor)
|
|
|
+ colormap[workpos] = Color(lastcolor...)
|
|
|
+ coloridx = append(coloridx, workpos)
|
|
|
+ } else {
|
|
|
+ // Not a color code. Add to work.
|
|
|
+ work = append(work, []byte(currentANSI)...)
|
|
|
+ workpos += len(currentANSI)
|
|
|
+ }
|
|
|
+ currentANSI = ""
|
|
|
+ } else {
|
|
|
+ // Not escape, so what is it?
|
|
|
+ if unicode.IsPrint(rune(char)) {
|
|
|
+
|
|
|
+ if char == ' ' {
|
|
|
+ work = append(work, char)
|
|
|
+ chartime = append(chartime, 0)
|
|
|
+ } else {
|
|
|
+ work = append(work, char)
|
|
|
+ chartime = append(chartime, rand.Intn(config.Max_Time+1))
|
|
|
+ }
|
|
|
+
|
|
|
+ charpos = append(charpos, workpos)
|
|
|
+ revealpos[workpos] = x
|
|
|
+ workpos++
|
|
|
+ } else {
|
|
|
+ // control code, CR NL.
|
|
|
+ work = append(work, char)
|
|
|
+ workpos++
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // jumble loop
|
|
|
+ var renderF func() string = func() string {
|
|
|
+ var result []byte
|
|
|
+ var lastcolor string
|
|
|
+ var pos int = 0
|
|
|
+
|
|
|
+ for idx, char := range work {
|
|
|
+ _, found := revealpos[idx]
|
|
|
+ if found {
|
|
|
+ for charpos[pos] != idx {
|
|
|
+ pos++
|
|
|
+ }
|
|
|
+ // This is a character
|
|
|
+
|
|
|
+ if chartime[pos] != 0 && char != ' ' {
|
|
|
+ if lastcolor != config.Color {
|
|
|
+ result = append(result, []byte(config.Color)...)
|
|
|
+ lastcolor = config.Color
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // look up the color in the colormap
|
|
|
+ var best string
|
|
|
+
|
|
|
+ for _, cpos := range coloridx {
|
|
|
+ if cpos > idx {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ best = colormap[cpos]
|
|
|
+ }
|
|
|
+
|
|
|
+ if lastcolor != best {
|
|
|
+ result = append(result, []byte(best)...)
|
|
|
+ lastcolor = best
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ result = append(result, char)
|
|
|
+ }
|
|
|
+ return string(result)
|
|
|
+ }
|
|
|
+
|
|
|
+ for i := 0; i < (config.Jumble_Sec*1000)/config.Jumble_Loop_Speed; i++ {
|
|
|
+ for _, pos := range charpos {
|
|
|
+ if work[pos] != ' ' {
|
|
|
+ work[pos] = getRandom()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Door.Write(renderF())
|
|
|
+ time.Sleep(time.Millisecond * time.Duration(config.Jumble_Loop_Speed))
|
|
|
+ }
|
|
|
+
|
|
|
+ for {
|
|
|
+ var revealed bool = true
|
|
|
+ for idx, pos := range charpos {
|
|
|
+ if work[pos] != ' ' {
|
|
|
+ if chartime[idx] > 0 {
|
|
|
+ if chartime[idx] < 500 {
|
|
|
+ if rand.Intn(3) == 0 {
|
|
|
+ work[pos] = getRandom()
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if rand.Intn(10) == 0 {
|
|
|
+ work[pos] = getRandom()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if chartime[idx] < config.Reveal_Loop_Speed {
|
|
|
+ chartime[idx] = 0
|
|
|
+ } else {
|
|
|
+ chartime[idx] -= config.Reveal_Loop_Speed
|
|
|
+ }
|
|
|
+ revealed = false
|
|
|
+ } else {
|
|
|
+ work[pos] = original[revealpos[pos]]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Door.Write(renderF())
|
|
|
+ time.Sleep(time.Millisecond * time.Duration(config.Reveal_Loop_Speed))
|
|
|
+ if revealed {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|