diff --git a/game/options.go b/game/options.go index 15fea9c..7c3d594 100644 --- a/game/options.go +++ b/game/options.go @@ -205,55 +205,11 @@ func (g *RogueGame) getStr(opt *string, win *Window) int { ) for { c = g.readchar() - if c == '\n' || c == '\r' || c == Escape { + if endsInput(c) || (len(buf) == 0 && c == '-' && !onStd) { 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) + buf = g.getStrEdit(win, buf, c, oy, ox) } if len(buf) > 0 { // only change option if something has been typed @@ -277,6 +233,52 @@ func (g *RogueGame) getStr(opt *string, win *Window) int { } } +// endsInput reports the keys that finish line input (options.c get_str). +func endsInput(c byte) bool { + return c == '\n' || c == '\r' || c == Escape +} + +// getStrErase deletes the last character of the buffer (options.c +// get_str). +func getStrErase(win *Window, buf []byte, oy, ox int) []byte { + if len(buf) > 0 { + buf = buf[:len(buf)-1] + win.Move(oy, ox+len(displayStr(buf))) + } + + win.Clrtoeol() + + return buf +} + +// getStrEdit applies one key to the line editor's buffer: erase, kill, +// home expansion, or a typed character (options.c get_str). +func (g *RogueGame) getStrEdit(win *Window, buf []byte, c byte, oy, ox int) []byte { + switch { + case c == 8 || c == 0x7f: + buf = getStrErase(win, buf, oy, ox) + case c == CTRL('U'): // kill character + buf = buf[:0] + + win.Move(oy, ox) + win.Clrtoeol() + case len(buf) == 0 && c == '~': + buf = append(buf, g.Home...) + win.AddStr(g.Home) + win.Clrtoeol() + case len(buf) >= MaxInp || (!isPrint(c) && c != ' '): + return buf // C beeps here + default: + buf = append(buf, c) + win.AddStr(unctrl(c)) + win.Clrtoeol() + } + + g.scr.RefreshWin(win) + + return buf +} + // displayStr renders a buffer the way the input echo did. func displayStr(buf []byte) string { var sb strings.Builder @@ -337,75 +339,7 @@ func (g *RogueGame) ParseOpts(str string) { 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 g.data.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 + rest := g.parseOptName(optlist, str[:i], str[i:]) // skip to start of next option name for rest != "" && !isAlpha(rest[0]) { rest = rest[1:] @@ -415,6 +349,89 @@ func (g *RogueGame) ParseOpts(str string) { } } +// parseOptName applies one named option, returning the unconsumed +// remainder: "name" turns a boolean on, "noname" turns it off, and +// string options consume a value (the option scan of options.c +// parse_opts). +func (g *RogueGame) parseOptName(optlist []optDesc, name, rest string) string { + for oi := range optlist { + op := &optlist[oi] + + isBoolOpt := op.kind == optBool || op.kind == optSeeFloor + if strings.HasPrefix(op.name, name) && name != "" { + if isBoolOpt { + *op.boolP = true + + return rest + } + + return g.parseOptValue(op, rest) + } + + if isBoolOpt && strings.HasPrefix(name, "no") && + strings.HasPrefix(op.name, name[2:]) { + *op.boolP = false + + return rest + } + } + + return rest +} + +// parseOptValue consumes an option's "=value" from rest, storing it, +// and returns the remainder (the string arm of options.c parse_opts). +func (g *RogueGame) parseOptValue(op *optDesc, rest string) string { + // 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] + + if op.kind == optInvT { + g.parseInvType(op, word) + } else { + *op.strP = prefix + strucpy(word) + } + + return val[end:] +} + +// parseInvType matches an inventory-style name by prefix (options.c +// parse_opts). +func (g *RogueGame) parseInvType(op *optDesc, word string) { + // check for type of inventory + if word != "" { + word = string(toUpper(word[0])) + word[1:] + } + + for ti, tn := range g.data.invTName { + if strings.HasPrefix(tn, word) { + *op.intP = ti + + break + } + } +} + // strucpy copies a string keeping only printable characters, capped at // MAXINP (options.c strucpy). func strucpy(s string) string {