Decompose things.go (refactor step 7)

inventoryName splits into nameScroll/nameFood/nameWeapon/nameArmor/
describeWorn/fixNameCase; newThing gains newFoodThing/newWeaponThing/
newArmorThing/newRingThing; dropCheck gains dropRing; addLine splits
into addLineSlow/addLinePaged/addLineOverlay. things.go is
complexity-clean. Behavior and RNG call order unchanged.
This commit is contained in:
2026-07-22 21:50:44 +07:00
parent 0b798c9c82
commit e1f065e783

View File

@@ -23,47 +23,74 @@ func (g *RogueGame) inventoryName(obj *Object, drop bool) string {
case KindWand: case KindWand:
g.nameit(&pb, obj, it.WandType[which], it.WandMade[which], &it.Sticks[which], chargeStr) g.nameit(&pb, obj, it.WandType[which], it.WandMade[which], &it.Sticks[which], chargeStr)
case KindScroll: case KindScroll:
g.nameScroll(&pb, obj)
case KindFood:
g.nameFood(&pb, obj)
case KindWeapon:
g.nameWeapon(&pb, obj)
case KindArmor:
g.nameArmor(&pb, obj)
case KindAmulet:
pb.WriteString("The Amulet of Yendor")
case KindGold:
fmt.Fprintf(&pb, "%d Gold pieces", obj.GoldValue)
}
return fixNameCase(g.describeWorn(obj, pb.String()), drop)
}
// nameScroll writes a scroll's inventory name (things.c inv_name).
func (g *RogueGame) nameScroll(pb *strings.Builder, obj *Object) {
if obj.Count == 1 { if obj.Count == 1 {
pb.WriteString("A scroll ") pb.WriteString("A scroll ")
} else { } else {
fmt.Fprintf(&pb, "%d scrolls ", obj.Count) fmt.Fprintf(pb, "%d scrolls ", obj.Count)
} }
op := &it.Scrolls[which] op := &g.Items.Scrolls[obj.Which]
switch { switch {
case op.Know: case op.Know:
fmt.Fprintf(&pb, "of %s", op.Name) fmt.Fprintf(pb, "of %s", op.Name)
case op.Guess != "": case op.Guess != "":
fmt.Fprintf(&pb, "called %s", op.Guess) fmt.Fprintf(pb, "called %s", op.Guess)
default: default:
fmt.Fprintf(&pb, "titled '%s'", it.ScrNames[which]) fmt.Fprintf(pb, "titled '%s'", g.Items.ScrNames[obj.Which])
} }
case KindFood: }
if which == 1 {
// nameFood writes a food item's inventory name; which 1 is the fruit
// (things.c inv_name).
func (g *RogueGame) nameFood(pb *strings.Builder, obj *Object) {
if obj.Which == 1 {
if obj.Count == 1 { if obj.Count == 1 {
fmt.Fprintf(&pb, "A%s %s", vowelstr(g.Fruit), g.Fruit) fmt.Fprintf(pb, "A%s %s", vowelstr(g.Fruit), g.Fruit)
} else { } else {
fmt.Fprintf(&pb, "%d %ss", obj.Count, g.Fruit) fmt.Fprintf(pb, "%d %ss", obj.Count, g.Fruit)
} }
} else {
return
}
if obj.Count == 1 { if obj.Count == 1 {
pb.WriteString("Some food") pb.WriteString("Some food")
} else { } else {
fmt.Fprintf(&pb, "%d rations of food", obj.Count) fmt.Fprintf(pb, "%d rations of food", obj.Count)
} }
} }
case KindWeapon:
sp := it.Weapons[which].Name // nameWeapon writes a weapon's inventory name (things.c inv_name).
func (g *RogueGame) nameWeapon(pb *strings.Builder, obj *Object) {
sp := g.Items.Weapons[obj.Which].Name
if obj.Count > 1 { if obj.Count > 1 {
fmt.Fprintf(&pb, "%d ", obj.Count) fmt.Fprintf(pb, "%d ", obj.Count)
} else { } else {
fmt.Fprintf(&pb, "A%s ", vowelstr(sp)) fmt.Fprintf(pb, "A%s ", vowelstr(sp))
} }
if obj.Flags.Has(Known) { if obj.Flags.Has(Known) {
fmt.Fprintf(&pb, "%s %s", num(obj.HPlus, obj.DPlus, Weapon), sp) fmt.Fprintf(pb, "%s %s", num(obj.HPlus, obj.DPlus, Weapon), sp)
} else { } else {
pb.WriteString(sp) pb.WriteString(sp)
} }
@@ -73,34 +100,38 @@ func (g *RogueGame) inventoryName(obj *Object, drop bool) string {
} }
if obj.Label != "" { if obj.Label != "" {
fmt.Fprintf(&pb, " called %s", obj.Label) fmt.Fprintf(pb, " called %s", obj.Label)
} }
case KindArmor: }
sp := it.Armors[which].Name
// nameArmor writes an armor's inventory name (things.c inv_name).
func (g *RogueGame) nameArmor(pb *strings.Builder, obj *Object) {
sp := g.Items.Armors[obj.Which].Name
if obj.Flags.Has(Known) { if obj.Flags.Has(Known) {
fmt.Fprintf(&pb, "%s %s [", num(g.data.aClass[which]-obj.ArmorClass, 0, Armor), sp) fmt.Fprintf(pb, "%s %s [",
num(g.data.aClass[obj.Which]-obj.ArmorClass, 0, Armor), sp)
if !g.Options.Terse { if !g.Options.Terse {
pb.WriteString("protection ") pb.WriteString("protection ")
} }
fmt.Fprintf(&pb, "%d]", 10-obj.ArmorClass) fmt.Fprintf(pb, "%d]", 10-obj.ArmorClass)
} else { } else {
pb.WriteString(sp) pb.WriteString(sp)
} }
if obj.Label != "" { if obj.Label != "" {
fmt.Fprintf(&pb, " called %s", obj.Label) fmt.Fprintf(pb, " called %s", obj.Label)
} }
case KindAmulet: }
pb.WriteString("The Amulet of Yendor")
case KindGold: // describeWorn appends the equipped-status notes to an inventory name
fmt.Fprintf(&pb, "%d Gold pieces", obj.GoldValue) // (things.c inv_name).
func (g *RogueGame) describeWorn(obj *Object, out string) string {
if !g.InvDescribe {
return out
} }
out := pb.String()
if g.InvDescribe {
p := &g.Player p := &g.Player
if obj == p.CurArmor { if obj == p.CurArmor {
out += " (being worn)" out += " (being worn)"
@@ -116,14 +147,23 @@ func (g *RogueGame) inventoryName(obj *Object, drop bool) string {
case p.CurRing[Right]: case p.CurRing[Right]:
out += " (on right hand)" out += " (on right hand)"
} }
return out
}
// fixNameCase upper- or lowercases the leading letter to suit the
// sentence it will land in (things.c inv_name).
func fixNameCase(out string, drop bool) string {
if out == "" {
return out
} }
if out != "" {
if drop && isUpper(out[0]) { if drop && isUpper(out[0]) {
out = string(toLower(out[0])) + out[1:] return string(toLower(out[0])) + out[1:]
} else if !drop && isLower(out[0]) {
out = string(toUpper(out[0])) + out[1:]
} }
if !drop && isLower(out[0]) {
return string(toUpper(out[0])) + out[1:]
} }
return out return out
@@ -192,6 +232,17 @@ func (g *RogueGame) dropCheck(obj *Object) bool {
p.CurArmor = nil p.CurArmor = nil
default: default:
g.dropRing(obj)
}
return true
}
// dropRing takes a worn ring off with its side effects (things.c
// dropcheck).
func (g *RogueGame) dropRing(obj *Object) {
p := &g.Player
hand := Right hand := Right
if obj == p.CurRing[Left] { if obj == p.CurRing[Left] {
hand = Left hand = Left
@@ -206,9 +257,6 @@ func (g *RogueGame) dropCheck(obj *Object) bool {
g.unsee(0) g.unsee(0)
g.Extinguish(DUnsee) g.Extinguish(DUnsee)
} }
}
return true
} }
// newThing returns a new random thing for the dungeon (things.c new_thing). // newThing returns a new random thing for the dungeon (things.c new_thing).
@@ -236,6 +284,25 @@ func (g *RogueGame) newThing() *Object {
cur.Kind = KindScroll cur.Kind = KindScroll
cur.Which = pickOne(g, g.Items.Scrolls[:]) cur.Which = pickOne(g, g.Items.Scrolls[:])
case 2: case 2:
g.newFoodThing(cur)
case 3:
g.newWeaponThing(cur)
case 4:
g.newArmorThing(cur)
case 5:
g.newRingThing(cur)
case 6:
cur.Kind = KindWand
cur.Which = pickOne(g, g.Items.Sticks[:])
g.fixStick(cur)
}
return cur
}
// newFoodThing rolls food, one time in ten the fruit (things.c
// new_thing).
func (g *RogueGame) newFoodThing(cur *Object) {
cur.Kind = KindFood cur.Kind = KindFood
g.Player.NoFood = 0 g.Player.NoFood = 0
@@ -244,7 +311,11 @@ func (g *RogueGame) newThing() *Object {
} else { } else {
cur.Which = 1 cur.Which = 1
} }
case 3: }
// newWeaponThing rolls a weapon, sometimes cursed or blessed (things.c
// new_thing).
func (g *RogueGame) newWeaponThing(cur *Object) {
g.initWeapon(cur, WeaponKind(pickOne(g, g.Items.Weapons[:NumWeaponTypes]))) g.initWeapon(cur, WeaponKind(pickOne(g, g.Items.Weapons[:NumWeaponTypes])))
if r := g.rnd(100); r < 10 { if r := g.rnd(100); r < 10 {
@@ -253,7 +324,11 @@ func (g *RogueGame) newThing() *Object {
} else if r < 15 { } else if r < 15 {
cur.HPlus += g.rnd(3) + 1 cur.HPlus += g.rnd(3) + 1
} }
case 4: }
// newArmorThing rolls armor, sometimes cursed or blessed (things.c
// new_thing).
func (g *RogueGame) newArmorThing(cur *Object) {
cur.Kind = KindArmor cur.Kind = KindArmor
cur.Which = pickOne(g, g.Items.Armors[:]) cur.Which = pickOne(g, g.Items.Armors[:])
@@ -264,7 +339,10 @@ func (g *RogueGame) newThing() *Object {
} else if r < 28 { } else if r < 28 {
cur.ArmorClass -= g.rnd(3) + 1 cur.ArmorClass -= g.rnd(3) + 1
} }
case 5: }
// newRingThing rolls a ring, cursing the bad ones (things.c new_thing).
func (g *RogueGame) newRingThing(cur *Object) {
cur.Kind = KindRing cur.Kind = KindRing
cur.Which = pickOne(g, g.Items.Rings[:]) cur.Which = pickOne(g, g.Items.Rings[:])
@@ -277,13 +355,6 @@ func (g *RogueGame) newThing() *Object {
case RingAggravateMonsters, RingTeleportation: case RingAggravateMonsters, RingTeleportation:
cur.Flags.Set(Cursed) cur.Flags.Set(Cursed)
} }
case 6:
cur.Kind = KindWand
cur.Which = pickOne(g, g.Items.Sticks[:])
g.fixStick(cur)
}
return cur
} }
// pickOne picks an item out of a list of possible objects using their // pickOne picks an item out of a list of possible objects using their
@@ -423,7 +494,6 @@ const flushSentinel = "\x00"
func (g *RogueGame) addLine(format string, a ...any) int { func (g *RogueGame) addLine(format string, a ...any) int {
pg := &g.invPage pg := &g.invPage
prompt := "--Press space to continue--"
isFlush := format == flushSentinel isFlush := format == flushSentinel
var line string var line string
@@ -440,14 +510,34 @@ func (g *RogueGame) addLine(format string, a ...any) int {
} }
if g.Options.InvType == InvSlow { if g.Options.InvType == InvSlow {
return g.addLineSlow(line, isFlush)
}
g.addLinePaged(line, isFlush)
return ^Escape
}
// addLineSlow shows one discovery line as a message (the slow-inventory
// arm of things.c add_line).
func (g *RogueGame) addLineSlow(line string, isFlush bool) int {
if !isFlush && line != "" { if !isFlush && line != "" {
if g.msg("%s", line) == Escape { if g.msg("%s", line) == Escape {
return Escape return Escape
} }
} }
pg.lineCnt++ g.invPage.lineCnt++
} else {
return ^Escape
}
// addLinePaged accumulates discovery lines into the paged window,
// prompting between full pages (the windowed arm of things.c add_line).
func (g *RogueGame) addLinePaged(line string, isFlush bool) {
pg := &g.invPage
prompt := "--Press space to continue--"
if !pg.init { if !pg.init {
pg.maxlen = len(prompt) pg.maxlen = len(prompt)
pg.init = true pg.init = true
@@ -455,26 +545,7 @@ func (g *RogueGame) addLine(format string, a ...any) int {
if pg.lineCnt >= NumLines-1 || isFlush { if pg.lineCnt >= NumLines-1 || isFlush {
if g.Options.InvType == InvOver && isFlush && !pg.newpage { if g.Options.InvType == InvOver && isFlush && !pg.newpage {
// Overlay the accumulated list in a box at the top right g.addLineOverlay(prompt)
// of the screen, prompt, and restore what was beneath.
g.msg("")
g.refresh()
saved := NewWindow(NumLines, NumCols)
saved.CopyFrom(g.scr.Std)
lx := NumCols - pg.maxlen - 2
for y := 0; y <= pg.lineCnt; y++ {
for x := 0; x <= pg.maxlen; x++ {
g.scr.Std.MvAddCh(y, lx+x, g.scr.Hw.MvInch(y, x))
}
}
g.scr.Std.MvAddStr(pg.lineCnt, lx, prompt)
g.refresh()
g.waitFor(' ')
g.scr.Std.CopyFrom(saved)
g.refresh()
} else { } else {
g.scr.Hw.MvAddStr(NumLines-1, 0, prompt) g.scr.Hw.MvAddStr(NumLines-1, 0, prompt)
g.scr.RefreshWin(g.scr.Hw) g.scr.RefreshWin(g.scr.Hw)
@@ -498,9 +569,32 @@ func (g *RogueGame) addLine(format string, a ...any) int {
pg.lastLine = line pg.lastLine = line
} }
}
// addLineOverlay draws the accumulated list in a box at the top right
// of the screen, prompts, and restores what was beneath (things.c
// add_line).
func (g *RogueGame) addLineOverlay(prompt string) {
pg := &g.invPage
g.msg("")
g.refresh()
saved := NewWindow(NumLines, NumCols)
saved.CopyFrom(g.scr.Std)
lx := NumCols - pg.maxlen - 2
for y := 0; y <= pg.lineCnt; y++ {
for x := 0; x <= pg.maxlen; x++ {
g.scr.Std.MvAddCh(y, lx+x, g.scr.Hw.MvInch(y, x))
}
} }
return ^Escape g.scr.Std.MvAddStr(pg.lineCnt, lx, prompt)
g.refresh()
g.waitFor(' ')
g.scr.Std.CopyFrom(saved)
g.refresh()
} }
// flushLine is add_line(NULL): force out the accumulated page. // flushLine is add_line(NULL): force out the accumulated page.