宫锁Ruby之Class(二)

前言:
有了上一次Class(一)的学习经验,这次再学习一个新的概念“继承”(inheritance),合在一起就可以实现简单的石头、剪刀、布游戏(简称RPS)。

PS:选择石头剪刀布游戏作为练习也是因为,最近这个我们儿时的游戏,又开始被大家关注,源于浙江大学、浙江工商大学和中科院理论物理研究所的研究人员在预印本网站上发表研究报告(PDF),他们通过实验发现了石头剪刀布的一个制胜策略。引起网民广泛讨论,可到知乎、果核进行搜索。

一、RPS游戏 简要说明

首先,我只是想实现一个简单的人机RPS游戏(主要是为了学习继承)。所以,避开了多人RPS游戏中,需要考虑的分组和解组等复杂的算法问题。(例如:16个人玩的话,如何分组?)

其次,考虑必须创建的类。玩家(Player)是必须创建的类,无论是人还是电脑在游戏中都属于或者继承自这个类。再有,要创建游戏(Game)类,其中包含了选手组(@players_in_game)实例,以及判定游戏胜负的方法等。创建一个游戏对象即开始一场新的游戏。

下面一起来看看代码吧!

二、创建玩家类(PlayerInGame)

player_in_game.rb

class PlayerInGame
    attr_reader :name, :player_choice

    def initialize(name, player_choice)
        @name = name
        @player_choice = player_choice
    end

    def choice_to_num
        if player_choice == "rock"
            return 0
        elsif player_choice == "paper"
            return 1
        elsif player_choice == "scissors"
            return 2
        else
            return "error"
        end
    end

    def to_s
        "I (#{@name}) choice #{@player_choice}"
    end
end

创建类和属性自不必再赘述(详见Class(一)),主要关注一下choice_to_num方法。首先,游戏中1v1的两个玩家都会有R/P/S 三个选择(@player_choice),考虑:判定胜负的方法中,@player_choice属性存储的是String对象,我们不可能通过正则表达式去验证两个玩家的选择,再根据选择得出胜负结果,这样太繁琐了。

试想一下,如果将两个玩家@player_choice属性存储的String对象转换成数值对象(Numeric),再通过某种公式计算来判定RPS游戏的胜负结果,这样就简便多了。

从而有了代码中的choice_to_num方法。将玩家的选择(String对象)与数值(Numeric对象)关联起来。

三、电脑玩家类(CompInGame)

comp_in_game.rb

require_relative 'player_in_game'

class CompInGame < PlayerInGame

    def initialize(name, player_choice)
        super(name, player_choice)
    end

    def num_to_choice
        if player_choice == 0
            return "rock"
        elsif player_choice == 1
            return "paper"
        elsif player_choice == 2
            return "scissors"
        else
            return "error"
        end
    end

    def to_s
        "#{@name} choice #{self.num_to_choice}"
    end
end

和电脑玩RPS游戏,那么电脑也算是玩家。但是电脑和人类总还是有所区别的,放在一个类别里似乎不太恰当。比如:或许电脑会有电量属性,电量不够就无法继续游戏,而人类玩家却不需要电量属性,但是二者又都有一些共同的属性,如:@name@player_choice。所以有了CompInGame类。也由此引出了一个新的概念继承(inheritance)。

继承(inheritance)
一起来看看Ruby编程语言之父——松本行弘对继承的阐释吧。

随 着 软 件 规 模 的 扩 大 , 用 到 的 类 的 个 数 也 随 之 增 加 , 其 中 也 会 有 很 多 性 质 相 似 的 类 。 这 就 违 背 了 我 们 之 前 强 调 多 次 的 DRY 原 则 。 程 序 会 变 得 重 复 而 且 不 容 易 理 解 。 修 改 程 序 的 代 价 也 会 变 高 , 生 产 力 则 会 降 低 。 所 以 , 如 果 有 把 这 些 相 似 的 部 分 汇 总 到 一 起 的 方 法 就 好 了 。 继 承 就 是 这 种 方 法 。 具 体 说 来 , 继 承 就 是 在 保 持 既 有 类 的 性 质 的 基 础 上 而 生 成 新 类 的 方 法 。 原 来 的 类 称 为 父 类 , 新 生 成 的 类 称 为 子 类 。 子 类 继 承 父 类 所 有 的 方 法 , 如 果 需 要 也 可 以 增 加 新 的 方 法 。 子 类 也 可 以 根 据 需 要 重 写 从 父 类 继 承 的 方 法 。

松本行弘. 松本行弘的程序世界 (Kindle Locations 543-548). 人民邮电出版社.

PS:推荐向我一样的初学者也可以看看《松本行弘的程序世界》,虽然是给高手们的醍醐灌顶之作。但个人感觉对于初学者,其中介绍Ruby起源、面向对象等方面,能够在开始学的过程中就奠定好的基础。

言归正传,对继承的概念有了了解后,回到代码上面。关注如何创建子类。上文中我们讨论了,电脑也算是玩家,与人类玩家有共同的属性,但又有别于人类玩家。那么我们就要创建电脑玩家类所为玩家类的子类。如下:
class CompInGame < PlayerInGame,即:CompInGame类继承自PlayerInGame类,也可以说:CompInGame类是PlayerInGame类的子类,PlayerInGame类是CompInGame类的父类。

其次,需要关注关键字super。在初始化方法中使用super,即:创建与父类相同的属性。

def initialize(name, player_choice)
    super(name, player_choice)
end

四、游戏类(Game)

game.rb

require_relative 'player_in_game'
require_relative 'comp_in_game'

class Game

    def initialize
        @players_in_game = []
    end

    def read_in_argv(name, player_choice)
        @players_in_game << CompInGame.new("Wall_E", [0, 1, 2].sample)
        @players_in_game << PlayerInGame.new(name, player_choice)
    end

    def to_s
        @players_in_game.each do |player|
            puts "#{player.name}"
        end
    end

    def rps
        20.times { print "=" }
        puts
        puts "#{@players_in_game[0].name} choice #{@players_in_game[0].num_to_choice}."
        puts "#{@players_in_game[1].name} choice #{@players_in_game[1].player_choice}."
        20.times { print "=" }
        puts
        diff = (@players_in_game[0].player_choice - @players_in_game[1].choice_to_num) % 5
        if diff == 4 || diff == 2
            puts "#{@players_in_game[1].name} wins."
        elsif diff == 3 || diff == 1
            puts "#{@players_in_game[0].name} wins."
        else
            puts "Player and Computer tie."
        end
        puts
    end
end

首先,考虑在Game类中需要初始化玩家实例变量,这样才能通过方法对每个玩家的@player_choice进行比较。因此,初始化@players_in_game实例变量,用来存储参加游戏的玩家对象,那么数组自然是最合适的数据类型。

第二、def read_in_argv(name, player_choice)方法。
read_in_argv方法用于获取终端输入的参数后,自动生成一个电脑玩家对象,同时,根据终端输入的参数生成真人玩家对象。并将两个玩家对象存储到@players_in_game实例变量。

注意一点的是电脑玩家对象的@player_choice属性,指向的并不是字符串对象,而是[0, 1, 2].sample随机生成的一个数字,随后可以使用电脑玩家类特有的方法def num_to_choice将数字转换成对应的Rock、Paper、Scissors。

PS:留意一下@players_in_game << CompInGame.new("Wall_E", [0, 1, 2].sample),80后的同学是否还记得Wall_E(霹雳五号)呢?

在八十年代颇受欢迎的机器人喜剧,描述一个拥有最精密雷射武器的机器人“五号",在一次短路状况下闯入了热心保护动物的史蒂芬妮家中,并从与人交往的过程中学习到人类的智慧的人性。

第三、def rps
Game类中的rps方法是游戏的核心,因为要通过该方法来判定胜负。

首先,打印出每个玩家的姓名(@name)以及选项(@player_choice),需要注意的是,电脑玩家的@player_choice是数字,所以,需要使用Class CompInGame类的def num_to_choice方法,将数字转换成对应的Rock、Paper、Scissors选项。

其次、如何判定胜负?通过将每个玩家@player_choice属性对应的数值做减法,再做求余运算,最后根据求余的结果使用条件语句判定胜负。

五、来场比赛吧(annual_rps_match)

annual_rps_match.rb

require_relative 'game'

match = Game.new

puts
STDERR.puts "Processing ARGV data"
match.read_in_argv(ARGV[0], ARGV[1])
match.rps

六、问题

为了学习继承,我臆测了这个极简的RPS游戏。但是其中存在漏洞,例如:如果我输入了一个不在范围内的选择(如:飞船),就会报错。或许还有更好地实现RPS游戏的方法。这就需要路过的各位同学给一些建议,我们共同进步。

PS:《纽约时报》提供的猜拳机器人。感兴趣的同学可以玩玩哈!
http://www.nytimes.com/interactive/science/rock-paper-scissors.html

它分为两个难度模式:
初学者(Novice)只会根据你的出拳习惯来猜你下一个会出什么
高难度(Veteran)等级则会从收集了超过二十万场剪刀、石头、布的数据库中, 猜你的下一步会出什么(过了五局之后,就可以点右上的「See What the Computer is Thinking」看计算机是怎么猜的)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容