space-ace.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "math/rand"
  6. "os"
  7. "path/filepath"
  8. "red-green/door"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "unicode"
  13. "github.com/seehuhn/mt19937"
  14. )
  15. var Config map[string]string
  16. func press_a_key(d *door.Door) rune {
  17. green := door.ColorText("BOLD GREEN")
  18. blue := door.ColorText("BOLD BLUE")
  19. yellow := door.ColorText("BOLD YELLOW")
  20. any_caps := green
  21. any_lower := yellow
  22. any_color := func(text string) string {
  23. var output string
  24. var lastColor string
  25. for _, letter := range text {
  26. if unicode.IsUpper(letter) {
  27. if lastColor != any_caps {
  28. lastColor = any_caps
  29. output += any_caps
  30. }
  31. output += string(letter)
  32. } else {
  33. if lastColor != any_lower {
  34. lastColor = any_lower
  35. output += any_lower
  36. }
  37. output += string(letter)
  38. }
  39. }
  40. return output
  41. }
  42. text := "Press a key to continue..."
  43. work_text := []rune(text)
  44. d.Write(door.Reset + any_color(text))
  45. var r rune
  46. var ex door.Extended
  47. var err error
  48. var t int
  49. sleep_ms := 250
  50. ms_sleep := 0
  51. words := find_words(text)
  52. var word int = 0
  53. var wpos int = 0
  54. current_word := text[words[word][0]:words[word][1]]
  55. t = 0
  56. r, ex, err = d.WaitKey(time.Duration(sleep_ms) * time.Millisecond) // 0, int64(sleep_ms)*1000)
  57. for err == door.ErrTimeout && ex == door.NOP { // r == -1 {
  58. ms_sleep += sleep_ms
  59. if ms_sleep > 1000 {
  60. ms_sleep -= 1000
  61. t++
  62. if t >= int(door.Inactivity) {
  63. d.Write(strings.Repeat("\b", len(text)))
  64. d.Write(any_color(text) + door.CRNL)
  65. return -1
  66. }
  67. }
  68. wpos++
  69. if wpos == len(current_word) {
  70. word++
  71. wpos = 0
  72. work_text = []rune(text)
  73. if word == len(words) {
  74. word = 0
  75. if any_caps == green {
  76. any_caps = blue
  77. } else {
  78. any_caps = green
  79. }
  80. d.Write(strings.Repeat("\b", len(text)))
  81. d.Write(any_color(string(work_text)))
  82. current_word = text[words[word][0]:words[word][1]]
  83. goto READKEY
  84. }
  85. current_word = text[words[word][0]:words[word][1]]
  86. }
  87. {
  88. c := rune(current_word[wpos])
  89. for !unicode.IsLower(c) {
  90. wpos++
  91. if wpos == len(current_word) {
  92. wpos = 0
  93. word++
  94. work_text = []rune(text)
  95. if word == len(words) {
  96. word = 0
  97. }
  98. current_word = text[words[word][0]:words[word][1]]
  99. }
  100. c = rune(current_word[wpos])
  101. }
  102. // Ok, we found something that's lower case
  103. work_text[words[word][0]+wpos] = unicode.ToUpper(c)
  104. }
  105. d.Write(strings.Repeat("\b", len(text)))
  106. d.Write(any_color(string(work_text)))
  107. READKEY:
  108. r, ex, err = d.WaitKey(time.Duration(sleep_ms) * time.Millisecond) // 0, int64(sleep_ms)*1000)
  109. }
  110. d.Write(strings.Repeat("\b", len(text)))
  111. d.Write(any_color(text))
  112. return r
  113. }
  114. func display_information(d *door.Door) {
  115. d.Write(door.Clrscr)
  116. keyColor := door.ColorText("BRI GREEN")
  117. sepColor := door.ColorText("BRI YEL")
  118. valColor := door.ColorText("CYAN")
  119. nice_format := func(key string, value string) string {
  120. return fmt.Sprintf("%s%-20s %s: %s%s", keyColor, key, sepColor, valColor, value) + door.CRNL
  121. }
  122. d.Write(nice_format("BBS Software", d.Config.BBSID))
  123. d.Write(nice_format("Real Name", d.Config.Real_name))
  124. d.Write(nice_format("Handle", d.Config.Handle))
  125. d.Write(nice_format("User #", strconv.Itoa(d.Config.User_number)))
  126. d.Write(nice_format("Security Level", strconv.Itoa(d.Config.Security_level)))
  127. d.Write(nice_format("Node #", strconv.Itoa(d.Config.Node)))
  128. d.Write(nice_format("Unicode", strconv.FormatBool(door.Unicode)))
  129. d.Write(nice_format("CP437", strconv.FormatBool(door.CP437)))
  130. d.Write(nice_format("Screen Size", fmt.Sprintf("%d X %d", door.Width, door.Height)))
  131. }
  132. func panel_demo(d *door.Door) {
  133. width := 55
  134. fmtStr := "%-55s"
  135. p := door.Panel{X: 5, Y: 5, Width: width, Style: door.DOUBLE, BorderColor: door.ColorText("CYAN ON BLUE"), Title: "[ Panel Demo ]"}
  136. lineColor := door.ColorText("BRIGHT WHI ON BLUE")
  137. // Add lines to the panel
  138. for _, line := range []string{"The BBS Door Panel Demo", "(C) 2021 Red Green Software, https://red-green.com"} {
  139. if door.Unicode {
  140. line = strings.Replace(line, "(C)", "\u00a9", -1)
  141. }
  142. l := door.Line{Text: fmt.Sprintf(fmtStr, line), DefaultColor: lineColor}
  143. p.Lines = append(p.Lines, l)
  144. }
  145. p.Lines = append(p.Lines, p.Spacer())
  146. p.Lines = append(p.Lines, door.Line{Text: fmt.Sprintf(fmtStr, "Welcome to golang!"), DefaultColor: lineColor})
  147. d.Write(door.Clrscr)
  148. d.Write(p.Output() + door.CRNL)
  149. }
  150. /*
  151. door::renderFunction statusValue(door::ANSIColor status,
  152. door::ANSIColor value) {
  153. door::renderFunction rf = [status,
  154. value](const std::string &txt) -> door::Render {
  155. door::Render r(txt);
  156. size_t pos = txt.find(':');
  157. if (pos == std::string::npos) {
  158. for (char const &c : txt) {
  159. if (std::isdigit(c))
  160. r.append(value);
  161. else
  162. r.append(status);
  163. }
  164. } else {
  165. pos++;
  166. r.append(status, pos);
  167. r.append(value, txt.length() - pos);
  168. }
  169. return r;
  170. };
  171. return rf;
  172. }
  173. */
  174. func RenderStatusValue(status string, value string) func(string) string {
  175. renderF := func(text string) string {
  176. pos := strings.Index(text, ":")
  177. if pos != -1 {
  178. return status + text[:pos] + value + text[pos:]
  179. } else {
  180. var r door.Render = door.Render{Line: text}
  181. for _, letter := range text {
  182. if unicode.IsDigit(letter) {
  183. r.Append(value, 1)
  184. } else {
  185. r.Append(status, 1)
  186. }
  187. }
  188. return r.Result
  189. }
  190. }
  191. return renderF
  192. }
  193. // Display the SysOp settings in the Config/yaml file
  194. func DisplaySettings(d *door.Door) {
  195. d.Write(door.Clrscr + door.Reset)
  196. W := 35
  197. panel := door.Panel{Width: W,
  198. X: 5,
  199. Y: 5,
  200. Style: door.DOUBLE,
  201. BorderColor: door.ColorText("BOLD CYAN ON BLUE"),
  202. }
  203. l := door.Line{Text: fmt.Sprintf("%*s", W, "Game Settings - SysOp Configurable"),
  204. DefaultColor: door.ColorText("BOLD GREEN ON BLUE")}
  205. panel.Lines = append(panel.Lines, l)
  206. renderF := RenderStatusValue(door.ColorText("BOLD YELLOW ON BLUE"), door.ColorText("BOLD GREEN ON BLUE"))
  207. for key, value := range Config {
  208. if key[0] == '_' {
  209. continue
  210. }
  211. key = strings.Replace(key, "_", " ", -1)
  212. l = door.Line{Text: fmt.Sprintf("%20s : %*s", key, -(W - 23), value), RenderF: renderF}
  213. panel.Lines = append(panel.Lines, l)
  214. }
  215. d.Write(panel.Output() + door.Reset + door.CRNL)
  216. press_a_key(d)
  217. }
  218. func Configure(d *door.Door, db *DBData) {
  219. menu := ConfigMenu()
  220. // deckcolor := "DeckColor"
  221. // save_deckcolor := false
  222. var choice int
  223. for choice >= 0 {
  224. d.Write(door.Clrscr)
  225. choice = menu.Choose(d)
  226. if choice < 0 {
  227. break
  228. }
  229. option := menu.GetOption(choice)
  230. switch option {
  231. case 'D':
  232. // Deck color
  233. current_color := db.GetSetting("DeckColor", "All")
  234. colormenu := DeckColorMenu(current_color)
  235. d.Write(door.Clrscr)
  236. c := colormenu.Choose(d)
  237. if c > 0 {
  238. db.SetSetting("DeckColor", DeckColors[c-1])
  239. }
  240. d.Write(door.CRNL)
  241. press_a_key(d)
  242. case 'V':
  243. // View settings
  244. DisplaySettings(d)
  245. case 'Q':
  246. choice = -1
  247. }
  248. }
  249. }
  250. func Help() door.Panel {
  251. W := 60
  252. center_x := (door.Width - W) / 2
  253. center_y := (door.Height - 16) / 2
  254. help := door.Panel{X: center_x,
  255. Y: center_y,
  256. Width: W,
  257. Style: door.DOUBLE,
  258. BorderColor: door.ColorText("BOLD YELLOW ON BLUE")}
  259. help.Lines = append(help.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, "Help"),
  260. DefaultColor: door.ColorText("BOLD CYAN ON BLUE")})
  261. help.Lines = append(help.Lines, help.Spacer())
  262. copyright := SPACEACE + " v" + SPACEACE_VERSION
  263. help.Lines = append(help.Lines,
  264. door.Line{Text: fmt.Sprintf("%*s", -W, copyright),
  265. DefaultColor: door.ColorText("BOLD WHITE ON BLUE")})
  266. copyright = SPACEACE_COPYRIGHT
  267. if door.Unicode {
  268. copyright = strings.Replace(copyright, "(C)", "\u00a9", -1)
  269. }
  270. help.Lines = append(help.Lines,
  271. door.Line{Text: fmt.Sprintf("%*s", -W, copyright),
  272. DefaultColor: door.ColorText("BOLD WHITE ON BLUE")})
  273. for _, text := range []string{"",
  274. "Use Left/Right arrow keys, or 4/6 keys to move marker.",
  275. "The marker wraps around the sides of the screen.", "",
  276. "Select card to play with Space or 5.",
  277. "A card can play if it is higher or lower in rank by 1.",
  278. "",
  279. "Enter draws another card.",
  280. "",
  281. "Example: A Jack could play either a Ten or a Queen.",
  282. "Example: A King could play either an Ace or a Queen.",
  283. "",
  284. "The more cards in your streak, the more points earn.",
  285. } {
  286. help.Lines = append(help.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, text)})
  287. }
  288. return help
  289. }
  290. func About() door.Panel {
  291. W := 60
  292. center_x := (door.Width - W) / 2
  293. center_y := (door.Height - 16) / 2
  294. about := door.Panel{X: center_x,
  295. Y: center_y,
  296. Width: W,
  297. Style: door.SINGLE_DOUBLE,
  298. BorderColor: door.ColorText("BOLD YELLOW ON BLUE"),
  299. }
  300. about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, "About This Door"),
  301. DefaultColor: door.ColorText("BOLD CYAN ON BLUE")})
  302. about.Lines = append(about.Lines, about.Spacer())
  303. copyright := SPACEACE + " v" + SPACEACE_VERSION
  304. about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, copyright)})
  305. copyright = SPACEACE_COPYRIGHT
  306. if door.Unicode {
  307. copyright = strings.Replace(copyright, "(C)", "\u00a9", -1)
  308. }
  309. about.Lines = append(about.Lines,
  310. door.Line{Text: fmt.Sprintf("%*s", -W, copyright),
  311. DefaultColor: door.ColorText("BOLD WHITE ON BLUE")})
  312. for _, text := range []string{"",
  313. "This door was written by Bugz.",
  314. "",
  315. "It is written in Go, understands CP437 and unicode, adapts",
  316. "to screen sizes, uses door32.sys, supports TheDraw Fonts,",
  317. "and runs on Linux and Windows."} {
  318. about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, text)})
  319. }
  320. return about
  321. }
  322. func MakeScoresRender(dateColor string, datelen int, nickColor string, nicklen int, scoreColor string) func(string) string {
  323. renderF := func(text string) string {
  324. var r door.Render = door.Render{Line: text}
  325. r.Append(dateColor, datelen)
  326. r.Append(nickColor, nicklen)
  327. r.Append(scoreColor, len(text)-(datelen+nicklen))
  328. return r.Result
  329. }
  330. return renderF
  331. }
  332. func DisplayScores(Door *door.Door, db *DBData, config *map[string]string) {
  333. // Scores::score()
  334. // make_top_scores_panel()
  335. W := 38
  336. var top_scores_panel door.Panel = door.Panel{Width: W,
  337. Style: door.DOUBLE,
  338. BorderColor: door.ColorText("CYAN ON BLUE"),
  339. Title: "[ The TOP Monthly Scores: ]",
  340. }
  341. const nick_len int = 17 // Max Nick Length
  342. var date_monthly string
  343. var ok bool
  344. date_monthly, ok = (*config)["date_monthly"]
  345. if !ok {
  346. date_monthly = "January"
  347. }
  348. var longest_month int = len(FormatDate(1631280600, date_monthly))
  349. monthly := db.GetMonthlyScores(15)
  350. if len(monthly) == 0 {
  351. top_scores_panel.Lines = append(top_scores_panel.Lines,
  352. door.Line{Text: fmt.Sprintf("%*s", -W, "No, Not Yet!"),
  353. DefaultColor: door.ColorText("BRI YELLOW ON BLUE"),
  354. })
  355. } else {
  356. ScoresRender := MakeScoresRender(door.ColorText("BOLD WHITE ON BLUE"),
  357. longest_month,
  358. door.ColorText("CYAN ON BLUE"),
  359. nick_len,
  360. door.ColorText("BOLD CYAN ON BLUE"))
  361. YourScoresRender := MakeScoresRender(
  362. door.ColorText("BOLD WHITE ON BLUE"),
  363. longest_month,
  364. door.ColorText("BOLD GREEN ON BLUE"),
  365. nick_len,
  366. door.ColorText("BOLD YELLOW ON BLUE"))
  367. for idx := range monthly {
  368. // What?
  369. timeDate := ToTime(monthly[idx].Date).Unix()
  370. var result string = fmt.Sprintf("%*s", -longest_month,
  371. FormatDate(timeDate, date_monthly))
  372. result += " " + fmt.Sprintf("%*s", -(nick_len-1), monthly[idx].User)
  373. result += fmt.Sprintf(" %d", monthly[idx].Score)
  374. result += strings.Repeat(" ", W-len(result))
  375. if monthly[idx].User == Door.Config.Real_name || monthly[idx].User == Door.Config.Handle {
  376. top_scores_panel.Lines = append(top_scores_panel.Lines,
  377. door.Line{Text: result, RenderF: YourScoresRender})
  378. } else {
  379. top_scores_panel.Lines = append(top_scores_panel.Lines,
  380. door.Line{Text: result, RenderF: ScoresRender})
  381. }
  382. }
  383. }
  384. // end make_top_scores_panel
  385. // make_top_this_month_panel()
  386. W = 38
  387. var title string = fmt.Sprintf("[ The TOP Scores for %s: ]",
  388. FormatDate(time.Now().Unix(), date_monthly))
  389. var top_month_panel door.Panel = door.Panel{Width: W,
  390. Style: door.DOUBLE,
  391. BorderColor: door.ColorText(""),
  392. Title: fmt.Sprintf("%*s", -W, title),
  393. }
  394. scores := db.GetScores(15)
  395. if len(scores) == 0 {
  396. top_month_panel.Lines = append(top_month_panel.Lines,
  397. door.Line{Text: fmt.Sprintf("%*s", -W, "No, Not Yet!"),
  398. DefaultColor: door.ColorText("BRI YELLOW ON BLUE"),
  399. })
  400. } else {
  401. }
  402. // Scores.display_scores()
  403. }
  404. func main() {
  405. var message string
  406. // Get path to binary, and chdir to it.
  407. binaryPath, _ := os.Executable()
  408. binaryPath = filepath.Dir(binaryPath)
  409. _ = os.Chdir(binaryPath)
  410. fmt.Println("Starting space-ace")
  411. rng := rand.New(mt19937.New())
  412. rng.Seed(time.Now().UnixNano())
  413. d := door.Door{}
  414. d.Init("space-ace")
  415. defer d.Close()
  416. db := DBData{}
  417. db.Open("space-database.db")
  418. defer db.Close()
  419. // Use Real_name or Handle? Or read config?
  420. db.User = d.Config.Real_name
  421. const config_filename = "space-ace.yaml"
  422. var err error
  423. if FileExists(config_filename) {
  424. Config, err = LoadConfig(config_filename)
  425. if err != nil {
  426. log.Printf("LoadConfig( %s ): %s\n", config_filename, err)
  427. Config = make(map[string]string)
  428. }
  429. } else {
  430. Config = make(map[string]string)
  431. }
  432. var update_config bool = false
  433. // Guessing at this point as to what settings I actually want.
  434. config_defaults := map[string]string{"hands_per_day": "3",
  435. "date_format": "January 2",
  436. "date_score": "01/02/2006",
  437. "makeup_per_day": "5",
  438. "play_days_ahead": "2",
  439. "date_monthly": "January 2006"}
  440. // _seed
  441. _, ok := Config["_seed"]
  442. if !ok {
  443. // Initialize the seed
  444. Config["_seed"] = fmt.Sprintf("%d,%d,%d", rng.Int31(), rng.Int31(), rng.Int31())
  445. update_config = true
  446. }
  447. for key, value := range config_defaults {
  448. if SetConfigDefault(&Config, key, value) {
  449. update_config = true
  450. }
  451. }
  452. if update_config {
  453. SaveConfig(config_filename, Config)
  454. }
  455. starfield := StarField{}
  456. starfield.Regenerate(rng)
  457. starfield.Display(&d)
  458. // d.Write(door.Goto(1, 1))
  459. cx := (door.Width - 72) / 2
  460. if cx < 0 {
  461. cx = 1
  462. }
  463. for idx, line := range ANSISpace() {
  464. d.Write(door.Goto(cx, idx+3) + line)
  465. }
  466. d.Write(door.Reset + door.CRNL)
  467. // Get the last call value (if they have called before)
  468. last_call, _ := strconv.ParseInt(db.GetSetting("LastCall", "0"), 10, 64)
  469. now := time.Now()
  470. db.SetSetting("LastCall", fmt.Sprintf("%d", now.Unix()))
  471. // Check for maint run -- get FirstOfMonthDate, and see if
  472. // records are older then it is. (If so, yes -- run maint)!
  473. db.ExpireScores(0)
  474. if last_call != 0 {
  475. d.Write("Welcome Back!" + door.CRNL)
  476. delta := now.Sub(time.Unix(last_call, 0))
  477. hours := delta.Hours()
  478. if hours > 24 {
  479. d.Write(fmt.Sprintf("It's been %0.1f days since you last played."+door.CRNL, hours/24))
  480. } else {
  481. if hours > 1 {
  482. d.Write(fmt.Sprintf("It's been %0.1f hours since you last played."+door.CRNL, hours))
  483. } else {
  484. minutes := delta.Minutes()
  485. d.Write(fmt.Sprintf("It's been %0.1f minutes since you last played."+door.CRNL, minutes))
  486. }
  487. }
  488. }
  489. left := d.TimeLeft()
  490. message = fmt.Sprintf("You have %0.2f minutes / %0.2f seconds remaining..."+door.CRNL, left.Minutes(), left.Seconds())
  491. d.Write(message)
  492. press_a_key(&d)
  493. mainmenu := MainMenu()
  494. var choice int
  495. for choice >= 0 {
  496. d.Write(door.Clrscr)
  497. starfield.Display(&d)
  498. choice = mainmenu.Choose(&d)
  499. if choice < 0 {
  500. break
  501. }
  502. option := mainmenu.GetOption(choice)
  503. // fmt.Printf("Choice: %d, Option: %c\n", choice, option)
  504. switch option {
  505. case 'P':
  506. // Play cards
  507. pc := PlayCards{Door: &d,
  508. DB: &db,
  509. Config: &Config,
  510. RNG: rng,
  511. }
  512. pc.Init()
  513. pc.Play()
  514. case 'S':
  515. // View Scores
  516. DisplayScores(&d, &db, &Config)
  517. press_a_key(&d)
  518. case 'C':
  519. // Configure
  520. Configure(&d, &db)
  521. case 'H':
  522. // Help
  523. h := Help()
  524. d.Write(door.Clrscr + h.Output() + door.Reset + door.CRNL)
  525. press_a_key(&d)
  526. case 'A':
  527. // About
  528. a := About()
  529. d.Write(door.Clrscr + a.Output() + door.Reset + door.CRNL)
  530. press_a_key(&d)
  531. case 'Q':
  532. choice = -1
  533. }
  534. }
  535. d.Write(door.Reset + door.CRNL)
  536. message = fmt.Sprintf("Returning to %s ..."+door.CRNL, d.Config.BBSID)
  537. d.Write(message)
  538. // d.WaitKey(1, 0)
  539. left = d.TimeLeft()
  540. message = fmt.Sprintf("You had %0.2f minutes / %0.2f seconds remaining!"+door.CRNL, left.Minutes(), left.Seconds())
  541. d.Write(message)
  542. left = d.TimeUsed()
  543. d.Write(fmt.Sprintf("You used %0.2f seconds / %0.2f minutes."+door.CRNL, left.Seconds(), left.Minutes()))
  544. fmt.Println("Exiting space-ace")
  545. }