nomoresecrets.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. package door
  2. import (
  3. "log"
  4. "math/rand"
  5. "time"
  6. "unicode"
  7. )
  8. /*
  9. No More Secrets - from "Sneakers"
  10. https://github.com/bartobri/no-more-secrets
  11. */
  12. type NoMoreSecretsConfig struct {
  13. Jumble_Sec int // in sec
  14. Jumble_Loop_Speed int // in ms
  15. Reveal_Loop_Speed int // in ms
  16. Max_Time int // Max value before reveal (per character)
  17. Color string // Color to use before reveal
  18. }
  19. // The default configuration for NoMoreSecrets
  20. var NoMoreSecretsDefault NoMoreSecretsConfig
  21. // Initialize the Defaults
  22. func init() {
  23. NoMoreSecretsDefault = NoMoreSecretsConfig{
  24. Jumble_Sec: 2,
  25. Jumble_Loop_Speed: 35,
  26. Reveal_Loop_Speed: 50,
  27. Max_Time: 5000,
  28. Color: ColorText("CYAN ON BLACK"),
  29. }
  30. }
  31. // Get random character code for jumble
  32. // We use chr 0x21 - 0xdf (excluding 0x7f)
  33. func getRandom() byte {
  34. // 0x7f = backspace / rubout
  35. var rb byte = 0x7f
  36. for rb == 0x7f {
  37. rb = byte(rand.Intn(0xdf-0x21) + 0x21)
  38. }
  39. return rb
  40. }
  41. /*
  42. NoMoreSecrets - render output as random, then fade in the original text.
  43. Example:
  44. func About_Example_NoMoreSecrets(d *door.Door) {
  45. W := 60
  46. center_x := (door.Width - W) / 2
  47. center_y := (door.Height - 16) / 2
  48. about := door.Panel{X: center_x,
  49. Y: center_y,
  50. Width: W,
  51. Style: door.SINGLE_DOUBLE,
  52. BorderColor: door.ColorText("BOLD YELLOW ON BLUE"),
  53. }
  54. about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, "About This Door"),
  55. DefaultColor: door.ColorText("BOLD CYAN ON BLUE")})
  56. about.Lines = append(about.Lines, about.Spacer())
  57. about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, "Test Door written in go, using go door.")})
  58. var copyright string = "(C) 2022 Bugz, Red Green Software"
  59. if door.Unicode {
  60. copyright = strings.Replace(copyright, "(C)", "\u00a9", -1)
  61. }
  62. about.Lines = append(about.Lines,
  63. door.Line{Text: fmt.Sprintf("%*s", -W, copyright),
  64. DefaultColor: door.ColorText("BOLD WHITE ON BLUE")})
  65. for _, text := range []string{"",
  66. "This door was written by Bugz.",
  67. "",
  68. "It is written in Go, understands CP437 and unicode, adapts",
  69. "to screen sizes, uses door32.sys, supports TheDraw Fonts,",
  70. "and runs on Linux and Windows."} {
  71. about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, text)})
  72. }
  73. better := door.NoMoreSecretsDefault
  74. better.Color = door.ColorText("BOLD CYAN ON BLUE")
  75. door.NoMoreSecrets(about.Output(), d, &better)
  76. }
  77. */
  78. func NoMoreSecrets(output string, Door *Door, config *NoMoreSecretsConfig) {
  79. // Find ANSI codes, strip color (We'll handle color)
  80. rand.Seed(time.Now().UnixNano())
  81. if Unicode {
  82. var original []rune = []rune(output) // original / master
  83. var work []rune = make([]rune, 0) // work copy we modify
  84. var workpos int = 0 // where we are in the work copy
  85. var charpos []int // where characters are we can change
  86. var chartime []int // character time / reveal timeout
  87. var revealpos map[int]int // workpos to original position
  88. var colormap map[int]string // index to color end position (workpos)
  89. var coloridx []int // index of positions (map isn't ordered)
  90. var lastcolor []int // LastColor tracking for keeping colors proper
  91. var currentANSI string // ANSI Escape code we've extracted
  92. var ANSIchar rune // Last character of the ANSI Escape code
  93. colormap = make(map[int]string)
  94. revealpos = make(map[int]int)
  95. coloridx = make([]int, 0)
  96. // default color = "reset"
  97. lastcolor = make([]int, 1)
  98. lastcolor[0] = 0
  99. // Not using range, I want to be able to look ahead and modify
  100. // x.
  101. for x := 0; x < len(original); x++ {
  102. var char rune = original[x]
  103. if char == '\x1b' {
  104. // ANSI Code
  105. currentANSI = "\x1b["
  106. if original[x+1] != '[' {
  107. log.Println("NoMoreSecrets: Found \\x1b not followed by [!")
  108. }
  109. x += 2
  110. for {
  111. currentANSI += string(original[x])
  112. if unicode.IsLetter(original[x]) {
  113. ANSIchar = original[x]
  114. break
  115. }
  116. x++
  117. }
  118. // log.Printf("curentANSI: END @ %d [%#v]\n", x, currentANSI[1:])
  119. // Is this a color code?
  120. if ANSIchar == 'm' {
  121. // Yes, don't store in work. Process code.
  122. Door.UpdateLastColor(currentANSI, &lastcolor)
  123. colormap[workpos] = Color(lastcolor...)
  124. coloridx = append(coloridx, workpos)
  125. // log.Printf("Added %d with %s\n", workpos, colormap[workpos][1:])
  126. } else {
  127. // Not a color code. Add to work.
  128. var ANSIrunes []rune = []rune(currentANSI)
  129. work = append(work, ANSIrunes...)
  130. workpos += len(ANSIrunes)
  131. }
  132. // currentANSI = ""
  133. } else {
  134. // Not escape, so what is it?
  135. if unicode.IsPrint(char) {
  136. if char == ' ' {
  137. work = append(work, char)
  138. chartime = append(chartime, 0)
  139. } else {
  140. work = append(work, char)
  141. chartime = append(chartime, rand.Intn(config.Max_Time+1))
  142. }
  143. charpos = append(charpos, workpos)
  144. revealpos[workpos] = x
  145. workpos++
  146. } else {
  147. // control code, CR NL.
  148. work = append(work, char)
  149. workpos++
  150. }
  151. }
  152. }
  153. // jumble loop
  154. var renderF func() string = func() string {
  155. var result string
  156. var lastcolor string
  157. var pos int = 0
  158. for idx, char := range work {
  159. var found bool
  160. _, found = revealpos[idx]
  161. if found {
  162. for charpos[pos] != idx {
  163. pos++
  164. }
  165. // This is a character
  166. if chartime[pos] != 0 && char != ' ' {
  167. if lastcolor != config.Color {
  168. result += config.Color
  169. lastcolor = config.Color
  170. }
  171. } else {
  172. // look up the color in the colormap
  173. var best string
  174. // use the coloridx, lookup in colormap
  175. for _, cpos := range coloridx {
  176. if cpos > idx {
  177. break
  178. }
  179. best = colormap[cpos]
  180. }
  181. if lastcolor != best {
  182. result += best
  183. lastcolor = best
  184. }
  185. }
  186. }
  187. result += string(char)
  188. }
  189. return result
  190. }
  191. for i := 0; i < (config.Jumble_Sec*1000)/config.Jumble_Loop_Speed; i++ {
  192. for _, pos := range charpos {
  193. if work[pos] != ' ' {
  194. // Safe way to handle bytes to unicode
  195. var rb byte = getRandom()
  196. var safe []byte = []byte{rb}
  197. var rndchar string = CP437_to_Unicode(string(safe))
  198. work[pos] = []rune(rndchar)[0]
  199. }
  200. }
  201. Door.Write(renderF())
  202. if Door.Disconnect() {
  203. return
  204. }
  205. time.Sleep(time.Millisecond * time.Duration(config.Jumble_Loop_Speed))
  206. }
  207. for {
  208. var revealed bool = true
  209. for idx, pos := range charpos {
  210. if work[pos] != ' ' {
  211. if chartime[idx] > 0 {
  212. if chartime[idx] < 500 {
  213. if rand.Intn(3) == 0 {
  214. var safe []byte = []byte{getRandom()}
  215. var rndchar string = CP437_to_Unicode(string(safe))
  216. work[pos] = []rune(rndchar)[0]
  217. }
  218. } else {
  219. if rand.Intn(10) == 0 {
  220. var safe []byte = []byte{getRandom()}
  221. var rndchar string = CP437_to_Unicode(string(safe))
  222. work[pos] = []rune(rndchar)[0]
  223. }
  224. }
  225. if chartime[idx] < config.Reveal_Loop_Speed {
  226. chartime[idx] = 0
  227. } else {
  228. chartime[idx] -= config.Reveal_Loop_Speed
  229. }
  230. revealed = false
  231. } else {
  232. work[pos] = original[revealpos[pos]]
  233. }
  234. }
  235. }
  236. Door.Write(renderF())
  237. if Door.Disconnect() {
  238. return
  239. }
  240. time.Sleep(time.Millisecond * time.Duration(config.Reveal_Loop_Speed))
  241. if revealed {
  242. break
  243. }
  244. }
  245. } else {
  246. // CP437
  247. var original []byte = []byte(output) // original / master
  248. var work []byte // work copy we modify
  249. var workpos int = 0 // where are we in the work copy
  250. var charpos []int // where characters are we can change
  251. var chartime []int // character time / reveal timeout
  252. var revealpos map[int]int // workpos to original position
  253. var colormap map[int]string // index to color end position (workpos)
  254. var coloridx []int // index of positions (map isn't ordered)
  255. var lastcolor []int // LastColor tracking for keeping color proper
  256. var currentANSI string // ANSI Escape code we've extracted
  257. var ANSIchar byte // Last character of the ANSI Escape code
  258. work = make([]byte, 0)
  259. colormap = make(map[int]string)
  260. revealpos = make(map[int]int)
  261. coloridx = make([]int, 0)
  262. // default color = "reset"
  263. lastcolor = make([]int, 1)
  264. lastcolor[0] = 0
  265. // Not using range, I want to be able to look ahead and modify
  266. // x.
  267. for x := 0; x < len(original); x++ {
  268. var char byte = original[x]
  269. if char == '\x1b' {
  270. // ANSI Code
  271. currentANSI = "\x1b["
  272. if original[x+1] != '[' {
  273. log.Println("NoMoreSecrets: Found \\x1b not followed by [!")
  274. }
  275. x += 2
  276. for {
  277. currentANSI += string(original[x])
  278. if unicode.IsLetter(rune(original[x])) {
  279. ANSIchar = original[x]
  280. break
  281. }
  282. x++
  283. }
  284. // Is this a color code?
  285. if ANSIchar == 'm' {
  286. // Yes, don't store in work. Process code.
  287. Door.UpdateLastColor(currentANSI, &lastcolor)
  288. colormap[workpos] = Color(lastcolor...)
  289. coloridx = append(coloridx, workpos)
  290. } else {
  291. // Not a color code. Add to work.
  292. work = append(work, []byte(currentANSI)...)
  293. workpos += len(currentANSI)
  294. }
  295. // currentANSI = ""
  296. } else {
  297. // Not escape, so what is it?
  298. if unicode.IsPrint(rune(char)) {
  299. if char == ' ' {
  300. work = append(work, char)
  301. chartime = append(chartime, 0)
  302. } else {
  303. work = append(work, char)
  304. chartime = append(chartime, rand.Intn(config.Max_Time+1))
  305. }
  306. charpos = append(charpos, workpos)
  307. revealpos[workpos] = x
  308. workpos++
  309. } else {
  310. // control code, CR NL.
  311. work = append(work, char)
  312. workpos++
  313. }
  314. }
  315. }
  316. // jumble loop
  317. var renderF func() string = func() string {
  318. var result []byte
  319. var lastcolor string
  320. var pos int = 0
  321. for idx, char := range work {
  322. _, found := revealpos[idx]
  323. if found {
  324. for charpos[pos] != idx {
  325. pos++
  326. }
  327. // This is a character
  328. if chartime[pos] != 0 && char != ' ' {
  329. if lastcolor != config.Color {
  330. result = append(result, []byte(config.Color)...)
  331. lastcolor = config.Color
  332. }
  333. } else {
  334. // look up the color in the colormap
  335. var best string
  336. for _, cpos := range coloridx {
  337. if cpos > idx {
  338. break
  339. }
  340. best = colormap[cpos]
  341. }
  342. if lastcolor != best {
  343. result = append(result, []byte(best)...)
  344. lastcolor = best
  345. }
  346. }
  347. }
  348. result = append(result, char)
  349. }
  350. return string(result)
  351. }
  352. for i := 0; i < (config.Jumble_Sec*1000)/config.Jumble_Loop_Speed; i++ {
  353. for _, pos := range charpos {
  354. if work[pos] != ' ' {
  355. work[pos] = getRandom()
  356. }
  357. }
  358. Door.Write(renderF())
  359. if Door.Disconnect() {
  360. return
  361. }
  362. time.Sleep(time.Millisecond * time.Duration(config.Jumble_Loop_Speed))
  363. }
  364. for {
  365. var revealed bool = true
  366. for idx, pos := range charpos {
  367. if work[pos] != ' ' {
  368. if chartime[idx] > 0 {
  369. if chartime[idx] < 500 {
  370. if rand.Intn(3) == 0 {
  371. work[pos] = getRandom()
  372. }
  373. } else {
  374. if rand.Intn(10) == 0 {
  375. work[pos] = getRandom()
  376. }
  377. }
  378. if chartime[idx] < config.Reveal_Loop_Speed {
  379. chartime[idx] = 0
  380. } else {
  381. chartime[idx] -= config.Reveal_Loop_Speed
  382. }
  383. revealed = false
  384. } else {
  385. work[pos] = original[revealpos[pos]]
  386. }
  387. }
  388. }
  389. Door.Write(renderF())
  390. if Door.Disconnect() {
  391. return
  392. }
  393. time.Sleep(time.Millisecond * time.Duration(config.Reveal_Loop_Speed))
  394. if revealed {
  395. break
  396. }
  397. }
  398. }
  399. }