space-ace.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. package main
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "os"
  6. "path/filepath"
  7. "red-green/door"
  8. "regexp"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "unicode"
  13. _ "github.com/mattn/go-sqlite3"
  14. "github.com/seehuhn/mt19937"
  15. )
  16. var Config map[string]string
  17. func find_words(text string) [][]int {
  18. word, _ := regexp.Compile("([A-Za-z]+)")
  19. return word.FindAllStringIndex(text, -1)
  20. }
  21. func press_a_key(d *door.Door) int {
  22. green := door.ColorText("BOLD GREEN")
  23. blue := door.ColorText("BOLD BLUE")
  24. yellow := door.ColorText("BOLD YELLOW")
  25. any_caps := green
  26. any_lower := yellow
  27. any_color := func(text string) string {
  28. var output string
  29. var lastColor string
  30. for _, letter := range text {
  31. if unicode.IsUpper(letter) {
  32. if lastColor != any_caps {
  33. lastColor = any_caps
  34. output += any_caps
  35. }
  36. output += string(letter)
  37. } else {
  38. if lastColor != any_lower {
  39. lastColor = any_lower
  40. output += any_lower
  41. }
  42. output += string(letter)
  43. }
  44. }
  45. return output
  46. }
  47. text := "Press a key to continue..."
  48. work_text := []rune(text)
  49. d.Write(door.Reset + any_color(text))
  50. var r int = -1
  51. var t int
  52. sleep_ms := 250
  53. ms_sleep := 0
  54. words := find_words(text)
  55. var word int = 0
  56. var wpos int = 0
  57. current_word := text[words[word][0]:words[word][1]]
  58. t = 0
  59. r = d.WaitKey(0, int64(sleep_ms)*1000)
  60. for r == -1 {
  61. ms_sleep += sleep_ms
  62. if ms_sleep > 1000 {
  63. ms_sleep -= 1000
  64. t++
  65. if t >= int(door.Inactivity) {
  66. d.Write(strings.Repeat("\b", len(text)))
  67. d.Write(any_color(text) + door.CRNL)
  68. return -1
  69. }
  70. }
  71. wpos++
  72. if wpos == len(current_word) {
  73. word++
  74. wpos = 0
  75. work_text = []rune(text)
  76. if word == len(words) {
  77. word = 0
  78. if any_caps == green {
  79. any_caps = blue
  80. } else {
  81. any_caps = green
  82. }
  83. d.Write(strings.Repeat("\b", len(text)))
  84. d.Write(any_color(string(work_text)))
  85. current_word = text[words[word][0]:words[word][1]]
  86. goto READKEY
  87. }
  88. current_word = text[words[word][0]:words[word][1]]
  89. }
  90. {
  91. c := rune(current_word[wpos])
  92. for !unicode.IsLower(c) {
  93. wpos++
  94. if wpos == len(current_word) {
  95. wpos = 0
  96. word++
  97. work_text = []rune(text)
  98. if word == len(words) {
  99. word = 0
  100. }
  101. current_word = text[words[word][0]:words[word][1]]
  102. }
  103. c = rune(current_word[wpos])
  104. }
  105. // Ok, we found something that's lower case
  106. work_text[words[word][0]+wpos] = unicode.ToUpper(c)
  107. }
  108. d.Write(strings.Repeat("\b", len(text)))
  109. d.Write(any_color(string(work_text)))
  110. READKEY:
  111. r = d.WaitKey(0, int64(sleep_ms)*1000)
  112. }
  113. d.Write(strings.Repeat("\b", len(text)))
  114. d.Write(any_color(text))
  115. return r
  116. }
  117. func MainMenu() door.Menu {
  118. // Make the main menu
  119. m := door.Menu{Panel: door.Panel{Width: 25,
  120. X: 5,
  121. Y: 5,
  122. Style: door.DOUBLE,
  123. Title: "[ Main Menu: ]",
  124. TitleOffset: 3,
  125. BorderColor: door.ColorText("BRI CYAN ON BLA")}}
  126. m.SelectedR = door.MakeMenuRender(door.ColorText("BOLD CYAN"),
  127. door.ColorText("BOLD BLUE"),
  128. door.ColorText("BOLD CYAN"),
  129. door.ColorText("BOLD BLUE"))
  130. m.UnselectedR = door.MakeMenuRender(door.ColorText("BOLD YEL ON BLUE"),
  131. door.ColorText("BOLD WHI ON BLUE"),
  132. door.ColorText("BOLD YEL ON BLUE"),
  133. door.ColorText("BOLD CYAN ON BLUE"))
  134. m.AddSelection("P", "Play Cards")
  135. m.AddSelection("S", "View Scores")
  136. m.AddSelection("C", "Configure")
  137. m.AddSelection("H", "Help")
  138. m.AddSelection("A", "About this game")
  139. m.AddSelection("Q", "Quit")
  140. return m
  141. }
  142. func ConfigMenu() door.Menu {
  143. m := door.Menu{Panel: door.Panel{Width: 31,
  144. X: 5,
  145. Y: 5,
  146. Style: door.DOUBLE,
  147. Title: "[ Configuration Menu ]",
  148. TitleColor: door.ColorText("BRI CYAN ON BLUE"),
  149. BorderColor: door.ColorText("CYAN ON BLUE")}}
  150. m.SelectedR = door.MakeMenuRender(door.ColorText("BOLD CYAN"),
  151. door.ColorText("BOLD BLUE"),
  152. door.ColorText("BOLD CYAN"),
  153. door.ColorText("BOLD BLUE"))
  154. m.UnselectedR = door.MakeMenuRender(door.ColorText("BOLD YEL ON BLUE"),
  155. door.ColorText("BOLD WHI ON BLUE"),
  156. door.ColorText("BOLD YEL ON BLUE"),
  157. door.ColorText("BOLD CYAN ON BLUE"))
  158. m.AddSelection("D", "Deck Colors")
  159. m.AddSelection("V", "View Settings")
  160. m.AddSelection("Q", "Quit")
  161. return m
  162. }
  163. func display_information(d *door.Door) {
  164. d.Write(door.Clrscr)
  165. keyColor := door.ColorText("BRI GREEN")
  166. sepColor := door.ColorText("BRI YEL")
  167. valColor := door.ColorText("CYAN")
  168. nice_format := func(key string, value string) string {
  169. return fmt.Sprintf("%s%-20s %s: %s%s", keyColor, key, sepColor, valColor, value) + door.CRNL
  170. }
  171. d.Write(nice_format("BBS Software", d.Config.BBSID))
  172. d.Write(nice_format("Real Name", d.Config.Real_name))
  173. d.Write(nice_format("Handle", d.Config.Handle))
  174. d.Write(nice_format("User #", strconv.Itoa(d.Config.User_number)))
  175. d.Write(nice_format("Security Level", strconv.Itoa(d.Config.Security_level)))
  176. d.Write(nice_format("Node #", strconv.Itoa(d.Config.Node)))
  177. d.Write(nice_format("Unicode", strconv.FormatBool(door.Unicode)))
  178. d.Write(nice_format("CP437", strconv.FormatBool(door.CP437)))
  179. d.Write(nice_format("Screen Size", fmt.Sprintf("%d X %d", door.Width, door.Height)))
  180. }
  181. func panel_demo(d *door.Door) {
  182. width := 55
  183. fmtStr := "%-55s"
  184. p := door.Panel{X: 5, Y: 5, Width: width, Style: door.DOUBLE, BorderColor: door.ColorText("CYAN ON BLUE"), Title: "[ Panel Demo ]"}
  185. lineColor := door.ColorText("BRIGHT WHI ON BLUE")
  186. // Add lines to the panel
  187. for _, line := range []string{"The BBS Door Panel Demo", "(C) 2021 Red Green Software, https://red-green.com"} {
  188. if door.Unicode {
  189. line = strings.Replace(line, "(C)", "\u00a9", -1)
  190. }
  191. l := door.Line{Text: fmt.Sprintf(fmtStr, line), DefaultColor: lineColor}
  192. p.Lines = append(p.Lines, l)
  193. }
  194. p.Lines = append(p.Lines, p.Spacer())
  195. p.Lines = append(p.Lines, door.Line{Text: fmt.Sprintf(fmtStr, "Welcome to golang!"), DefaultColor: lineColor})
  196. d.Write(door.Clrscr)
  197. d.Write(p.Output() + door.CRNL)
  198. }
  199. func MakeColorsRender(brackets string, text_color string, colors map[string]string) func(string) string {
  200. renderF := func(text string) string {
  201. words := find_words(text)
  202. var option bool = true
  203. var color_word bool = false
  204. var tpos int = 0
  205. var result string
  206. var last_color string
  207. var word_color string
  208. var words_id = 0
  209. word_pair := words[words_id]
  210. normal := colors["ALL"]
  211. for _, c := range text {
  212. if option {
  213. if c == '[' || c == ']' {
  214. if last_color != brackets {
  215. result += brackets
  216. last_color = brackets
  217. }
  218. if c == ']' {
  219. option = false
  220. }
  221. } else {
  222. if last_color != text_color {
  223. result += text_color
  224. last_color = text_color
  225. }
  226. }
  227. result += string(c)
  228. } else {
  229. // Out of the options
  230. if color_word {
  231. if tpos < word_pair[1] {
  232. if last_color != word_color {
  233. result += word_color
  234. last_color = word_color
  235. } else {
  236. color_word = false
  237. if last_color != normal {
  238. result += normal
  239. last_color = normal
  240. }
  241. }
  242. result += string(c)
  243. }
  244. } else {
  245. // look for COLOR word
  246. if tpos >= word_pair[1] {
  247. words_id++
  248. if words_id > len(words) {
  249. word_pair = []int{999, 999}
  250. } else {
  251. word_pair = words[words_id]
  252. }
  253. }
  254. if word_pair[0] == tpos {
  255. // start of word
  256. possible := text[word_pair[0]:word_pair[1]]
  257. color_code, has := colors[possible]
  258. if has {
  259. word_color = color_code
  260. } else {
  261. word_color = colors["ALL"]
  262. }
  263. if last_color != word_color {
  264. result += word_color
  265. last_color = word_color
  266. }
  267. }
  268. result += string(c)
  269. }
  270. }
  271. }
  272. return result
  273. }
  274. return renderF
  275. }
  276. func DeckColorMenu() door.Menu {
  277. m := door.Menu{Panel: door.Panel{Width: 31,
  278. X: 5,
  279. Y: 5,
  280. Style: door.DOUBLE,
  281. Title: "[ Deck Menu ]",
  282. TitleColor: door.ColorText("BRI CYAN ON BLUE"),
  283. BorderColor: door.ColorText("CYAN ON BLUE")}}
  284. m.SelectedR = door.MakeMenuRender(door.ColorText("BOLD CYAN"),
  285. door.ColorText("BOLD BLUE"),
  286. door.ColorText("BOLD CYAN"),
  287. door.ColorText("BOLD BLUE"))
  288. m.UnselectedR = door.MakeMenuRender(door.ColorText("BOLD YEL ON BLUE"),
  289. door.ColorText("BOLD WHI ON BLUE"),
  290. door.ColorText("BOLD YEL ON BLUE"),
  291. door.ColorText("BOLD CYAN ON BLUE"))
  292. m.AddSelection("D", "Deck Colors")
  293. m.AddSelection("V", "View Settings")
  294. m.AddSelection("Q", "Quit")
  295. return m
  296. }
  297. func RenderStatusValue(status string, value string) func(string) string {
  298. renderF := func(text string) string {
  299. var result string
  300. pos := strings.Index(text, ":")
  301. result = status + text[:pos] + value + text[pos:]
  302. return result
  303. }
  304. return renderF
  305. }
  306. // Display the SysOp settings in the Config/yaml file
  307. func DisplaySettings(d *door.Door) {
  308. d.Write(door.Clrscr + door.Reset)
  309. W := 35
  310. panel := door.Panel{Width: W,
  311. X: 5,
  312. Y: 5,
  313. Style: door.DOUBLE,
  314. BorderColor: door.ColorText("BOLD CYAN ON BLUE"),
  315. }
  316. l := door.Line{Text: fmt.Sprintf("%*s", W, "Game Settings - SysOp Configurable"),
  317. DefaultColor: door.ColorText("BOLD GREEN ON BLUE")}
  318. panel.Lines = append(panel.Lines, l)
  319. renderF := RenderStatusValue(door.ColorText("BOLD YELLOW ON BLUE"), door.ColorText("BOLD GREEN ON BLUE"))
  320. for key, value := range Config {
  321. if key[0] == '_' {
  322. continue
  323. }
  324. l = door.Line{Text: fmt.Sprintf("%20s : %*s", key, -(W - 23), value), RenderF: renderF}
  325. panel.Lines = append(panel.Lines, l)
  326. }
  327. d.Write(panel.Output() + door.Reset + door.CRNL)
  328. press_a_key(d)
  329. }
  330. func Configure(d *door.Door, db *DBData) {
  331. menu := ConfigMenu()
  332. // deckcolor := "DeckColor"
  333. // save_deckcolor := false
  334. var choice int
  335. for choice >= 0 {
  336. d.Write(door.Clrscr)
  337. choice = menu.Choose(d)
  338. if choice < 0 {
  339. break
  340. }
  341. option := menu.GetOption(choice)
  342. switch option {
  343. case 'D':
  344. // Deck color
  345. case 'V':
  346. // View settings
  347. DisplaySettings(d)
  348. case 'Q':
  349. choice = -1
  350. }
  351. }
  352. }
  353. func main() {
  354. var message string
  355. // Get path to binary, and chdir to it.
  356. binaryPath, _ := os.Executable()
  357. binaryPath = filepath.Dir(binaryPath)
  358. _ = os.Chdir(binaryPath)
  359. fmt.Println("Starting space-ace")
  360. rng := rand.New(mt19937.New())
  361. rng.Seed(time.Now().UnixNano())
  362. d := door.Door{}
  363. d.Init("space-ace")
  364. db := DBData{}
  365. db.Open("space-database.db")
  366. defer db.Close()
  367. // Use Real_name or Handle? Or read config?
  368. db.User = d.Config.Real_name
  369. const config_filename = "space-ace.yaml"
  370. if FileExists(config_filename) {
  371. Config = LoadConfig(config_filename)
  372. } else {
  373. Config = make(map[string]string)
  374. }
  375. var update_config bool = false
  376. // Guessing at this point as to what settings I actually want.
  377. config_defaults := map[string]string{"hands_per_day": "3",
  378. "date_format": "%B %d",
  379. "date_score": "%m/%d/%Y",
  380. "makeup_per_day": "5",
  381. "play_days_ahead": "2",
  382. "date_monthly": "%B %Y"}
  383. // _seed
  384. _, ok := Config["_seed"]
  385. if !ok {
  386. // Initialize the seed
  387. Config["_seed"] = fmt.Sprintf("%d,%d,%d", rng.Int31(), rng.Int31(), rng.Int31())
  388. update_config = true
  389. }
  390. for key, value := range config_defaults {
  391. if SetConfigDefault(&Config, key, value) {
  392. update_config = true
  393. }
  394. }
  395. if update_config {
  396. SaveConfig(config_filename, Config)
  397. }
  398. s := StarField{}
  399. s.Regenerate()
  400. s.Display(&d)
  401. d.Write(door.Goto(1, 1) + door.Reset)
  402. // Get the last call value (if they have called before)
  403. last_call, _ := strconv.ParseInt(db.GetSetting("LastCall", "0"), 10, 64)
  404. now := time.Now()
  405. db.SetSetting("LastCall", fmt.Sprintf("%d", now.Unix()))
  406. // Check for maint run -- get FirstOfMonthDate, and see if
  407. // records are older then it is. (If so, yes -- run maint)!
  408. if last_call != 0 {
  409. d.Write("Welcome Back!" + door.CRNL)
  410. delta := now.Sub(time.Unix(last_call, 0))
  411. hours := delta.Hours()
  412. if hours > 24 {
  413. d.Write(fmt.Sprintf("It's been %0.1f days since you last played."+door.CRNL, hours/24))
  414. } else {
  415. if hours > 1 {
  416. d.Write(fmt.Sprintf("It's been %0.1f hours since you last played."+door.CRNL, hours))
  417. } else {
  418. minutes := delta.Minutes()
  419. d.Write(fmt.Sprintf("It's been %0.1f minutes since you last played."+door.CRNL, minutes))
  420. }
  421. }
  422. }
  423. left := d.TimeLeft()
  424. message = fmt.Sprintf("You have %0.2f minutes / %0.2f seconds remaining..."+door.CRNL, left.Minutes(), left.Seconds())
  425. d.Write(message)
  426. press_a_key(&d)
  427. mainmenu := MainMenu()
  428. var choice int
  429. for choice >= 0 {
  430. d.Write(door.Clrscr)
  431. s.Display(&d)
  432. choice = mainmenu.Choose(&d)
  433. if choice < 0 {
  434. break
  435. }
  436. option := mainmenu.GetOption(choice)
  437. // fmt.Printf("Choice: %d, Option: %c\n", choice, option)
  438. switch option {
  439. case 'P':
  440. // Play cards
  441. case 'S':
  442. // View Scores
  443. case 'C':
  444. // Configure
  445. Configure(&d, &db)
  446. case 'H':
  447. // Help
  448. press_a_key(&d)
  449. case 'A':
  450. // About
  451. press_a_key(&d)
  452. case 'Q':
  453. choice = -1
  454. }
  455. }
  456. d.Write(door.Reset + door.CRNL)
  457. message = fmt.Sprintf("Returning to %s ..."+door.CRNL, d.Config.BBSID)
  458. d.Write(message)
  459. // d.WaitKey(1, 0)
  460. left = d.TimeLeft()
  461. message = fmt.Sprintf("You had %0.2f minutes / %0.2f seconds remaining!"+door.CRNL, left.Minutes(), left.Seconds())
  462. d.Write(message)
  463. left = d.TimeUsed()
  464. d.Write(fmt.Sprintf("You used %0.2f seconds / %0.2f minutes."+door.CRNL, left.Seconds(), left.Minutes()))
  465. fmt.Println("Exiting space-ace")
  466. }