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)
|
||||
|
||||
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)
|
||||
st.Dests = append(st.Dests, g.destRefFor(m, objAt))
|
||||
}
|
||||
|
||||
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.
|
||||
func (g *RogueGame) applySnapshot(st *SaveState) {
|
||||
p := &g.Player
|
||||
@@ -412,7 +413,15 @@ func (g *RogueGame) applySnapshot(st *SaveState) {
|
||||
p.MaxStats = sp.MaxStats
|
||||
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
|
||||
for i := range st.Monsters {
|
||||
sc := &st.Monsters[i]
|
||||
@@ -430,13 +439,17 @@ func (g *RogueGame) applySnapshot(st *SaveState) {
|
||||
g.Level.Monsters = append(g.Level.Monsters, 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 {
|
||||
m := g.Level.Monsters[i]
|
||||
|
||||
switch ref.Kind {
|
||||
case 1:
|
||||
m.Dest = &p.Pos
|
||||
m.Dest = &g.Player.Pos
|
||||
case 2:
|
||||
m.Dest = &g.Level.Monsters[ref.Idx].Pos
|
||||
case 3:
|
||||
@@ -445,13 +458,20 @@ func (g *RogueGame) applySnapshot(st *SaveState) {
|
||||
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
|
||||
// labeled prompt loop and the useDefault flag stand in for the C
|
||||
// goto over/gotfile flow.
|
||||
// labeled prompt loop stands in for the C goto over/gotfile flow.
|
||||
func (g *RogueGame) saveGame() {
|
||||
g.Msgs.Mpos = 0
|
||||
|
||||
@@ -460,84 +480,34 @@ prompt:
|
||||
useDefault := false
|
||||
|
||||
if g.FileName != "" {
|
||||
var c byte
|
||||
|
||||
for {
|
||||
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")
|
||||
a := g.askDefaultSave()
|
||||
if a == saveAbort {
|
||||
return
|
||||
}
|
||||
|
||||
if c == 'y' || c == 'Y' {
|
||||
g.addstr("Yes\n")
|
||||
g.refresh()
|
||||
|
||||
useDefault = true
|
||||
}
|
||||
useDefault = a == saveYes
|
||||
}
|
||||
|
||||
for {
|
||||
var buf string
|
||||
if useDefault {
|
||||
buf = g.FileName
|
||||
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
|
||||
buf, ok := g.saveFileName(useDefault)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// 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()
|
||||
if c == Escape {
|
||||
g.msg("")
|
||||
useDefault = false
|
||||
|
||||
return
|
||||
}
|
||||
a := g.saveCheckOverwrite(buf)
|
||||
if a == saveAbort {
|
||||
return
|
||||
}
|
||||
|
||||
if c == 'y' || c == 'Y' {
|
||||
break
|
||||
}
|
||||
|
||||
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)
|
||||
if a == saveNo {
|
||||
continue prompt // the C goto over: start again
|
||||
}
|
||||
|
||||
g.FileName = buf
|
||||
|
||||
err := g.saveFile(g.FileName)
|
||||
if err != nil {
|
||||
if err := g.saveFile(g.FileName); err != nil {
|
||||
g.msg("%s", err.Error())
|
||||
|
||||
continue
|
||||
@@ -550,6 +520,96 @@ prompt:
|
||||
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
|
||||
// a corrupt save, so the file is removed before reporting the error.
|
||||
func (g *RogueGame) saveFile(path string) error {
|
||||
|
||||
Reference in New Issue
Block a user