package game import "strings" // options.c — the option command and ROGUEOPTS parsing. // optKind selects the get/put behavior of an option (the C function // pointers o_putfunc/o_getfunc). type optKind int const ( optBool optKind = iota optSeeFloor optInvT optStr ) // optDesc describes an option (options.c OPTION). type optDesc struct { name string prompt string kind optKind boolP *bool intP *int strP *string } // optList builds the options.c optlist for this game. func (g *RogueGame) optList() []optDesc { o := &g.Options return []optDesc{ {"terse", "Terse output", optBool, &o.Terse, nil, nil}, {"flush", "Flush typeahead during battle", optBool, &o.FightFlush, nil, nil}, {"jump", "Show position only at end of run", optBool, &o.Jump, nil, nil}, {"seefloor", "Show the lamp-illuminated floor", optSeeFloor, &o.SeeFloor, nil, nil}, {"passgo", "Follow turnings in passageways", optBool, &o.PassGo, nil, nil}, {"tombstone", "Print out tombstone when killed", optBool, &o.Tombstone, nil, nil}, {"inven", "Inventory style", optInvT, nil, &o.InvType, nil}, {"name", "Name", optStr, nil, nil, &g.Whoami}, {"fruit", "Fruit", optStr, nil, nil, &g.Fruit}, {"file", "Save file", optStr, nil, nil, &g.FileName}, } } // option prints and then sets options from the terminal (options.c option). func (g *RogueGame) option() { hw := g.scr.Hw optlist := g.optList() hw.Clear() // Display current values of options for i := range optlist { op := &optlist[i] g.prOptname(op) g.putOpt(op) hw.AddCh('\n') } // Set values hw.Move(0, 0) for i := 0; i < len(optlist); i++ { op := &optlist[i] g.prOptname(op) retval := g.getOpt(op) if retval != Norm { if retval == Quit { break } // MINUS: back up one option if i > 0 { hw.Move(i-1, 0) i -= 2 } else { // trying to back up beyond the top hw.Move(0, 0) i-- } } } // Switch back to original screen hw.MvAddStr(NumLines-1, 0, "--Press space to continue--") g.scr.RefreshWin(hw) g.waitFor(' ') g.refresh() g.After = false } // prOptname prints out the option name prompt (options.c pr_optname). func (g *RogueGame) prOptname(op *optDesc) { g.scr.Hw.Printw("%s (\"%s\"): ", op.prompt, op.name) } // putOpt prints an option's current value (options.c put_bool/put_str/ // put_inv_t). func (g *RogueGame) putOpt(op *optDesc) { hw := g.scr.Hw switch op.kind { case optBool, optSeeFloor: hw.AddStr(boolStr(*op.boolP)) case optInvT: hw.AddStr(invTName[*op.intP]) case optStr: hw.AddStr(*op.strP) } } func boolStr(b bool) string { if b { return "True" } return "False" } // getOpt reads a new value for an option (options.c get_bool/get_sf/ // get_inv_t/get_str dispatch). func (g *RogueGame) getOpt(op *optDesc) int { switch op.kind { case optBool: return g.getBool(op.boolP) case optSeeFloor: return g.getSf(op.boolP) case optInvT: return g.getInvT(op.intP) default: return g.getStr(op.strP, g.scr.Hw) } } // getBool allows changing a boolean option and prints it out (options.c // get_bool). func (g *RogueGame) getBool(bp *bool) int { win := g.scr.Hw oy, ox := win.GetYX() win.AddStr(boolStr(*bp)) for { win.Move(oy, ox) g.scr.RefreshWin(win) switch g.readchar() { case 't', 'T': *bp = true case 'f', 'F': *bp = false case '\n', '\r': case Escape: return Quit case '-': return Minus default: win.Move(oy, ox+10) win.AddStr("(T or F)") continue } break } win.Move(oy, ox) win.AddStr(boolStr(*bp)) win.AddCh('\n') return Norm } // getSf changes see_floor and handles the display transition (options.c // get_sf). func (g *RogueGame) getSf(bp *bool) int { wasSf := g.Options.SeeFloor retval := g.getBool(bp) if retval == Quit { return Quit } if wasSf != g.Options.SeeFloor { if !g.Options.SeeFloor { g.Options.SeeFloor = true g.eraseLamp(g.Player.Pos, g.Player.Room) g.Options.SeeFloor = false } else { g.look(false) } } return Norm } // getStr sets a string option (options.c get_str). win selects between the // options window and the message line (C's stdscr), which changes the '-' // and mpos behavior. func (g *RogueGame) getStr(opt *string, win *Window) int { onStd := win == g.scr.Std oy, ox := win.GetYX() g.scr.RefreshWin(win) // loop reading in the string, and put it in a temporary buffer var buf []byte var c byte for { c = g.readchar() if c == '\n' || c == '\r' || c == Escape { break } if c == 8 || c == 0x7f { // erase character if len(buf) > 0 { buf = buf[:len(buf)-1] win.Move(oy, ox+len(displayStr(buf))) } win.Clrtoeol() g.scr.RefreshWin(win) continue } if c == CTRL('U') { // kill character buf = buf[:0] win.Move(oy, ox) win.Clrtoeol() g.scr.RefreshWin(win) continue } if len(buf) == 0 { if c == '-' && !onStd { break } if c == '~' { buf = append(buf, g.Home...) win.AddStr(g.Home) win.Clrtoeol() g.scr.RefreshWin(win) continue } } if len(buf) >= MaxInp || !(isPrint(c) || c == ' ') { continue // C beeps here } buf = append(buf, c) win.AddStr(unctrl(c)) win.Clrtoeol() g.scr.RefreshWin(win) } if len(buf) > 0 { // only change option if something has been typed *opt = strucpy(string(buf)) } win.MvPrintw(oy, ox, "%s\n", *opt) g.scr.RefreshWin(win) if onStd { g.Msgs.Mpos += len(buf) } switch c { case '-': return Minus case Escape: return Quit default: return Norm } } // displayStr renders a buffer the way the input echo did. func displayStr(buf []byte) string { var sb strings.Builder for _, c := range buf { sb.WriteString(unctrl(c)) } return sb.String() } // getInvT gets an inventory type name (options.c get_inv_t). func (g *RogueGame) getInvT(ip *int) int { win := g.scr.Hw oy, ox := win.GetYX() win.AddStr(invTName[*ip]) for { win.Move(oy, ox) g.scr.RefreshWin(win) switch g.readchar() { case 'o', 'O': *ip = InvOver case 's', 'S': *ip = InvSlow case 'c', 'C': *ip = InvClear case '\n', '\r': case Escape: return Quit case '-': return Minus default: win.Move(oy, ox+15) win.AddStr("(O, S, or C)") continue } break } win.MvPrintw(oy, ox, "%s\n", invTName[*ip]) return Norm } // ParseOpts parses options from a string, usually taken from the // environment: comma separated values, booleans stated as "name" (true) or // "noname" (false), strings as "name=..." (options.c parse_opts). func (g *RogueGame) ParseOpts(str string) { optlist := g.optList() for str != "" { // Get option name i := 0 for i < len(str) && isAlpha(str[i]) { i++ } name := str[:i] rest := str[i:] matched := false for oi := range optlist { op := &optlist[oi] isBoolOpt := op.kind == optBool || op.kind == optSeeFloor if strings.HasPrefix(op.name, name) && name != "" { matched = true if isBoolOpt { *op.boolP = true } else { // Skip to start of string value for rest != "" && rest[0] == '=' { rest = rest[1:] } val := rest var prefix string if val != "" && val[0] == '~' { prefix = g.Home val = val[1:] for val != "" && val[0] == '/' { val = val[1:] } } // Skip to end of string value end := strings.IndexByte(val, ',') if end < 0 { end = len(val) } word := val[:end] rest = val[end:] if op.kind == optInvT { // check for type of inventory w := word if w != "" { w = string(toUpper(w[0])) + w[1:] } for ti, tn := range invTName { if strings.HasPrefix(tn, w) { *op.intP = ti break } } } else { *op.strP = prefix + strucpy(word) } } break } else if isBoolOpt && strings.HasPrefix(name, "no") && strings.HasPrefix(op.name, name[2:]) { matched = true *op.boolP = false break } } _ = matched // skip to start of next option name for rest != "" && !isAlpha(rest[0]) { rest = rest[1:] } str = rest } } // strucpy copies a string keeping only printable characters, capped at // MAXINP (options.c strucpy). func strucpy(s string) string { if len(s) > MaxInp { s = s[:MaxInp] } var sb strings.Builder for i := 0; i < len(s); i++ { if isPrint(s[i]) || s[i] == ' ' { sb.WriteByte(s[i]) } } return sb.String() }