| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667 | package mainimport (	"fmt"	"math/rand"	"os"	"path/filepath"	"red-green/door"	"regexp"	"strconv"	"strings"	"time"	"unicode"	_ "github.com/mattn/go-sqlite3"	"github.com/seehuhn/mt19937")var SPACEACE stringvar SPACEACE_VERSION stringvar SPACEACE_COPYRIGHT stringfunc init() {	SPACEACE = "Space Ace"	SPACEACE_VERSION = "0.0.2"	SPACEACE_COPYRIGHT = "(C) 2021 Bugz, Red-Green Software"}var Config map[string]stringfunc find_words(text string) [][]int {	word, _ := regexp.Compile("([A-Za-z]+)")	return word.FindAllStringIndex(text, -1)}func press_a_key(d *door.Door) int {	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 int = -1	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 = d.WaitKey(0, int64(sleep_ms)*1000)	for 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 = d.WaitKey(0, int64(sleep_ms)*1000)	}	d.Write(strings.Repeat("\b", len(text)))	d.Write(any_color(text))	return r}func MainMenu() door.Menu {	// Make the main menu	m := door.Menu{Panel: door.Panel{Width: 25,		X:           5,		Y:           5,		Style:       door.DOUBLE,		Title:       "[ Main Menu: ]",		TitleOffset: 3,		BorderColor: door.ColorText("BRI CYAN ON BLA")}}	m.SelectedR = door.MakeMenuRender(door.ColorText("BOLD CYAN"),		door.ColorText("BOLD BLUE"),		door.ColorText("BOLD CYAN"),		door.ColorText("BOLD BLUE"))	m.UnselectedR = door.MakeMenuRender(door.ColorText("BOLD YEL ON BLUE"),		door.ColorText("BOLD WHI ON BLUE"),		door.ColorText("BOLD YEL ON BLUE"),		door.ColorText("BOLD CYAN ON BLUE"))	m.AddSelection("P", "Play Cards")	m.AddSelection("S", "View Scores")	m.AddSelection("C", "Configure")	m.AddSelection("H", "Help")	m.AddSelection("A", "About this game")	m.AddSelection("Q", "Quit")	return m}func ConfigMenu() door.Menu {	m := door.Menu{Panel: door.Panel{Width: 31,		X:           5,		Y:           5,		Style:       door.DOUBLE,		Title:       "[ Configuration Menu ]",		TitleColor:  door.ColorText("BRI CYAN ON BLUE"),		BorderColor: door.ColorText("CYAN ON BLUE")}}	m.SelectedR = door.MakeMenuRender(door.ColorText("BOLD CYAN"),		door.ColorText("BOLD BLUE"),		door.ColorText("BOLD CYAN"),		door.ColorText("BOLD BLUE"))	m.UnselectedR = door.MakeMenuRender(door.ColorText("BOLD YEL ON BLUE"),		door.ColorText("BOLD WHI ON BLUE"),		door.ColorText("BOLD YEL ON BLUE"),		door.ColorText("BOLD CYAN ON BLUE"))	m.AddSelection("D", "Deck Colors")	m.AddSelection("V", "View Settings")	m.AddSelection("Q", "Quit")	return m}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)}func MakeColorsRender(brackets string, text_color string, colors map[string]string) func(string) string {	renderF := func(text string) string {		words := find_words(text)		var option bool = true		var color_word bool = false		var result string		var last_color string		var word_color string		var words_id = 0		word_pair := words[words_id]		normal := colors["ALL"]		for tpos, c := range text {			if option {				if c == '[' || c == ']' {					if last_color != brackets {						result += brackets						last_color = brackets					}					if c == ']' {						option = false					}				} else {					if last_color != text_color {						result += text_color						last_color = text_color					}				}				result += string(c)			} else {				// Out of the options				if color_word {					if tpos < word_pair[1] {						if last_color != word_color {							result += word_color							last_color = word_color						} else {							color_word = false							if last_color != normal {								result += normal								last_color = normal							}						}						result += string(c)					}				} else {					// look for COLOR word					if tpos >= word_pair[1] {						words_id++						if words_id >= len(words) {							word_pair = []int{999, 999}						} else {							word_pair = words[words_id]						}					}					if word_pair[0] == tpos {						// start of word						possible := text[word_pair[0]:word_pair[1]]						color_code, has := colors[strings.ToUpper(possible)]						// log.Printf("Match %s / found %s %#v\n", possible, color_code, has)						if has {							word_color = color_code						} else {							word_color = colors["ALL"]						}						if last_color != word_color {							result += word_color							last_color = word_color						}					}					result += string(c)				}			}		}		return result	}	return renderF}var DeckColors []stringfunc init() {	DeckColors = []string{		"All", "Brown", "Green", "Red", "Blue", "Cyan", "Magenta", "White"}}func DeckColorMenu(current string) door.Menu {	m := door.Menu{Panel: door.Panel{Width: 31,		X:           5,		Y:           5,		Style:       door.DOUBLE,		Title:       "[ Deck Menu ]",		TitleColor:  door.ColorText("BRI CYAN ON BLUE"),		BorderColor: door.ColorText("CYAN ON BLUE")}}	unselectMap := map[string]string{"BLUE": door.ColorText("BRI BLUE ON BLUE"),		"BROWN":   door.ColorText("BROWN ON BLUE"),		"RED":     door.ColorText("BRI RED ON BLUE"),		"CYAN":    door.ColorText("CYAN ON BLUE"),		"GREEN":   door.ColorText("BRI GREEN ON BLUE"),		"MAGENTA": door.ColorText("BRI MAGENTA ON BLUE"),		"WHITE":   door.ColorText("BRI WHITE ON BLUE"),		"ALL":     door.ColorText("BRI WHITE ON BLUE")}	selectMap := map[string]string{"BLUE": door.ColorText("BRI BLUE ON BLACK"),		"BROWN":   door.ColorText("BROWN ON BLACK"),		"RED":     door.ColorText("BRI RED ON BLACK"),		"CYAN":    door.ColorText("CYAN ON BLACK"),		"GREEN":   door.ColorText("BRI GREEN ON BLACK"),		"MAGENTA": door.ColorText("BRI MAGENTA ON BLACK"),		"WHITE":   door.ColorText("BRI WHITE ON BLACK"),		"ALL":     door.ColorText("BRI WHITE ON BLACK")}	m.SelectedR = MakeColorsRender(door.ColorText("BOLD CYAN"),		door.ColorText("BOLD BLUE"),		selectMap)	m.UnselectedR = MakeColorsRender(door.ColorText("BOLD YEL ON BLUE"),		door.ColorText("BOLD WHI ON BLUE"),		unselectMap)	m.Chosen = 0	for idx, color := range DeckColors {		m.AddSelection(color[:1], color)		if color == current {			m.Chosen = idx		}	}	return m}func RenderStatusValue(status string, value string) func(string) string {	renderF := func(text string) string {		var result string		pos := strings.Index(text, ":")		result = status + text[:pos] + value + text[pos:]		return result	}	return renderF}// Display the SysOp settings in the Config/yaml filefunc 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])			}			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 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")	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"	if FileExists(config_filename) {		Config = LoadConfig(config_filename)	} 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) + door.Reset)	// 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)!	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		case 'S':			// View Scores		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")}
 |