Files
rgoue/game/rip.go
sneak 2eff377a73 Un-overload Object fields and pre-parse damage dice (refactor step 3)
Object.Arm carried four meanings in C (o_arm/o_charges/o_goldval plus
ring bonuses); it is now four fields: ArmorClass, Charges, GoldValue,
and Bonus. Stats.Arm becomes Stats.ArmorClass. The Charges()/GoldVal()
accessor pair is gone.

Damage dice strings ("1x4/1x2") are parsed once into DiceSpec — at
table definition for the bestiary and weapon tables, at creation for
items — instead of re-parsed with atoi on every swing as fight.c
roll_em did. ParseDice preserves the C parse semantics exactly,
including the junk-tolerant "%%%x0" bestiary placeholder and the
"000x0" flytrap reset (regression-tested in dice_test.go); the
flytrap's growing grip becomes DiceSpec{{VfHit, 1}}.

Save format bumps to 5.4.4-go3 (field renames and retypes would
silently zero under gob's match-by-name decoding).

No behavior change; full suite green.
2026-07-06 23:07:57 +02:00

250 lines
6.7 KiB
Go

package game
import (
"fmt"
"time"
)
// rip.c — the fun ends: death or a total win.
//
// The C functions here call exit(); the port panics with a gameEnd sentinel
// that Run recovers, so the terminal is restored by normal unwinding.
// gameEnd is the sentinel carried by the panic that replaces my_exit().
type gameEnd struct{ status int }
// myExit leaves the process properly (main.c my_exit): it unwinds to Run.
func (g *RogueGame) myExit(st int) {
g.Playing = false
panic(gameEnd{status: st})
}
var ripArt = []string{
" __________",
" / \\",
" / REST \\",
" / IN \\",
" / PEACE \\",
" / \\",
" | |",
" | |",
" | killed by a |",
" | |",
" | 1980 |",
" *| * * * | *",
" ________)/\\\\_//(\\/(/\\)/\\//\\/|_)_______",
}
// death does something really fun when he dies (rip.c death).
func (g *RogueGame) death(monst byte) {
p := &g.Player
p.Purse -= p.Purse / 10
g.clear()
killer := g.killname(monst, false)
if !g.Options.Tombstone {
g.scr.Std.MvPrintw(NumLines-2, 0, "Killed by ")
if monst != 's' && monst != 'h' {
g.printw("a%s ", vowelstr(killer))
}
g.printw("%s with %d gold", killer, p.Purse)
} else {
year := time.Now().Year()
for i, line := range ripArt {
g.scr.Std.MvAddStr(8+i, 0, line)
}
g.mvaddstr(17, center(killer), killer)
if monst == 's' || monst == 'h' {
g.mvaddch(16, 32, ' ')
} else {
g.mvaddstr(16, 33, vowelstr(killer))
}
g.mvaddstr(14, center(g.Whoami), g.Whoami)
au := fmt.Sprintf("%d Au", p.Purse)
g.mvaddstr(15, center(au), au)
g.mvaddstr(18, 26, fmt.Sprintf("%4d", year))
}
g.mvaddstr(NumLines-1, 0, "[Press return to continue]")
g.refresh()
flags := 0
if g.HasAmulet {
flags = 3
}
g.score(p.Purse, flags, monst)
g.waitFor('\n')
g.myExit(0)
}
// center returns the column to center the given string on the tombstone
// (rip.c center).
func center(str string) int {
return 28 - (len(str)+1)/2
}
// totalWinner is the code for a winner (rip.c total_winner).
func (g *RogueGame) totalWinner() {
p := &g.Player
g.clear()
g.standout()
banner := []string{
" ",
" @ @ @ @ @ @@@ @ @ ",
" @ @ @@ @@ @ @ @ @ ",
" @ @ @@@ @ @ @ @ @ @@@ @@@@ @@@ @ @@@ @ ",
" @@@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ",
" @ @ @ @ @ @ @ @@@@ @ @ @@@@@ @ @ @ ",
" @ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ ",
" @@@ @@@ @@ @ @ @ @@@@ @@@@ @@@ @@@ @@ @ ",
" ",
" Congratulations, you have made it to the light of day! ",
}
for i, line := range banner {
g.scr.Std.MvAddStr(i, 0, line)
}
g.standend()
g.scr.Std.MvAddStr(10, 0, "You have joined the elite ranks of those who have escaped the")
g.scr.Std.MvAddStr(11, 0, "Dungeons of Doom alive. You journey home and sell all your loot at")
g.scr.Std.MvAddStr(12, 0, "a great profit and are admitted to the Fighters' Guild.")
g.mvaddstr(NumLines-1, 0, "--Press space to continue--")
g.refresh()
g.waitFor(' ')
g.clear()
g.mvaddstr(0, 0, " Worth Item")
g.move(1, 0)
oldpurse := p.Purse
line := 1
for _, obj := range p.Pack {
worth := 0
it := &g.Items
switch obj.Kind {
case KindFood:
worth = 2 * obj.Count
case KindWeapon:
worth = it.Weapons[obj.Which].Worth
worth *= 3*(obj.HPlus+obj.DPlus) + obj.Count
obj.Flags.Set(Known)
case KindArmor:
worth = it.Armors[obj.Which].Worth
worth += (9 - obj.ArmorClass) * 100
worth += 10 * (aClass[obj.Which] - obj.ArmorClass)
obj.Flags.Set(Known)
case KindScroll:
op := &it.Scrolls[obj.Which]
worth = op.Worth * obj.Count
if !op.Know {
worth /= 2
}
op.Know = true
case KindPotion:
op := &it.Potions[obj.Which]
worth = op.Worth * obj.Count
if !op.Know {
worth /= 2
}
op.Know = true
case KindRing:
op := &it.Rings[obj.Which]
worth = op.Worth
if obj.RingKind() == RingAddStrength || obj.RingKind() == RingIncreaseDamage ||
obj.RingKind() == RingProtection || obj.RingKind() == RingDexterity {
if obj.Bonus > 0 {
worth += obj.Bonus * 100
} else {
worth = 10
}
}
if !obj.Flags.Has(Known) {
worth /= 2
}
obj.Flags.Set(Known)
op.Know = true
case KindWand:
op := &it.Sticks[obj.Which]
worth = op.Worth
worth += 20 * obj.Charges
if !obj.Flags.Has(Known) {
worth /= 2
}
obj.Flags.Set(Known)
op.Know = true
case KindAmulet:
worth = 1000
}
if worth < 0 {
worth = 0
}
g.scr.Std.MvPrintw(line, 0, "%c) %5d %s", obj.PackCh, worth,
g.invName(obj, false))
line++
p.Purse += worth
}
g.scr.Std.MvPrintw(line, 0, " %5d Gold Pieces ", oldpurse)
g.refresh()
g.score(p.Purse, 2, ' ')
g.myExit(0)
}
// killnameTable is the rip.c nlist[]: special death causes.
var killnameTable = []helpEntry{
{'a', "arrow", true},
{'b', "bolt", true},
{'d', "dart", true},
{'h', "hypothermia", false},
{'s', "starvation", false},
}
// killname converts a code to a monster name (rip.c killname).
func (g *RogueGame) killname(monst byte, doart bool) string {
var sp string
var article bool
if isUpper(monst) {
sp = g.Monsters[monst-'A'].Name
article = true
} else {
sp = "Wally the Wonder Badger"
article = false
for _, hp := range killnameTable {
if hp.Ch == monst {
sp = hp.Desc
article = hp.Print
break
}
}
}
if doart && article {
return "a" + vowelstr(sp) + " " + sp
}
return sp
}
// DeathDemo implements the -d command line option (main.c): burn some
// random numbers to break patterns, then die a random death.
func (g *RogueGame) DeathDemo() {
defer func() {
if r := recover(); r != nil {
if _, ok := r.(gameEnd); ok {
return
}
panic(r)
}
}()
dnum := g.rnd(100)
for dnum--; dnum > 0; dnum-- {
g.rnd(100)
}
g.Player.Purse = g.rnd(100) + 1
g.Depth = g.rnd(100) + 1
g.death(g.deathMonst())
}
// deathMonst returns a monster appropriate for a random death (rip.c
// death_monst).
func (g *RogueGame) deathMonst() byte {
poss := []byte{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'h', 'd', 's',
' ', // generates the "Wally the Wonder Badger" message
}
return poss[g.rnd(len(poss))]
}