在我的游戏中添加一个保存系统

时间:2014-07-07 12:16:01

标签: ruby save yaml

我一直在使用Ruby中的文本冒险游戏Conquest,我对它的发展方向感到非常满意,但我想尽快添加保存游戏的功能。我以前只用YAML做这样的事情,但我想这是一个更好的方法。

这个游戏也比我制作的其他游戏复杂一点,所以我想要一个非常简单的方法来管理保存系统。

我想在我的游戏中为我的自定义类的每个实例用YAML做这样的事情:

--- !ruby/object:Player
items: ...

但我认为这可能是一个不好的方法。我会告诉你一些我的文件,我很想知道你认为最好的方法。并且记住它正在进行中,所以像任务这样的事情并没有完全发挥作用,但你会明白这一点。

LIB / player.rb:

class Player

    attr_accessor :items

    def initialize
        @items = {}
        @quests = QuestList.quests
    end

    def pickup(key, item)
        @items[key.to_sym] = item
        if key == "scroll"
            @quests[:mordor].start
        end
    end

    def inventory
        @items.values.each { |item|
            a_or_an = %w[a e i o u].include?(item.name[0]) \
                ? "an " : "a " 
            a_or_an = "" if item.name[-1] == "s"
            puts "#{a_or_an}#{item.name.downcase}"
        }
    end

end

LIB / item.rb的:

class Item

    attr_reader :name, :description, :hidden, :can_pickup

    def initialize(name, description, options = {})
        @name = name
        @description = description
        @options = options
        @hidden = options[:hidden] || false
        @can_pickup = options[:hidden] || true
        add_info
    end

    def add_info
    end

end

class Prop < Item
    def add_info
        @hidden = true
        @can_pickup = false
    end
end

# sends you to a new room, usally something that
# has more functionality than just a room
class Transporter < Prop

    attr_accessor :goto

    def add_info
        @hidden = true
        @can_pickup = false
        @goto = options[:goto]
    end
end

# SUBCLASSES BELOW: (only subclass when you have a good reason)

# can be eaten,
# use item.is_a? Food
class Food < Item
end

class Tree < Prop

    def climb
        if @options[:can_climb]
            puts @description
        else
            puts "You start climbing the tree, but you don't get far before you fall down."
        end
    end
end

lib / quest_list.rb(这可能会被保存系统取代):

module QuestList

    QUESTS = {
        # this need a better name ⬇️ 
        main:   Quest.new("The main mission", []),
        mordor: Quest.new("Onward to Mordor", [])
    }

    def self.quests
        QUESTS
    end

end

lib / room_list.rb(这也可能被保存系统取代):

module RoomList

    ROOMS = {
        castle_main:
            Room.new("Main room", "This is the main room of the castle. It needs a better description\nand name. Theres a hallway south.", 
                paths: { s: :hallway}
                ),
        hallway:
            Room.new("Hallway", "This castle has a long hallway. There is a door to the west and\na large room north.",
                paths: { n: :castle_main, s: :castle, w: :dinning_hall }
                ),
            dinning_hall:
                Room.new("Dinning hall", "The dinning hall. There is a door to the east.",
                    paths: { e: :hallway }
                    ),
        castle:
            Room.new("Castle", "You are in the castle. There's a long hallway to the north, and\nthe courtyard is to the south.",
                paths: { n: :hallway, s: :courtyard }
                ),
        courtyard:
            Room.new("Castle courtyard", "You are at the castle courtyard. There's a nice fountain in the center.\nThe castle entrance is north. There is a forest south.",
                paths: { n: :castle, s: :forest },
                items: {
                    # this peach is useless, it'll confuse people 
                    # a peach: 
                    peach: Food.new("Peach", "A delicious peach")
                    }),
        forest:
            Room.new("Large forest", "This forest is very dense. There is a nice courtyard north.\nThe forest continues west and south.",
                paths: { n: :courtyard, s: :forest_1, w: :forest__1 }
                ),
    forest__1:
        Room.new("Large forest", "This forest is very nice. You can go north, east and west into\nsome more forest.", 
            paths: { n: :forest__2, e: :forest, w: :sticks }
            ),
sticks:
    Room.new("Large forest", "This forest is getting boring, but hey, who knows what you'll find here!\nYou can go east.",
        paths: { e: :forest__1 },
        items: {
            sticks: Item.new("Sticks", "Just a couple of sticks. They like they are cedar wood.")
            }),
    forest__2:
        Room.new("Large forest",  "You are in a large forest. There looks like theres a grand building over\neast, but you can't quite get to it from here. You can go south.",
            paths: { s: :forest__1 }
            ),
        forest_1:
            Room.new("Large forest", "There is a large, magnificent tree east. The forest continues\nnorth and south.",
                paths: { n: :forest, e: :banyan_tree, s: :forest_2 }
                ),
            banyan_tree:
                # http://en.wikipedia.org/wiki/Banyan
                Room.new("Large banyan tree", "There is a large banyan tree, with many twists and roots going up the tree.\nYou can go west.",
                    paths: { w: :forest_1 },
                    items: {
                        tree: Tree.new("Banyan", "You climb up the top of the tree, and see lots of trees and a\ncastle somewhere around north. It looks like there is a small\nvillage some where south east. You climb back down.", { # 
                            can_climb: true
                            })}),
        forest_2:
            Room.new("Large forest", "Just some more forest. The forest continues north and south.",
                paths: { n: :forest_1, s: :forest_3 }
                ),
        forest_3:
            Room.new("Large forest", "Dang, how many trees are in this forest? You can go north, south, and west.",
                paths: { n: :forest_2, s: :forest_4, w: :more_trees }
                ),
    more_trees:
        Room.new("Large forest", "You can go east and west.",
            paths: { e: :forest_3, w: :more_trees_1 }
            ),
more_trees_1:
    Room.new("Large forest", "You can go east and south.", 
        paths: { e: :more_trees, s: :more_trees_2 }
        ),
more_trees_2:
    Room.new("Large forest", "You can go north and south.",
        paths: { n: :more_trees_1, s: :more_trees_3 }
        ),
more_trees_3:
    Room.new("Large forest", "You can go north and east",
        paths: { n: :more_trees_2, e: :path_to_village }
        ),
        path_to_village:
            Room.new("Large forest", "Its hard to see because of all these trees, but you think you see a small\nhut to the east. You can also go back west",
                paths: { e: :village, w: :more_trees_3 }
                ),
            village:
                # add an item or 2 here
                Room.new("Abandon village", "There are a bunch of huts here, some people must have lived here before.\nThere is some more forest down south. You can go back west into the forest.",
                    paths: { w: :path_to_village, s: :forest_by_village },
                    items: { 
                        pickaxe: Item.new("Pickaxe", "Be careful, it looks sharp.")
                        }),
            forest_by_village:
                Room.new("Large forest", "Geez more forest. The village is north, and there is a valley east",
                    paths: { n: :village, e: :valley }
                    ),
                valley:
                    Room.new("Valley", "It's a beautiful valley, with some giganic mountains east, with some\nsnow of the tops. There is a forest to the west",
                        paths: { e: :mountains, w: :forest_by_village }
                        ),
                    mountains:
                        Room.new("Mountains", "There are many tall mountains with snow on the tops. You can go back west.",
                            paths: { u: :mountain, w: :valley },
                            has_mountain: true
                            ),
                        mountain:
                            Room.new("Tall mountain", "This mountain is very steep. You can continue climbing or go back down",
                                paths: { d: :mountains, u: :mountain_1 },

                                # the scroll and Randy should be moved to mountain_3 once it exists
                                items: {
                                    scroll: Item.new("Scroll", "Its some kind of elvish... You can't read it.") },
                                people: {
                                    # Randy will read elvish in the future
                                    randy: Person.new("Randy", "He's just an elf",
                                        race: "Elf",
                                        talk: "I can read elvish. Go figure."
                                        )}),
                        mountain_1:
                            Room.new("Tall mountain", "Climbing this mountain is very tiring. You can continue climbing\nor go back down",
                                paths: { d: :mountain }
                                ),
        forest_4:
            Room.new("Large forest", "There is a lot of trees here. It's very shady in this area.\nThe forest continues north.", 
                paths: { n: :forest_3 }
                )
    }

    def self.room_list
        ROOMS
    end

end

LIB / delegate.rb:

class Delegate

    attr_accessor :current_room

    def initialize
        @rooms = RoomList.room_list
        @player = Player.new
        @current_room = @rooms[:courtyard]
        @help = 0
    end

    def parse(input)
        directions = "up|down|north|east|south|west|u|d|n|e|s|w"
        # input will always be converted to lower case before getting here
        case input
        when /^(?<direction>(#{directions}))$/
            direction = $~[:direction]
            walk(direction)
        when /^(go|walk)( (?<direction>#{directions}|to mordor))?$/
            direction = $~[:direction]
            if direction
                walk(direction)
            else
                puts "#{input.capitalize} where?"
            end
        when /^(get|take|pickup|pick up)( (?<item>[a-z ]+))?$/
            item = $~[:item]
            if item
                pickup(item)
            else
                puts "Please supply an object to #{input}."
            end
        when /^look( (?<item>[a-z]+))?$/
            item = $~[:item]
            item.nil? ? look : inspect(item)
        when /^inspect( (?<item>[a-z]+))?$/
            item = $~[:item]
            if item
                inspect(item)
            else
                puts "Please supply an object to inspect."
            end
        when /^rub sticks( together)?$/
            rub_sticks
        when /^quests?$/
            # this is probably going to be a for statement.  You understand thos more than i do so have at it.
            # this should loop through the list of quests in quests.yml and return the ones that are true

            # correction: it should call .each, for statments are bad practice in ruby
        when /^(i|inv|inventory)$/
            inventory
        when /^climb( (?<tree_name>[a-z]+))?( tree)?$/
            # this regex needs to be cleaned up, just the tree part really
            # nvm, the whole regex sucks
             = $~[:tree_name]
            climb()
            # doesn't have to be a tree...
        when /^(help|h)$/
            @smart_aleck ||= ["Why?","No.","Stop asking plz.","seriously, shut up.","...","...","...","Ok, seriously.","Do u not understand the meaning of \"be quiet\"?","ug"].to_enum
            begin
                puts @smart_aleck.next
            rescue StopIteration
                @smart_aleck.rewind
                puts @smart_aleck.next
            end
        when /^(quit|exit)$/
            quit
        when /^\s?$/
        else
             = ["I don't speak jibberish.","Speak up. Ur not making any sense.","R u trying to confuse me? Cuz dats not gonna work","What the heck is that supposed to mean?"]
            puts .sample
        end
    end

    def walk(direction)
        if direction != "to mordor"
            if new_room = @rooms[@current_room[direction]]
                @current_room = new_room.enter
            else
                puts "You can't go that way."
            end
        else
            #TODO: add quest system.  We should have a main quest and other side quests like going to mordor.
            puts "One does not simply walk to Mordor... You need to find the eagles. They will take you to Mordor."
        end
    end

    def pickup(item)
        if _item = @current_room.items[item.to_sym]
            if _item.can_pickup
                _item = @current_room.remove_item(item)
                @player.pickup(item, _item)
            else
                puts "You can't pick that up."
            end
        else
            puts "That item isn't in here."
        end
    end

    def inventory
        @player.inventory
    end

    def look
        @current_room.look
    end

    def inspect(item)
        # this could be refactored
        if the_item = @player.items[item.to_sym]
            puts the_item.description
        elsif the_item = @current_room.items[item.to_sym]
            puts the_item.description
        else
            puts "This item is not here or your inventory."
        end
    end

    def rub_sticks
        if @player.items[:sticks]
            # do something involving fire
            puts "I need to implement this."
        end
    end

    def climb(thing_name)
        if  = @current_room.items[:tree]
            name = .name.downcase
            if thing_name.nil? || thing_name == "tree" || thing_name == name
                .climb
            else
                puts "You can't climb that."
            end

        # I don't like how this works :(
        elsif @current_room.options[:has_mountain]
            if ["up", "mountain", nil].include? thing_name
                walk("u")
            end
        else
            puts "You can't climb that."
        end
    end

    def quit
        exit
    end

end

LIB / quest.rb:

class Quest

    attr_accessor :steps

    def initialize(name, steps, options = {})
        @name = name

        # steps (the argument) should be a hash like this:
        # [:found_ring, :melted_ring]
        @steps = steps.inject({}) { |hash, step| hash[step] = false; hash }
        # then @step will be this:
        # { found_ring: false, melted_ring: false }

        @started = false
        @options = options
    end

    def start
        @started = true
        puts "#{'Quest started!'.cyan} - #{name}"
    end

end

LIB / room.rb:

class Room

    attr_reader :items, :options, :people

    def initialize(name, description, options = {})
        @name = name
        @description = description
        @paths = options[:paths] || {}
        @items = options[:items] || {}
        @people = options[:people] || {}
        @options = options
        @visited = false
    end

    def [](direction)
        @paths[direction.to_sym]
    end

    def enter
        puts @name.cyan
        unless @visited
            puts @description
            list_items
        end
        @visited = true # can't hurt to set it every time, right?
        self
    end

    def remove_item(item)
        @items.delete(item.to_sym)
    end

    def look
        puts @name.cyan
        puts @description
        list_items
    end

    def list_items
        visible_items = @items.values.select { |i| (!i.hidden) && i.can_pickup }
        unless visible_items.empty?

            puts "Items that are here:".magenta
            visible_items.map do |item|
                a_or_an = %w[a e i o u].include?(item.name[0]) \
                    ? "an " : "a "
                a_or_an = "" if item.name[-1] == "s"
                puts "#{a_or_an}#{item.name.downcase}"
            end
        end

        visible_people = @people.values.select { |i| (!i.hidden) && i.can_pickup }
        unless visible_people.empty?

            puts "People that are here:".magenta
            visible_people.map do |people|
                puts "#{people.name}"
            end
        end

    end

end

我知道Delegate.current_room应该是Player not Delegate的属性,如果我要保存它,我只是没有解决这个问题。

那么你们所有人都想到用YAML序列化来保存它? (丑陋的!ruby/object:Class东西)

我真的想知道一个更好的方法,但我想不出任何事情。我想我可以将yaml或其他保存格式放在〜/ .conquest_save

我很乐意收到您的所有意见,谢谢!完整的项目在Github上here

1 个答案:

答案 0 :(得分:0)

您可以尝试使用Marshal。

http://ruby-doc.org/core-2.1.2/Marshal.html

使用的两种主要方法是:

data = Marshal.dump(o)
obj = Marshal.load(data)

LPC MUD实际上传统上使用SQL。我发现当我遇到一些错误并且出现矩阵故障时,然后查看日志,我看到SQL相关错误让我大吃一惊。

我也试过了MUD的运气,我实际上对自己说,如果它比yaml文件更复杂,那么这是我不会去的方式。

我认为如果你的系统实际上会变得更复杂,你是否试图抽象出来并使用像postgresql或mysql这样的包装器,比如activerecord?