nomoresecrets.go 13 KB

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