nomoresecrets.go 13 KB

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