package main import ( "fmt" "log" "math/rand" "os" "path/filepath" "red-green/door" "strconv" "strings" "time" "unicode" "github.com/seehuhn/mt19937" ) var Config map[string]string func press_a_key(d *door.Door) rune { green := door.ColorText("BOLD GREEN") blue := door.ColorText("BOLD BLUE") yellow := door.ColorText("BOLD YELLOW") any_caps := green any_lower := yellow any_color := func(text string) string { var output string var lastColor string for _, letter := range text { if unicode.IsUpper(letter) { if lastColor != any_caps { lastColor = any_caps output += any_caps } output += string(letter) } else { if lastColor != any_lower { lastColor = any_lower output += any_lower } output += string(letter) } } return output } text := "Press a key to continue..." work_text := []rune(text) d.Write(door.Reset + any_color(text)) var r rune var ex door.Extended var err error var t int sleep_ms := 250 ms_sleep := 0 words := find_words(text) var word int = 0 var wpos int = 0 current_word := text[words[word][0]:words[word][1]] t = 0 r, ex, err = d.WaitKey(time.Duration(sleep_ms) * time.Millisecond) // 0, int64(sleep_ms)*1000) for err == door.ErrTimeout && ex == door.NOP { // r == -1 { ms_sleep += sleep_ms if ms_sleep > 1000 { ms_sleep -= 1000 t++ if t >= int(door.Inactivity) { d.Write(strings.Repeat("\b", len(text))) d.Write(any_color(text) + door.CRNL) return -1 } } wpos++ if wpos == len(current_word) { word++ wpos = 0 work_text = []rune(text) if word == len(words) { word = 0 if any_caps == green { any_caps = blue } else { any_caps = green } d.Write(strings.Repeat("\b", len(text))) d.Write(any_color(string(work_text))) current_word = text[words[word][0]:words[word][1]] goto READKEY } current_word = text[words[word][0]:words[word][1]] } { c := rune(current_word[wpos]) for !unicode.IsLower(c) { wpos++ if wpos == len(current_word) { wpos = 0 word++ work_text = []rune(text) if word == len(words) { word = 0 } current_word = text[words[word][0]:words[word][1]] } c = rune(current_word[wpos]) } // Ok, we found something that's lower case work_text[words[word][0]+wpos] = unicode.ToUpper(c) } d.Write(strings.Repeat("\b", len(text))) d.Write(any_color(string(work_text))) READKEY: r, ex, err = d.WaitKey(time.Duration(sleep_ms) * time.Millisecond) // 0, int64(sleep_ms)*1000) } d.Write(strings.Repeat("\b", len(text))) d.Write(any_color(text)) return r } func display_information(d *door.Door) { d.Write(door.Clrscr) keyColor := door.ColorText("BRI GREEN") sepColor := door.ColorText("BRI YEL") valColor := door.ColorText("CYAN") nice_format := func(key string, value string) string { return fmt.Sprintf("%s%-20s %s: %s%s", keyColor, key, sepColor, valColor, value) + door.CRNL } d.Write(nice_format("BBS Software", d.Config.BBSID)) d.Write(nice_format("Real Name", d.Config.Real_name)) d.Write(nice_format("Handle", d.Config.Handle)) d.Write(nice_format("User #", strconv.Itoa(d.Config.User_number))) d.Write(nice_format("Security Level", strconv.Itoa(d.Config.Security_level))) d.Write(nice_format("Node #", strconv.Itoa(d.Config.Node))) d.Write(nice_format("Unicode", strconv.FormatBool(door.Unicode))) d.Write(nice_format("CP437", strconv.FormatBool(door.CP437))) d.Write(nice_format("Screen Size", fmt.Sprintf("%d X %d", door.Width, door.Height))) } func panel_demo(d *door.Door) { width := 55 fmtStr := "%-55s" p := door.Panel{X: 5, Y: 5, Width: width, Style: door.DOUBLE, BorderColor: door.ColorText("CYAN ON BLUE"), Title: "[ Panel Demo ]"} lineColor := door.ColorText("BRIGHT WHI ON BLUE") // Add lines to the panel for _, line := range []string{"The BBS Door Panel Demo", "(C) 2021 Red Green Software, https://red-green.com"} { if door.Unicode { line = strings.Replace(line, "(C)", "\u00a9", -1) } l := door.Line{Text: fmt.Sprintf(fmtStr, line), DefaultColor: lineColor} p.Lines = append(p.Lines, l) } p.Lines = append(p.Lines, p.Spacer()) p.Lines = append(p.Lines, door.Line{Text: fmt.Sprintf(fmtStr, "Welcome to golang!"), DefaultColor: lineColor}) d.Write(door.Clrscr) d.Write(p.Output() + door.CRNL) } /* door::renderFunction statusValue(door::ANSIColor status, door::ANSIColor value) { door::renderFunction rf = [status, value](const std::string &txt) -> door::Render { door::Render r(txt); size_t pos = txt.find(':'); if (pos == std::string::npos) { for (char const &c : txt) { if (std::isdigit(c)) r.append(value); else r.append(status); } } else { pos++; r.append(status, pos); r.append(value, txt.length() - pos); } return r; }; return rf; } */ func RenderStatusValue(status string, value string) func(string) string { renderF := func(text string) string { pos := strings.Index(text, ":") if pos != -1 { return status + text[:pos] + value + text[pos:] } else { var r door.Render = door.Render{Line: text} for _, letter := range text { if unicode.IsDigit(letter) { r.Append(value, 1) } else { r.Append(status, 1) } } return r.Result } } return renderF } // Display the SysOp settings in the Config/yaml file func DisplaySettings(d *door.Door) { d.Write(door.Clrscr + door.Reset) W := 35 panel := door.Panel{Width: W, X: 5, Y: 5, Style: door.DOUBLE, BorderColor: door.ColorText("BOLD CYAN ON BLUE"), } l := door.Line{Text: fmt.Sprintf("%*s", W, "Game Settings - SysOp Configurable"), DefaultColor: door.ColorText("BOLD GREEN ON BLUE")} panel.Lines = append(panel.Lines, l) renderF := RenderStatusValue(door.ColorText("BOLD YELLOW ON BLUE"), door.ColorText("BOLD GREEN ON BLUE")) for key, value := range Config { if key[0] == '_' { continue } key = strings.Replace(key, "_", " ", -1) l = door.Line{Text: fmt.Sprintf("%20s : %*s", key, -(W - 23), value), RenderF: renderF} panel.Lines = append(panel.Lines, l) } d.Write(panel.Output() + door.Reset + door.CRNL) press_a_key(d) } func Configure(d *door.Door, db *DBData) { menu := ConfigMenu() // deckcolor := "DeckColor" // save_deckcolor := false var choice int for choice >= 0 { d.Write(door.Clrscr) choice = menu.Choose(d) if choice < 0 { break } option := menu.GetOption(choice) switch option { case 'D': // Deck color current_color := db.GetSetting("DeckColor", "All") colormenu := DeckColorMenu(current_color) d.Write(door.Clrscr) c := colormenu.Choose(d) if c > 0 { db.SetSetting("DeckColor", DeckColors[c-1]) } d.Write(door.CRNL) press_a_key(d) case 'V': // View settings DisplaySettings(d) case 'Q': choice = -1 } } } func Help() door.Panel { W := 60 center_x := (door.Width - W) / 2 center_y := (door.Height - 16) / 2 help := door.Panel{X: center_x, Y: center_y, Width: W, Style: door.DOUBLE, BorderColor: door.ColorText("BOLD YELLOW ON BLUE")} help.Lines = append(help.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, "Help"), DefaultColor: door.ColorText("BOLD CYAN ON BLUE")}) help.Lines = append(help.Lines, help.Spacer()) copyright := SPACEACE + " v" + SPACEACE_VERSION help.Lines = append(help.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, copyright), DefaultColor: door.ColorText("BOLD WHITE ON BLUE")}) copyright = SPACEACE_COPYRIGHT if door.Unicode { copyright = strings.Replace(copyright, "(C)", "\u00a9", -1) } help.Lines = append(help.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, copyright), DefaultColor: door.ColorText("BOLD WHITE ON BLUE")}) for _, text := range []string{"", "Use Left/Right arrow keys, or 4/6 keys to move marker.", "The marker wraps around the sides of the screen.", "", "Select card to play with Space or 5.", "A card can play if it is higher or lower in rank by 1.", "", "Enter draws another card.", "", "Example: A Jack could play either a Ten or a Queen.", "Example: A King could play either an Ace or a Queen.", "", "The more cards in your streak, the more points earn.", } { help.Lines = append(help.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, text)}) } return help } func About() door.Panel { W := 60 center_x := (door.Width - W) / 2 center_y := (door.Height - 16) / 2 about := door.Panel{X: center_x, Y: center_y, Width: W, Style: door.SINGLE_DOUBLE, BorderColor: door.ColorText("BOLD YELLOW ON BLUE"), } about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, "About This Door"), DefaultColor: door.ColorText("BOLD CYAN ON BLUE")}) about.Lines = append(about.Lines, about.Spacer()) copyright := SPACEACE + " v" + SPACEACE_VERSION about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, copyright)}) copyright = SPACEACE_COPYRIGHT if door.Unicode { copyright = strings.Replace(copyright, "(C)", "\u00a9", -1) } about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, copyright), DefaultColor: door.ColorText("BOLD WHITE ON BLUE")}) for _, text := range []string{"", "This door was written by Bugz.", "", "It is written in Go, understands CP437 and unicode, adapts", "to screen sizes, uses door32.sys, supports TheDraw Fonts,", "and runs on Linux and Windows."} { about.Lines = append(about.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, text)}) } return about } func MakeScoresRender(dateColor string, datelen int, nickColor string, nicklen int, scoreColor string) func(string) string { renderF := func(text string) string { var r door.Render = door.Render{Line: text} r.Append(dateColor, datelen) r.Append(nickColor, nicklen) r.Append(scoreColor, len(text)-(datelen+nicklen)) return r.Result } return renderF } func DisplayScores(Door *door.Door, db *DBData, config *map[string]string) { // Scores::score() // make_top_scores_panel() W := 38 var top_scores_panel door.Panel = door.Panel{Width: W, Style: door.DOUBLE, BorderColor: door.ColorText("CYAN ON BLUE"), Title: "[ The TOP Monthly Scores: ]", } const nick_len int = 17 // Max Nick Length var date_monthly string var ok bool date_monthly, ok = (*config)["date_monthly"] if !ok { date_monthly = "January" } var longest_month int = len(FormatDate(1631280600, date_monthly)) monthly := db.GetMonthlyScores(15) if len(monthly) == 0 { top_scores_panel.Lines = append(top_scores_panel.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, "No, Not Yet!"), DefaultColor: door.ColorText("BRI YELLOW ON BLUE"), }) } else { ScoresRender := MakeScoresRender(door.ColorText("BOLD WHITE ON BLUE"), longest_month, door.ColorText("CYAN ON BLUE"), nick_len, door.ColorText("BOLD CYAN ON BLUE")) YourScoresRender := MakeScoresRender( door.ColorText("BOLD WHITE ON BLUE"), longest_month, door.ColorText("BOLD GREEN ON BLUE"), nick_len, door.ColorText("BOLD YELLOW ON BLUE")) for idx := range monthly { // What? timeDate := ToTime(monthly[idx].Date).Unix() var result string = fmt.Sprintf("%*s", -longest_month, FormatDate(timeDate, date_monthly)) result += " " + fmt.Sprintf("%*s", -(nick_len-1), monthly[idx].User) result += fmt.Sprintf(" %d", monthly[idx].Score) result += strings.Repeat(" ", W-len(result)) if monthly[idx].User == Door.Config.Real_name || monthly[idx].User == Door.Config.Handle { top_scores_panel.Lines = append(top_scores_panel.Lines, door.Line{Text: result, RenderF: YourScoresRender}) } else { top_scores_panel.Lines = append(top_scores_panel.Lines, door.Line{Text: result, RenderF: ScoresRender}) } } } // end make_top_scores_panel // make_top_this_month_panel() W = 38 var title string = fmt.Sprintf("[ The TOP Scores for %s: ]", FormatDate(time.Now().Unix(), date_monthly)) var top_month_panel door.Panel = door.Panel{Width: W, Style: door.DOUBLE, BorderColor: door.ColorText(""), Title: fmt.Sprintf("%*s", -W, title), } scores := db.GetScores(15) if len(scores) == 0 { top_month_panel.Lines = append(top_month_panel.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, "No, Not Yet!"), DefaultColor: door.ColorText("BRI YELLOW ON BLUE"), }) } else { } // Scores.display_scores() } func main() { var message string // Get path to binary, and chdir to it. binaryPath, _ := os.Executable() binaryPath = filepath.Dir(binaryPath) _ = os.Chdir(binaryPath) fmt.Println("Starting space-ace") rng := rand.New(mt19937.New()) rng.Seed(time.Now().UnixNano()) d := door.Door{} d.Init("space-ace") defer d.Close() db := DBData{} db.Open("space-database.db") defer db.Close() // Use Real_name or Handle? Or read config? db.User = d.Config.Real_name const config_filename = "space-ace.yaml" var err error if FileExists(config_filename) { Config, err = LoadConfig(config_filename) if err != nil { log.Printf("LoadConfig( %s ): %s\n", config_filename, err) Config = make(map[string]string) } } else { Config = make(map[string]string) } var update_config bool = false // Guessing at this point as to what settings I actually want. config_defaults := map[string]string{"hands_per_day": "3", "date_format": "January 2", "date_score": "01/02/2006", "makeup_per_day": "5", "play_days_ahead": "2", "date_monthly": "January 2006"} // _seed _, ok := Config["_seed"] if !ok { // Initialize the seed Config["_seed"] = fmt.Sprintf("%d,%d,%d", rng.Int31(), rng.Int31(), rng.Int31()) update_config = true } for key, value := range config_defaults { if SetConfigDefault(&Config, key, value) { update_config = true } } if update_config { SaveConfig(config_filename, Config) } starfield := StarField{} starfield.Regenerate(rng) starfield.Display(&d) // d.Write(door.Goto(1, 1)) cx := (door.Width - 72) / 2 if cx < 0 { cx = 1 } for idx, line := range ANSISpace() { d.Write(door.Goto(cx, idx+3) + line) } d.Write(door.Reset + door.CRNL) // Get the last call value (if they have called before) last_call, _ := strconv.ParseInt(db.GetSetting("LastCall", "0"), 10, 64) now := time.Now() db.SetSetting("LastCall", fmt.Sprintf("%d", now.Unix())) // Check for maint run -- get FirstOfMonthDate, and see if // records are older then it is. (If so, yes -- run maint)! db.ExpireScores(0) if last_call != 0 { d.Write("Welcome Back!" + door.CRNL) delta := now.Sub(time.Unix(last_call, 0)) hours := delta.Hours() if hours > 24 { d.Write(fmt.Sprintf("It's been %0.1f days since you last played."+door.CRNL, hours/24)) } else { if hours > 1 { d.Write(fmt.Sprintf("It's been %0.1f hours since you last played."+door.CRNL, hours)) } else { minutes := delta.Minutes() d.Write(fmt.Sprintf("It's been %0.1f minutes since you last played."+door.CRNL, minutes)) } } } left := d.TimeLeft() message = fmt.Sprintf("You have %0.2f minutes / %0.2f seconds remaining..."+door.CRNL, left.Minutes(), left.Seconds()) d.Write(message) press_a_key(&d) mainmenu := MainMenu() var choice int for choice >= 0 { d.Write(door.Clrscr) starfield.Display(&d) choice = mainmenu.Choose(&d) if choice < 0 { break } option := mainmenu.GetOption(choice) // fmt.Printf("Choice: %d, Option: %c\n", choice, option) switch option { case 'P': // Play cards pc := PlayCards{Door: &d, DB: &db, Config: &Config, RNG: rng, } pc.Init() pc.Play() case 'S': // View Scores DisplayScores(&d, &db, &Config) press_a_key(&d) case 'C': // Configure Configure(&d, &db) case 'H': // Help h := Help() d.Write(door.Clrscr + h.Output() + door.Reset + door.CRNL) press_a_key(&d) case 'A': // About a := About() d.Write(door.Clrscr + a.Output() + door.Reset + door.CRNL) press_a_key(&d) case 'Q': choice = -1 } } d.Write(door.Reset + door.CRNL) message = fmt.Sprintf("Returning to %s ..."+door.CRNL, d.Config.BBSID) d.Write(message) // d.WaitKey(1, 0) left = d.TimeLeft() message = fmt.Sprintf("You had %0.2f minutes / %0.2f seconds remaining!"+door.CRNL, left.Minutes(), left.Seconds()) d.Write(message) left = d.TimeUsed() d.Write(fmt.Sprintf("You used %0.2f seconds / %0.2f minutes."+door.CRNL, left.Seconds(), left.Minutes())) fmt.Println("Exiting space-ace") }