package door import ( "bytes" "log" "math/rand" "time" "unicode" ) /* No More Secrets - from "Sneakers" https://github.com/bartobri/no-more-secrets */ const NORANDOM bool = false const DEBUG_NMS bool = true 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 []byte // 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 } var rb byte func nonRandom() byte { if rb == 0 { rb = 0x21 return rb } rb++ if rb == 0x7f { rb++ } if rb >= 0xdf { rb = 0x21 } return rb } // NoMoreSecrets Sleep // // This allows the user to abort the effect with a key or left mouse click. func NMSSleep(Door *Door, sleep time.Duration) bool { _, ex, err := Door.WaitKey(sleep) if err == nil { if ex == MOUSE { m, ok := Door.GetMouse() if ok { if m.Button == 1 { log.Println("NoMore") return true } } return false } log.Println("NoMore") return true } return false } /* 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 []byte, Door *Door, config *NoMoreSecretsConfig) { // Find ANSI codes, strip color (We'll handle color) var keyAbort bool rand.Seed(time.Now().UnixNano()) if Unicode { var original []rune = []rune(string(output)) // original / master var work []rune = make([]rune, 0, len(original)) // work copy we modify var workpos int = 0 // where we are in the work copy var charpos []int = make([]int, 0, len(original)) // where characters are we can change var chartime []int = make([]int, 0, len(original)) // character time / reveal timeout var revealpos map[int]int // workpos to original position var colormap map[int][]byte // 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][]byte) 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 renderResults *bytes.Buffer = &bytes.Buffer{} var renderF func() []byte = func() []byte { renderResults.Reset() var lastcolor *[]byte var pos int = 0 for idx, char := range work { var found bool _, found = revealpos[idx] if found { for charpos[pos] != idx { pos++ } // This is a character if chartime[pos] != 0 && char != ' ' { if lastcolor != &config.Color { renderResults.Write(config.Color) lastcolor = &config.Color } } else { // look up the color in the colormap var best []byte // use the coloridx, lookup in colormap for _, cpos := range coloridx { if cpos > idx { break } best = colormap[cpos] } if lastcolor != &best { renderResults.Write(best) lastcolor = &best } } } renderResults.WriteRune(char) } return renderResults.Bytes() } for i := 0; (i < (config.Jumble_Sec*1000)/config.Jumble_Loop_Speed) && (!keyAbort); i++ { for _, pos := range charpos { if work[pos] != ' ' { // Safe way to handle bytes to unicode var rb byte if NORANDOM { rb = nonRandom() } else { rb = getRandom() } var safe []byte = []byte{rb} var rndchar string = CP437_to_Unicode(string(safe)) work[pos] = []rune(rndchar)[0] } } Door.Write(renderF()) if Door.Disconnect() { return } // time.Sleep() keyAbort = NMSSleep(Door, time.Millisecond*time.Duration(config.Jumble_Loop_Speed)) } for { var revealed bool = true for idx, pos := range charpos { if work[pos] != ' ' { if !keyAbort && (chartime[idx] > 0) { if chartime[idx] < 500 { if rand.Intn(3) == 0 { var safe [1]byte if NORANDOM { safe[0] = nonRandom() } else { safe[0] = getRandom() } var rndchar string = CP437_to_Unicode(string(safe[:])) work[pos] = []rune(rndchar)[0] } } else { if rand.Intn(10) == 0 { var safe [1]byte if NORANDOM { safe[0] = nonRandom() } else { safe[0] = 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 { chartime[idx] = 0 work[pos] = original[revealpos[pos]] } } } Door.Write(renderF()) if Door.Disconnect() { return } // time.Sleep() keyAbort = NMSSleep(Door, 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][]byte // 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][]byte) 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++ } } } var renderResults *bytes.Buffer = &bytes.Buffer{} // jumble loop var renderF func() []byte = func() []byte { renderResults.Reset() var lastcolor *[]byte 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 { renderResults.Write(config.Color) lastcolor = &config.Color } } else { // look up the color in the colormap var best []byte for _, cpos := range coloridx { if cpos > idx { break } best = colormap[cpos] } if lastcolor != &best { renderResults.Write(best) lastcolor = &best } } } renderResults.WriteByte(char) } return renderResults.Bytes() } for i := 0; (i < (config.Jumble_Sec*1000)/config.Jumble_Loop_Speed) && (!keyAbort); i++ { for _, pos := range charpos { if work[pos] != ' ' { if NORANDOM { work[pos] = nonRandom() } else { work[pos] = getRandom() } } } Door.Write(renderF()) if Door.Disconnect() { return } // time.Sleep() keyAbort = NMSSleep(Door, time.Millisecond*time.Duration(config.Jumble_Loop_Speed)) } for { var revealed bool = true for idx, pos := range charpos { if work[pos] != ' ' { if !keyAbort && (chartime[idx] > 0) { if chartime[idx] < 500 { if rand.Intn(3) == 0 { if NORANDOM { work[pos] = nonRandom() } else { work[pos] = getRandom() } } } else { if rand.Intn(10) == 0 { if NORANDOM { work[pos] = nonRandom() } else { work[pos] = getRandom() } } } if chartime[idx] < config.Reveal_Loop_Speed { chartime[idx] = 0 } else { chartime[idx] -= config.Reveal_Loop_Speed } revealed = false } else { chartime[idx] = 0 work[pos] = original[revealpos[pos]] } } } Door.Write(renderF()) if Door.Disconnect() { return } // time.Sleep() keyAbort = NMSSleep(Door, time.Millisecond*time.Duration(config.Reveal_Loop_Speed)) if revealed { break } } } }