- 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.
230 lines
6.1 KiB
Go
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))]
|
|
}
|