package main
import (
"bufio"
"fmt"
"os"
"strings"
)
type choices struct {
cmd string
description string
nextNode *storyNode
nextChoice *choices
}
type storyNode struct {
text string
choices *choices
}
func (node *storyNode) addChoice(cmd string, description string, nextNode *storyNode) {
choice := &choices{
cmd, description, nextNode, nil}
if node.choices == nil {
node.choices = choice
} else {
currentChoice := node.choices
for currentChoice.nextChoice != nil {
currentChoice = currentChoice.nextChoice
}
currentChoice.nextChoice = choice
}
}
func (node *storyNode) render() {
fmt.Println(node.text)
currentChoice := node.choices
for currentChoice != nil {
fmt.Println(currentChoice.cmd, ":", currentChoice.description)
currentChoice = currentChoice.nextChoice
}
}
func (node *storyNode) executeCmd(cmd string) *storyNode {
currentChoice := node.choices
for currentChoice != nil {
if strings.ToLower(currentChoice.cmd) == strings.ToLower(cmd) {
return currentChoice.nextNode
}
currentChoice = currentChoice.nextChoice
}
fmt.Println("Sorry I didn't understand that.")
return node
}
var scanner *bufio.Scanner
func (node *storyNode) play() {
node.render()
if node.choices != nil {
scanner.Scan()
node.executeCmd(scanner.Text()).play()
}
}
func main() {
scanner = bufio.NewScanner(os.Stdin)
start := storyNode{
text: `You are in large chamber,deep underground.You see three passages leading out.A north passage
into darkness.To the south,a passage appears to head upward.The easter appears flat and well traveled.`}
darkRoom := storyNode{
text: `It is pitch black.You cannot see a thing.`}
darkRoomLit := storyNode{
text: `The dark passage is now lit by your lantern. You can continue north or head back south.`}
grue := storyNode{
text: `while stumbling around in the darkness, you are eaten by a grue`}
trap := storyNode{
text: `You head down the well traveled path when suddenly a trap door opens and yo u fall into a pit`}
treasure := storyNode{
text: `You arrive at a small chamber,filled with treasure!`}
start.addChoice("N", "go north", &darkRoom)
start.addChoice("S", "go south", &darkRoom)
start.addChoice("E", "go east", &trap)
darkRoom.addChoice("S", "Try to go back south", &grue)
darkRoom.addChoice("O", "Turn on lantern", &darkRoomLit)
darkRoomLit.addChoice("N","Go North",&treasure)
darkRoomLit.addChoice("S","Go South",&start)
start.play()
fmt.Println()
fmt.Println("The End.")
}
storyNode是一堆离散的故事节点,choice是一组行为链表,
每一个离散的故事节点会在尾部衔接一堆choice行为,每个choice又会通过输入连接到不同的故事节点。很适合理解go关于图的数据结构。
视频原连接 go游戏编程系列 油管上找到的。
https://www.youtube.com/watch?v=u87bfwSGtrg&list=PLDZujg-VgQlZUy1iCqBbe5faZLMkA3g2x&index=6
画了上面这张图来解释一下,这是一个关于图的文字游戏,方块是故事节点,圈是行为链表。然后他们的关系就是图中画的那样,理解代码最重要,我写的英文和字以及图很丑不重要,学习到东西才是重点。
下面贴出数组实现的代码,很简单就是把行为链表换成数组就行了。
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
type choices struct {
cmd string
description string
nextNode *storyNode
}
type storyNode struct {
text string
choices []*choices
}
func (node *storyNode) addChoice(cmd string, description string, nextNode *storyNode) {
choice:=&choices{
cmd,description,nextNode}
node.choices=append(node.choices,choice)
}
func (node *storyNode) render() {
fmt.Println(node.text)
if node.choices!=nil{
for _,choice:=range node.choices{
fmt.Println(choice.cmd,choice.description)
}
}
}
func (node *storyNode) executeCmd(cmd string) *storyNode {
for _,choice:=range node.choices{
if strings.ToLower(choice.cmd)==strings.ToLower(cmd){
return choice.nextNode
}
}
fmt.Println("sorry ,i didn't understand that.")
return node
}
var scanner *bufio.Scanner
func (node *storyNode) play() {
node.render()
if node.choices != nil {
scanner.Scan()
node.executeCmd(scanner.Text()).play()
}
}
func main() {
scanner = bufio.NewScanner(os.Stdin)
start := storyNode{
text: `You are in large chamber,deep underground.You see three passages leading out.A north passage
into darkness.To the south,a passage appears to head upward.The easter appears flat and well traveled.`}
darkRoom := storyNode{
text: `It is pitch black.You cannot see a thing.`}
darkRoomLit := storyNode{
text: `The dark passage is now lit by your lantern. You can continue north or head back south.`}
grue := storyNode{
text: `while stumbling around in the darkness, you are eaten by a grue`}
trap := storyNode{
text: `You head down the well traveled path when suddenly a trap door opens and yo u fall into a pit`}
treasure := storyNode{
text: `You arrive at a small chamber,filled with treasure!`}
start.addChoice("N", "go north", &darkRoom)
start.addChoice("S", "go south", &darkRoom)
start.addChoice("E", "go east", &trap)
darkRoom.addChoice("S", "Try to go back south", &grue)
darkRoom.addChoice("O", "Turn on lantern", &darkRoomLit)
darkRoomLit.addChoice("N", "Go North", &treasure)
darkRoomLit.addChoice("S", "Go South", &start)
start.play()
fmt.Println()
fmt.Println("The End.")
}
下图是优化后的版本
方块依旧是故事节点,数组是行为列表,当输入遍历的角色来到没有行为列表的故事节点后游戏终止。
其实如果有精力的话,下面是油管博主的额外建议可拓展
//homework
//add npc non player characters talk to them,fight
//npc move around the graph
//items can picked up or placed down
//accept natural language as input
//verb noun
//north south east west
//build you own with this
//big state machine
可以添加故事节点支持玩家对话,npc对话,以及npc移动,包括可以打怪。
也可以做自然语言输入来进行交互,当然如果有足够精力的话可以搞一些当输入对话中含有某些动词,名词时候触发新的行为,故事,boss等。
加油学习,GO万岁