Skip to content

Commit

Permalink
replace runner.JumpTo(node) by runner.RestoreAt(snapshot)
Browse files Browse the repository at this point in the history
  • Loading branch information
RemiEven committed Feb 4, 2024
1 parent e293537 commit 917c158
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 48 deletions.
57 changes: 39 additions & 18 deletions runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@ import (
// It keeps track of the current state of the dialogue (variables, current and visited steps).
// It orchestrates the call of Commands and Functions.
type DialogueRunner struct {
dialogue *tree.Dialogue
statementsToRun container.Stack[*statementQueue]
lastStatement *tree.Statement
variableStorer variable.Storer
functionStorer *functionStorer
commandStorer *commandStorer
commandErrChan <-chan error
lineParser markup.LineParser
currentNode string
visitedNodes map[string]int
dialogue *tree.Dialogue
statementsToRun container.Stack[*statementQueue]
lastStatement *tree.Statement
variableStorer variable.Storer
functionStorer *functionStorer
commandStorer *commandStorer
commandErrChan <-chan error
lineParser markup.LineParser
currentNode string
visitedNodes map[string]int
variableSnapshot map[string]variable.Value
}

// DialogueElement represents a step of a dialogue as it is presented in a game.
Expand Down Expand Up @@ -303,6 +304,7 @@ func (dr *DialogueRunner) executeJumpStatement(statement *tree.JumpStatement) er
return fmt.Errorf("node [%s] not found in dialogue", *value.String)
} else {
dr.incrementNodeTrackingIfAllowed()
dr.variableSnapshot = dr.variableStorer.GetValues()
dr.statementsToRun.Clear()
dr.statementsToRun.Push(&statementQueue{statements: node.Statements})
dr.currentNode = node.Title()
Expand Down Expand Up @@ -394,13 +396,27 @@ func (dr *DialogueRunner) executeDeclareStatement(statement *tree.DeclareStateme
})
}

// JumpTo moves the dialogue to the beginning of the given node.
func (dr *DialogueRunner) JumpTo(nodeTitle string) error {
node, ok := dr.dialogue.FindNode(nodeTitle)
// RestoreAt uses a snapshot to restore a dialogue runner to a former state.
func (dr *DialogueRunner) RestoreAt(snapshot *Snapshot) error {
node, ok := dr.dialogue.FindNode(snapshot.CurrentNode)
if !ok {
return fmt.Errorf("dialogue does not contain a node with title [%s]", nodeTitle)
return fmt.Errorf("dialogue does not contain a node with title [%s]", snapshot.CurrentNode)
}
dr.incrementNodeTrackingIfAllowed()

dr.visitedNodes = snapshot.VisitedNodes
dr.variableStorer.Clear()
for variable, value := range snapshot.Variables {
if value.Boolean != nil {
dr.variableStorer.SetBooleanValue(variable, *value.Boolean)
}
if value.Number != nil {
dr.variableStorer.SetNumberValue(variable, *value.Number)
}
if value.String != nil {
dr.variableStorer.SetStringValue(variable, *value.String)
}
}

dr.statementsToRun.Clear()
dr.statementsToRun.Push(&statementQueue{statements: node.Statements})
dr.currentNode = node.Title()
Expand Down Expand Up @@ -429,9 +445,14 @@ func (dr *DialogueRunner) ConvertAndAddCommand(commandID string, command any) er
return dr.commandStorer.convertAndAddCommand(commandID, command)
}

// CurrentNode returns the name of the node the runner is currently visiting.
func (dr *DialogueRunner) CurrentNode() string {
return dr.currentNode
// Snapshot returns the state of the dialogue runner as of the last time a node was entered.
// It can then be used to later restore the state of the dialogue runner.
func (dr *DialogueRunner) Snapshot() *Snapshot {
return &Snapshot{
Variables: dr.variableSnapshot,
CurrentNode: dr.currentNode,
VisitedNodes: dr.visitedNodes,
}
}

type statementQueue struct {
Expand Down
62 changes: 33 additions & 29 deletions runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,18 +157,22 @@ func TestRunnerPlan(t *testing.T) {
}
}

func TestRunnerJumpTo(t *testing.T) {
func TestRunnerRestoreAt(t *testing.T) {
script := `
title: flavors
title: node_1
---
Do you like chocolate?
Yes!
Nice! Here, get some icecream!
Do you like chocolate? Here, get some icecream!
<<declare $flavor to "chocolate">>
Thanks!
<<jump node_2>>
===
title: bakery
title: node_2
---
How much for a baguette?
About 1€!
You like {$flavor}, right? Here's more icecream!
Actually, I'd like raspberries this time!
<<declare $flavor to "raspberries">>
Oh ok, {$flavor} it is then!
Thanks!
===`

dr, err := ysgo.NewDialogueRunner(nil, "", strings.NewReader(script))
Expand All @@ -177,29 +181,29 @@ About 1€!
return
}

dialogueElement, err := dr.Next(0)
if err != nil {
t.Errorf("failed to get to first dialogue element: %v", err)
return
}
if expected, actual := "Do you like chocolate?", dialogueElement.Line.Text; expected != actual {
t.Errorf("unexpected line text: wanted [%s], got [%s]", expected, actual)
return
assertNextLine := func(expectedLine string) {
dialogueElement, err := dr.Next(0)
if err != nil {
t.Errorf("failed to get to dialogue element: %v", err)
return
}
if expected, actual := expectedLine, dialogueElement.Line.Text; expected != actual {
t.Errorf("unexpected line text: wanted [%s], got [%s]", expected, actual)
return
}
}

err = dr.JumpTo("bakery")
if err != nil {
t.Errorf("jump to bakery node failed: %v", err)
return
}
assertNextLine("Do you like chocolate? Here, get some icecream!")
assertNextLine("Thanks!")
assertNextLine("You like chocolate, right? Here's more icecream!")
assertNextLine("Actually, I'd like raspberries this time!")
assertNextLine("Oh ok, raspberries it is then!")

dialogueElement, err = dr.Next(0)
if err != nil {
t.Errorf("failed to get to first dialogue element: %v", err)
return
}
if expected, actual := "How much for a baguette?", dialogueElement.Line.Text; expected != actual {
t.Errorf("unexpected line text: wanted [%s], got [%s]", expected, actual)
return
snapshot := dr.Snapshot()

if err := dr.RestoreAt(snapshot); err != nil {
t.Errorf("unexpected error when restoring snapshot: %v", err)
}

assertNextLine("You like chocolate, right? Here's more icecream!")
}
13 changes: 13 additions & 0 deletions snapshot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ysgo

import "github.com/remieven/ysgo/variable"

// Snapshot holds data that represents the current state of a dialogue, so it can be restored later
type Snapshot struct {
Variables map[string]variable.Value

CurrentNode string

VisitedNodes map[string]int
}

17 changes: 17 additions & 0 deletions variable/in_memory_storer.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ func (storer *InMemoryStorer) GetValue(variableName string) (*Value, bool) {
return nil, false
}

// GetValues returns a map of all stored values.
func (storer *InMemoryStorer) GetValues() map[string]Value {
values := make(map[string]Value, len(storer.booleans)+len(storer.numbers)+len(storer.strings))

for variable, value := range storer.booleans {
values[variable] = *NewBoolean(value)
}
for variable, value := range storer.numbers {
values[variable] = *NewNumber(value)
}
for variable, value := range storer.strings {
values[variable] = *NewString(value)
}

return values
}

// SetNumberValue stores a number.
func (storer *InMemoryStorer) SetNumberValue(variableName string, value float64) {
storer.numbers[variableName] = value
Expand Down
34 changes: 33 additions & 1 deletion variable/in_memory_storer_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package variable

import "testing"
import (
"testing"

"github.com/remieven/ysgo/internal/testutils"
)

func TestInMemoryStorer(t *testing.T) {
storer := NewInMemoryStorer()
Expand Down Expand Up @@ -145,4 +149,32 @@ func TestInMemoryStorer(t *testing.T) {
return
}
})

t.Run("Export of empty store", func(t *testing.T) {
storer := NewInMemoryStorer()

if len(storer.GetValues()) != 0 {
t.Errorf("expected GetValues of empty storer to return 0 values but got %v", len(storer.GetValues()))
}
})

t.Run("Export of filled store", func(t *testing.T) {
storer := NewInMemoryStorer()
storer.SetBooleanValue("bool1", true)
storer.SetBooleanValue("bool2", false)
storer.SetNumberValue("number", 70400)
storer.SetStringValue("string", "some string value")

expected := map[string]Value{
"bool1": *NewBoolean(true),
"bool2": *NewBoolean(false),
"number": *NewNumber(70400),
"string": *NewString("some string value"),
}

if diff := testutils.DeepEqual(storer.GetValues(), expected); diff != "" {
t.Errorf("unexpected result from GetValues: " + diff)
}

})
}
2 changes: 2 additions & 0 deletions variable/storer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package variable
type Retriever interface {
GetValue(variableName string) (*Value, bool)

GetValues() map[string]Value

Contains(variableName string) bool
}

Expand Down

0 comments on commit 917c158

Please sign in to comment.