Browse Source

Added NoMoreSecrets to door package.

This adds the screen effect from "Sneakers".
https://github.com/bartobri/no-more-secrets
Steve Thielemann 3 years ago
parent
commit
625be8edf8
3 changed files with 515 additions and 36 deletions
  1. 30 35
      door/door.go
  2. 440 0
      door/nomoresecrets.go
  3. 45 1
      testdoor/testdoor.go

+ 30 - 35
door/door.go

@@ -1,20 +1,21 @@
 /*
-Package door: a golang implementation of a BBS door for linux that
-support door32.sys.
+Package door: a go implementation of a BBS door for linux and Windows
+that uses door32.sys, understand CP437 and unicode (if available),
+detects screen size, and supports TheDraw Fonts.
 
     import (
 			"door"
 		)
 
-		int main() {
-			d = door.Door{}
-			d.Init() // Process commandline switches, initialize door, detect screen size.
+	int main() {
+		d = door.Door{}
+		d.Init() // Process commandline switches, initialize door, detect screen size.
 
-			d.Write("Welcome to my awesome door, written in "+door.ColorText("BLINK BOLD WHITE")+"golang"+door.Reset+"."+door.CRNL)
-			d.Write("Press a key...")
-			d.Key()
-			d.Write(door.CRNL)
-		}
+		d.Write("Welcome to my awesome door, written in "+door.ColorText("BLINK BOLD WHITE")+"go"+door.Reset+"."+door.CRNL)
+		d.Write("Press a key...")
+		d.Key()
+		d.Write(door.CRNL)
+	}
 */
 package door
 
@@ -61,43 +62,42 @@ g00r00                       Line 7 : User's handle/alias
 1                            Line 11: Current node number
 */
 
+// Door32 information
 type DropfileConfig struct {
-	Comm_type      int
-	Comm_handle    int
-	Baudrate       int
-	BBSID          string
-	User_number    int
-	Real_name      string
-	Handle         string
-	Security_level int
-	Time_left      int
-	Emulation      int
-	Node           int
+	Comm_type      int    // (not used)
+	Comm_handle    int    // Handle to use to talk to the user
+	Baudrate       int    // (not used)
+	BBSID          string // BBS Software name
+	User_number    int    // User number
+	Real_name      string // User's Real Name
+	Handle         string // User's Handle/Nick
+	Security_level int    // Security Level (if given)
+	Time_left      int    // Time Left (minutes)
+	Emulation      int    // (not used)
+	Node           int    // BBS Node number
 }
 
 type Door struct {
 	Config        DropfileConfig
 	READFD        int
 	WRITEFD       int
-	Disconnected  bool
+	Disconnected  bool      // Has User disconnected/Hung up?
 	TimeOut       time.Time // Fixed point in time, when time expires
-	StartTime     time.Time
+	StartTime     time.Time // Time when User started door
 	Pushback      FIFOBuffer
 	LastColor     []int
-	readerChannel chan byte
-	writerChannel chan string
+	readerChannel chan byte   // Reading from the User
+	writerChannel chan string // Writing to the User
 	writerMutex   sync.Mutex
 }
 
 // Return the amount of time left as time.Duration
 func (d *Door) TimeLeft() time.Duration {
 	return time.Until(d.TimeOut)
-	// return d.TimeOut.Sub(time.Now())
 }
 
 func (d *Door) TimeUsed() time.Duration {
 	return time.Since(d.StartTime)
-	// return time.Now().Sub(d.StartTime)
 }
 
 // Read the BBS door file.  We only support door32.sys.
@@ -105,14 +105,12 @@ func (d *Door) ReadDropfile(filename string) {
 	file, err := os.Open(filename)
 	if err != nil {
 		log.Panicf("Open(%s): %s\n", filename, err)
-		// os.Exit(2)
 	}
 
 	defer file.Close()
 
 	var lines []string
 	// read line by line
-
 	// The scanner handles DOS and linux file endings.
 
 	scanner := bufio.NewScanner(file)
@@ -160,22 +158,22 @@ func (d *Door) ReadDropfile(filename string) {
 	d.READFD = d.Config.Comm_handle
 	d.WRITEFD = d.Config.Comm_handle
 
-	// Calculate the time when time expires.
 	d.StartTime = time.Now()
+	// Calculate when time expires.
 	d.TimeOut = time.Now().Add(time.Duration(d.Config.Time_left) * time.Minute)
 }
 
 // Detect client terminal capabilities, Unicode, CP437, Full_CP437,
 // screen Height and Width.
+// Full_CP437 means the terminal understands control codes as CP437.
 func (d *Door) detect() {
 	d.Write("\x1b[0;30;40m\x1b[2J\x1b[H")                          // black on black, clrscr, go home
 	d.Write("\x03\x04\x1b[6n")                                     // hearts and diamonds does CP437 work?
 	d.Write(CRNL + "\u2615\x1b[6n")                                // hot beverage
 	d.Write("\x1b[999C\x1b[999B\x1b[6n" + Reset + "\x1b[2J\x1b[H") // goto end of screen + cursor pos
-	// time.Sleep(50 * time.Millisecond)
 	time.Sleep(250 * time.Millisecond)
+
 	// read everything
-	// telnet term isn't in RAW mode, so keys are buffer until <CR>
 	var results string
 
 	for {
@@ -190,7 +188,6 @@ func (d *Door) detect() {
 		output := strings.Replace(results, "\x1b", "^[", -1)
 		log.Println("DETECT:", output)
 	} else {
-		// local telnet echos the reply  :()
 		log.Println("DETECT: Nothing received.")
 		return
 	}
@@ -209,7 +206,6 @@ func (d *Door) detect() {
 	}
 
 	// get screen size
-
 	pos := strings.LastIndex(results, "\x1b")
 	if pos != -1 {
 		pos++
@@ -228,7 +224,6 @@ func (d *Door) detect() {
 					width := results[:pos]
 
 					Width, _ = strconv.Atoi(width)
-					// log.Printf("Width: %s, %d, %v\n", results, Width, err)
 				}
 			} else {
 				Height = 0

+ 440 - 0
door/nomoresecrets.go

@@ -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
+			}
+		}
+	}
+}

+ 45 - 1
testdoor/testdoor.go

@@ -34,6 +34,45 @@ func press_a_key(d *door.Door) int {
 	return k
 }
 
+func about_test_door(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("BRI CYAN ON BLUE")
+
+	door.NoMoreSecrets(about.Output(), d, &better)
+}
+
 func MainMenu() door.Menu {
 	// Make the main menu
 	m := door.Menu{Panel: door.Panel{Width: 45,
@@ -60,6 +99,7 @@ func MainMenu() door.Menu {
 	m.AddSelection("M", "Menu Demo")
 	m.AddSelection("P", "Progress Bars Demo")
 	m.AddSelection("S", "Show Panel")
+	m.AddSelection("T", "Test Door About")
 	m.AddSelection("W", "Screen Width")
 
 	m.AddSelection("Q", "Quit")
@@ -293,7 +333,8 @@ func input_demo(d *door.Door) {
 
 	go func() {
 		for t := range ticker.C {
-			output := door.SavePos + door.Goto(5, 1) + door.ColorText("CYAN ON BLACK") + t.Format(time.RFC3339) + door.RestorePos
+			const tf = "January 2, 2006 03:04:05 PM MST"
+			output := door.SavePos + door.Goto(5, 2) + door.ColorText("BRI WHI ON GREEN") + t.Format(tf) + door.RestorePos
 			d.Write(output)
 		}
 	}()
@@ -542,6 +583,9 @@ func main() {
 		case 'S':
 			panel_demo(&d)
 			press_a_key(&d)
+		case 'T':
+			about_test_door(&d)
+			press_a_key(&d)
 		case 'W':
 			width_demo(&d)
 		case 'Q':