Decompose save.go (refactor step 7)
saveGame splits into askDefaultSave/saveFileName/saveCheckOverwrite/ askOverwrite around a saveAnswer tri-state; snapshot gains destRefFor; applySnapshot gains applyMonsters/applyDests. save.go is complexity-clean. Behavior unchanged.
This commit is contained in:
266
game/save.go
266
game/save.go
@@ -275,43 +275,44 @@ func (g *RogueGame) snapshot() *SaveState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
st.Monsters = append(st.Monsters, sc)
|
st.Monsters = append(st.Monsters, sc)
|
||||||
|
st.Dests = append(st.Dests, g.destRefFor(m, objAt))
|
||||||
ref := destRef{}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case m.Dest == nil:
|
|
||||||
case m.Dest == &p.Pos:
|
|
||||||
ref = destRef{Kind: 1}
|
|
||||||
default:
|
|
||||||
for mi, om := range g.Level.Monsters {
|
|
||||||
if m.Dest == &om.Pos {
|
|
||||||
ref = destRef{Kind: 2, Idx: mi}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ref.Kind == 0 {
|
|
||||||
for _, oo := range g.Level.Objects {
|
|
||||||
if m.Dest == &oo.Pos {
|
|
||||||
ref = destRef{Kind: 3, Idx: objAt[oo]}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ref.Kind == 0 {
|
|
||||||
for ri := range g.Level.Rooms {
|
|
||||||
if m.Dest == &g.Level.Rooms[ri].Gold {
|
|
||||||
ref = destRef{Kind: 4, Idx: ri}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
st.Dests = append(st.Dests, ref)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return st
|
return st
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// destRefFor encodes a monster's chase target as a (kind, index)
|
||||||
|
// reference: the hero, another monster, a level object, or room gold
|
||||||
|
// (state.c rs_write_thing).
|
||||||
|
func (g *RogueGame) destRefFor(m *Monster, objAt map[*Object]int) destRef {
|
||||||
|
switch {
|
||||||
|
case m.Dest == nil:
|
||||||
|
return destRef{}
|
||||||
|
case m.Dest == &g.Player.Pos:
|
||||||
|
return destRef{Kind: 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
for mi, om := range g.Level.Monsters {
|
||||||
|
if m.Dest == &om.Pos {
|
||||||
|
return destRef{Kind: 2, Idx: mi}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, oo := range g.Level.Objects {
|
||||||
|
if m.Dest == &oo.Pos {
|
||||||
|
return destRef{Kind: 3, Idx: objAt[oo]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ri := range g.Level.Rooms {
|
||||||
|
if m.Dest == &g.Level.Rooms[ri].Gold {
|
||||||
|
return destRef{Kind: 4, Idx: ri}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return destRef{}
|
||||||
|
}
|
||||||
|
|
||||||
// applySnapshot rebuilds live game state from a snapshot.
|
// applySnapshot rebuilds live game state from a snapshot.
|
||||||
func (g *RogueGame) applySnapshot(st *SaveState) {
|
func (g *RogueGame) applySnapshot(st *SaveState) {
|
||||||
p := &g.Player
|
p := &g.Player
|
||||||
@@ -412,7 +413,15 @@ func (g *RogueGame) applySnapshot(st *SaveState) {
|
|||||||
p.MaxStats = sp.MaxStats
|
p.MaxStats = sp.MaxStats
|
||||||
p.VfHit = sp.VfHit
|
p.VfHit = sp.VfHit
|
||||||
|
|
||||||
// monsters, their map index, and their chase targets
|
g.applyMonsters(st)
|
||||||
|
g.applyDests(st)
|
||||||
|
|
||||||
|
g.scr.Std.SetContents(st.Screen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyMonsters rebuilds the monster list and its map index from a
|
||||||
|
// snapshot (the monster half of applySnapshot).
|
||||||
|
func (g *RogueGame) applyMonsters(st *SaveState) {
|
||||||
g.Level.Monsters = nil
|
g.Level.Monsters = nil
|
||||||
for i := range st.Monsters {
|
for i := range st.Monsters {
|
||||||
sc := &st.Monsters[i]
|
sc := &st.Monsters[i]
|
||||||
@@ -430,13 +439,17 @@ func (g *RogueGame) applySnapshot(st *SaveState) {
|
|||||||
g.Level.Monsters = append(g.Level.Monsters, m)
|
g.Level.Monsters = append(g.Level.Monsters, m)
|
||||||
g.Level.SetMonsterAt(m.Pos.Y, m.Pos.X, m)
|
g.Level.SetMonsterAt(m.Pos.Y, m.Pos.X, m)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyDests re-aims the monsters' chase targets from their (kind,
|
||||||
|
// index) references (the fixup half of applySnapshot).
|
||||||
|
func (g *RogueGame) applyDests(st *SaveState) {
|
||||||
for i, ref := range st.Dests {
|
for i, ref := range st.Dests {
|
||||||
m := g.Level.Monsters[i]
|
m := g.Level.Monsters[i]
|
||||||
|
|
||||||
switch ref.Kind {
|
switch ref.Kind {
|
||||||
case 1:
|
case 1:
|
||||||
m.Dest = &p.Pos
|
m.Dest = &g.Player.Pos
|
||||||
case 2:
|
case 2:
|
||||||
m.Dest = &g.Level.Monsters[ref.Idx].Pos
|
m.Dest = &g.Level.Monsters[ref.Idx].Pos
|
||||||
case 3:
|
case 3:
|
||||||
@@ -445,13 +458,20 @@ func (g *RogueGame) applySnapshot(st *SaveState) {
|
|||||||
m.Dest = &g.Level.Rooms[ref.Idx].Gold
|
m.Dest = &g.Level.Rooms[ref.Idx].Gold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g.scr.Std.SetContents(st.Screen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// saveAnswer is a yes/no/escape prompt result in the save-game flow.
|
||||||
|
type saveAnswer int
|
||||||
|
|
||||||
|
// The saveGame prompt outcomes.
|
||||||
|
const (
|
||||||
|
saveYes saveAnswer = iota
|
||||||
|
saveNo
|
||||||
|
saveAbort
|
||||||
|
)
|
||||||
|
|
||||||
// saveGame implements the "save game" command (save.c save_game). The
|
// saveGame implements the "save game" command (save.c save_game). The
|
||||||
// labeled prompt loop and the useDefault flag stand in for the C
|
// labeled prompt loop stands in for the C goto over/gotfile flow.
|
||||||
// goto over/gotfile flow.
|
|
||||||
func (g *RogueGame) saveGame() {
|
func (g *RogueGame) saveGame() {
|
||||||
g.Msgs.Mpos = 0
|
g.Msgs.Mpos = 0
|
||||||
|
|
||||||
@@ -460,84 +480,34 @@ prompt:
|
|||||||
useDefault := false
|
useDefault := false
|
||||||
|
|
||||||
if g.FileName != "" {
|
if g.FileName != "" {
|
||||||
var c byte
|
a := g.askDefaultSave()
|
||||||
|
if a == saveAbort {
|
||||||
for {
|
return
|
||||||
g.msg("save file (%s)? ", g.FileName)
|
|
||||||
c = g.readchar()
|
|
||||||
|
|
||||||
g.Msgs.Mpos = 0
|
|
||||||
if c == Escape {
|
|
||||||
g.msg("")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if c == 'n' || c == 'N' || c == 'y' || c == 'Y' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
g.msg("please answer Y or N")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c == 'y' || c == 'Y' {
|
useDefault = a == saveYes
|
||||||
g.addstr("Yes\n")
|
|
||||||
g.refresh()
|
|
||||||
|
|
||||||
useDefault = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var buf string
|
buf, ok := g.saveFileName(useDefault)
|
||||||
if useDefault {
|
if !ok {
|
||||||
buf = g.FileName
|
return
|
||||||
useDefault = false
|
|
||||||
} else {
|
|
||||||
g.Msgs.Mpos = 0
|
|
||||||
g.msg("file name: ")
|
|
||||||
|
|
||||||
if g.getStr(&buf, g.scr.Std) == Quit {
|
|
||||||
g.msg("")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
g.Msgs.Mpos = 0
|
|
||||||
}
|
}
|
||||||
// test to see if the file exists
|
|
||||||
_, statErr := os.Stat(buf)
|
|
||||||
if statErr == nil {
|
|
||||||
for {
|
|
||||||
g.msg("File exists. Do you wish to overwrite it?")
|
|
||||||
g.Msgs.Mpos = 0
|
|
||||||
|
|
||||||
c := g.readchar()
|
useDefault = false
|
||||||
if c == Escape {
|
|
||||||
g.msg("")
|
|
||||||
|
|
||||||
return
|
a := g.saveCheckOverwrite(buf)
|
||||||
}
|
if a == saveAbort {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if c == 'y' || c == 'Y' {
|
if a == saveNo {
|
||||||
break
|
continue prompt // the C goto over: start again
|
||||||
}
|
|
||||||
|
|
||||||
if c == 'n' || c == 'N' {
|
|
||||||
continue prompt // the C goto over: start again
|
|
||||||
}
|
|
||||||
|
|
||||||
g.msg("Please answer Y or N")
|
|
||||||
}
|
|
||||||
|
|
||||||
g.msg("file name: %s", buf)
|
|
||||||
_ = os.Remove(g.FileName) // best effort, as in C (md_unlink)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g.FileName = buf
|
g.FileName = buf
|
||||||
|
|
||||||
err := g.saveFile(g.FileName)
|
if err := g.saveFile(g.FileName); err != nil {
|
||||||
if err != nil {
|
|
||||||
g.msg("%s", err.Error())
|
g.msg("%s", err.Error())
|
||||||
|
|
||||||
continue
|
continue
|
||||||
@@ -550,6 +520,96 @@ prompt:
|
|||||||
g.myExit()
|
g.myExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// askDefaultSave asks whether to save to the current file name (save.c
|
||||||
|
// save_game).
|
||||||
|
func (g *RogueGame) askDefaultSave() saveAnswer {
|
||||||
|
for {
|
||||||
|
g.msg("save file (%s)? ", g.FileName)
|
||||||
|
c := g.readchar()
|
||||||
|
|
||||||
|
g.Msgs.Mpos = 0
|
||||||
|
|
||||||
|
switch c {
|
||||||
|
case Escape:
|
||||||
|
g.msg("")
|
||||||
|
|
||||||
|
return saveAbort
|
||||||
|
case 'y', 'Y':
|
||||||
|
g.addstr("Yes\n")
|
||||||
|
g.refresh()
|
||||||
|
|
||||||
|
return saveYes
|
||||||
|
case 'n', 'N':
|
||||||
|
return saveNo
|
||||||
|
}
|
||||||
|
|
||||||
|
g.msg("please answer Y or N")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveFileName picks the save path: the default, or a prompted one; ok
|
||||||
|
// is false when the player quit the prompt (save.c save_game).
|
||||||
|
func (g *RogueGame) saveFileName(useDefault bool) (string, bool) {
|
||||||
|
if useDefault {
|
||||||
|
return g.FileName, true
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Msgs.Mpos = 0
|
||||||
|
g.msg("file name: ")
|
||||||
|
|
||||||
|
buf := ""
|
||||||
|
if g.getStr(&buf, g.scr.Std) == Quit {
|
||||||
|
g.msg("")
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Msgs.Mpos = 0
|
||||||
|
|
||||||
|
return buf, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveCheckOverwrite guards an existing file: saveNo restarts the whole
|
||||||
|
// prompt, saveAbort quits (save.c save_game).
|
||||||
|
func (g *RogueGame) saveCheckOverwrite(buf string) saveAnswer {
|
||||||
|
// test to see if the file exists
|
||||||
|
if _, statErr := os.Stat(buf); statErr != nil {
|
||||||
|
return saveYes
|
||||||
|
}
|
||||||
|
|
||||||
|
answer := g.askOverwrite()
|
||||||
|
if answer != saveYes {
|
||||||
|
return answer
|
||||||
|
}
|
||||||
|
|
||||||
|
g.msg("file name: %s", buf)
|
||||||
|
_ = os.Remove(g.FileName) // best effort, as in C (md_unlink)
|
||||||
|
|
||||||
|
return saveYes
|
||||||
|
}
|
||||||
|
|
||||||
|
// askOverwrite asks whether to overwrite the existing file (save.c
|
||||||
|
// save_game).
|
||||||
|
func (g *RogueGame) askOverwrite() saveAnswer {
|
||||||
|
for {
|
||||||
|
g.msg("File exists. Do you wish to overwrite it?")
|
||||||
|
g.Msgs.Mpos = 0
|
||||||
|
|
||||||
|
switch g.readchar() {
|
||||||
|
case Escape:
|
||||||
|
g.msg("")
|
||||||
|
|
||||||
|
return saveAbort
|
||||||
|
case 'y', 'Y':
|
||||||
|
return saveYes
|
||||||
|
case 'n', 'N':
|
||||||
|
return saveNo
|
||||||
|
}
|
||||||
|
|
||||||
|
g.msg("Please answer Y or N")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// saveFile writes the saved game (save.c save_file). A failed write means
|
// saveFile writes the saved game (save.c save_file). A failed write means
|
||||||
// a corrupt save, so the file is removed before reporting the error.
|
// a corrupt save, so the file is removed before reporting the error.
|
||||||
func (g *RogueGame) saveFile(path string) error {
|
func (g *RogueGame) saveFile(path string) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user