nomoresecrets.go 13 KB

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