Files
rgoue/game/rip.go
sneak 3c5add87cd go: port combat, chase driver, traps, zapping, death and scores
- fight.c in full: fight/attack with all eight special monster attacks
  (aquator rust, ice freeze, rattlesnake poison, wraith/vampire drains,
  flytrap hold, leprechaun/nymph theft), swing, roll_em dice parsing
  (with a C-semantics atoi — Go's strconv rejects '1x4/...'), hit/miss
  message variants, killed, remove_mon
- chase.c completed: runners, move_monst, relocate, do_chase with
  dragon breath, chase target selection (C's dead oroom comparison in
  relocate is preserved as-is)
- move.c in full: do_move with passgo turning, all eight traps,
  rndmove, rust_armor
- sticks.c completed: do_zap (all 14 sticks), drain, fire_bolt with
  wall bounces; weapons.c completed: missile/do_motion/fall/wield
- rip.c: death tombstone, total_winner, killname; C exit() becomes a
  gameEnd panic that Run will recover
- score.go: top-ten scoreboard as a gob file with lock-file protocol
  replacing the XOR-encrypted C format
- wizard.c: whatis/set_know/teleport; daemons.c stomach
- monster bestiary moved to per-game state (C mutates the flytrap
  damage string during play)

Tests: combat math, kill/removal bookkeeping, monster chase pursuit,
death unwinding, scripted-input headless terminal.
2026-07-06 19:23:45 +02:00

230 lines
6.1 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.Type {
case Food:
worth = 2 * obj.Count
case Weapon:
worth = it.Weapons[obj.Which].Worth
worth *= 3*(obj.HPlus+obj.DPlus) + obj.Count
obj.Flags.Set(IsKnow)
case Armor:
worth = it.Armors[obj.Which].Worth
worth += (9 - obj.Arm) * 100
worth += 10 * (aClass[obj.Which] - obj.Arm)
obj.Flags.Set(IsKnow)
case Scroll:
op := &it.Scrolls[obj.Which]
worth = op.Worth * obj.Count
if !op.Know {
worth /= 2
}
op.Know = true
case Potion:
op := &it.Potions[obj.Which]
worth = op.Worth * obj.Count
if !op.Know {
worth /= 2
}
op.Know = true
case Ring:
op := &it.Rings[obj.Which]
worth = op.Worth
if obj.Which == RAddStr || obj.Which == RAddDam ||
obj.Which == RProtect || obj.Which == RAddHit {
if obj.Arm > 0 {
worth += obj.Arm * 100
} else {
worth = 10
}
}
if !obj.Flags.Has(IsKnow) {
worth /= 2
}
obj.Flags.Set(IsKnow)
op.Know = true
case Stick:
op := &it.Sticks[obj.Which]
worth = op.Worth
worth += 20 * obj.Charges()
if !obj.Flags.Has(IsKnow) {
worth /= 2
}
obj.Flags.Set(IsKnow)
op.Know = true
case Amulet:
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
}
// 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))]
}