编程技巧:如何创建一个简单的纸牌游戏

时间:2010-04-14 21:52:57

标签: ruby algorithm language-agnostic artificial-intelligence

当我学习Ruby语言时,我越来越接近实际的编程。我在想创造一个简单的纸牌游戏。我的问题不是面向Ruby,但我确实想知道如何用真正的OOP方法解决这个问题。在我的纸牌游戏中,我希望有四个玩家,使用标准牌组,52张牌,没有笑话/通配符。在游戏中,我不会将ace用作双卡,它始终是最高卡。

所以,我想知道的编程问题如下:

  1. 如何对牌组进行分类/随机化?有四种类型,每种类型有13个值。最终只能有唯一值,因此选择随机值可能会生成重复值。

  2. 如何实现简单的AI?由于有大量的纸牌游戏,有人会已经想出这部分,所以参考会很棒。

  3. 我是一个真正的Ruby nuby,我的目标是学习解决问题,所以伪代码会很棒,只是为了理解如何以编程方式解决问题。如果不清楚,我为我的语法和写作风格道歉,因为它不是我的母语。

    此外,指向解释此类挑战的网站的指针将是一个很好的资源!

    感谢您的意见,答案和反馈!

6 个答案:

答案 0 :(得分:15)

让你入门的东西

使用0到51之间的数字,您可以非常轻松地确保唯一卡片。

Array#shuffle方法基于Knuth-Fisher-Yates shuffle算法。 http://en.wikipedia.org/wiki/Fisher–Yates_shuffle

class Card
  RANKS = %w(2 3 4 5 6 7 8 9 10 J Q K A)
  SUITS = %w(Spade Heart Club Diamond)

  attr_accessor :rank, :suit

  def initialize(id)
    self.rank = RANKS[id % 13]
    self.suit = SUITS[id % 4]
  end
end

class Deck
  attr_accessor :cards
  def initialize
    # shuffle array and init each Card
    self.cards = (0..51).to_a.shuffle.collect { |id| Card.new(id) }
  end
end

# people with Ruby 1.9 (or 1.8.7 with backports) can safely ignore this duck punch
class Array
  # knuth-fisher-yates shuffle algorithm
  def shuffle!
    n = length
    for i in 0...n
      r = rand(n-i)+i
      self[r], self[i] = self[i], self[r]
    end
    self
  end
  def shuffle
    dup.shuffle!
  end
end

测试

d = Deck.new
d.cards.each do |card|
  puts "#{card.rank} #{card.suit}"
end

输出

6 Spade
5 Heart
2 Heart
8 Heart
8 Diamond
7 Club
J Diamond
4 Club
K Spade
5 Diamond
J Heart
8 Spade
10 Club
4 Diamond
9 Heart
7 Diamond
3 Diamond
K Diamond
7 Spade
Q Diamond
9 Diamond
6 Heart
A Heart
9 Club
A Spade
5 Club
J Club
Q Spade
2 Club
2 Spade
Q Heart
A Diamond
10 Spade
10 Diamond
Q Club
3 Club
A Club
K Club
6 Club
10 Heart
2 Diamond
3 Spade
K Heart
5 Spade
9 Spade
7 Heart
4 Spade
J Spade
3 Heart
4 Heart
8 Club
6 Diamond

答案 1 :(得分:5)

我没有在评论中填写这一切,而是将其添加为可能会发现它有用的人的注释。 Ruby 1.9的原生Array#shuffle!Array#shuffle确实使用了Knuth-Fisher-Yates shuffle algorithm

红宝石1.9.1-P376 / array.c

/*
 *  call-seq:
 *     array.shuffle!        -> array
 *  
 *  Shuffles elements in _self_ in place.
 */

static VALUE
rb_ary_shuffle_bang(VALUE ary)
{
    long i = RARRAY_LEN(ary);

    rb_ary_modify(ary);
    while (i) {
    long j = rb_genrand_real()*i;
    VALUE tmp = RARRAY_PTR(ary)[--i];
    RARRAY_PTR(ary)[i] = RARRAY_PTR(ary)[j];
    RARRAY_PTR(ary)[j] = tmp;
    }
    return ary;
}


/*
 *  call-seq:
 *     array.shuffle -> an_array
 *  
 *  Returns a new array with elements of this array shuffled.
 *     
 *     a = [ 1, 2, 3 ]           #=> [1, 2, 3]
 *     a.shuffle                 #=> [2, 3, 1]
 */

static VALUE
rb_ary_shuffle(VALUE ary)
{
    ary = rb_ary_dup(ary);
    rb_ary_shuffle_bang(ary);
    return ary;
}

答案 2 :(得分:3)

不要费心寻找AI包

您将通过自己编写“AI”来了解更多信息并获得更高的满意度。

从简单开始,只考虑:

  • 游戏状态 - 玩过或看过哪些牌,所有玩家都可以看到哪些牌
  • 策略 - 计算机玩家如何根据当前手牌及其对游戏状态的了解做出回应

一旦你有了这个工作,你就可以制定更复杂的策略:

  • inference - 人类玩家可能根据之前的行动持有哪些牌
  • game tree search - 如何在可能发生的情况下最大化获胜机会

然后如果你想让真的复杂,你可以开始研究opponent modeling

答案 3 :(得分:2)

对于建立一个套牌,马斯克的答案是好的。

您还询问了其他实体。

你可能想要四个“玩家”。每个玩家可能是人或受机器控制的。

要实现人类播放器,您需要与屏幕/鼠标/键盘连接;为了实现机器控制的玩家,你有一只手,你可以看到桌子上的一些中央牌(所有玩家都需要知道一张中央牌桌,可以放在牌桌上的牌)。

从那里开始,逻辑基于你正在玩什么游戏。

一旦你的“玩家”(AI)转向(例如,在你的玩家对象上调用takeTurn方法),它应该检查它的牌并做出正确的决定 - 从堆栈中取出牌桌子或将牌从手放到桌子上。 (该表几乎肯定至少有两个玩家可以访问的堆栈 - “Draw”和“Discard”。)

当一个人类玩家调用他的takeTurn方法时,它应该与屏幕交互 - 更新玩家的手,允许他绘制和丢弃。

当每个玩家完成转弯时,它应该返回。它不能直接调用下一个玩家(否则你会开始建立一个堆栈),所以你需要某种形式的转弯控制,可以按顺序调用玩家。这种集中控制还可以防止玩家彼此了解,他们不应该真正需要(最好的OO设计策略之一就是每个对象应该尽可能少地了解其他对象)。

......还在想......我可以添加更多...

答案 4 :(得分:1)

我不确定你想要构建什么类型的纸牌游戏,但是构建这种AI的最常见方式是生成一个可能的选项树。我不认为有这样的库可以做到这一点,但红宝石可以很容易地做树。

目标是拥有一个根节点,它是当前时间,然后每个子节点都是可能的动作。然后,每个可能的动作的孩子是下一个可能的动作。从那里你可以建立一个包含所有可能结果的树。剩下的就是选择你喜欢的结果。

如果您没有所有信息(即无法看到您的对手牌),您可以模拟它。通过模拟我的意思是猜测。所有模拟/猜测的平均值将使您清楚地了解哪些树枝“可能是最好的”。

如果你可以做好所有你正在做的事情(这是一个非常好的练习),那里有数以百计的人工智能文章,谷歌将成为你的朋友。我描述的方法唯一的问题是它可能非常慢,但有许多聪明的技术来加速它,如换位表,alpha-beta修剪等......我不建议你查看它。

答案 5 :(得分:0)

让你开始的事情非常简单:

class CardGame
  DECK = %w[A 2 3 4 5 6 7 8 9 T J Q K].product(%w[c d h s]).map(&:join)

  def initialize(decks=1)
    @decks = decks
  end

  def shuffle
    @playing_deck = (DECK*@decks).shuffle
  end

  def deal(players=1, cards=5)
    shuffle
    @dealt = Array.new(players) { Array.new }

    @dealt.map { |hand| cards.times { hand << @playing_deck.pop } }
  end

  def display
    @dealt.each_with_index { |cards, i| puts "Player #{i+1}: #{cards.join(' | ')}" }
    puts "Cards used: #{@dealt.flatten.size}"
    puts "Cards remaining: #{@playing_deck.size}"
  end

  private :shuffle   
end

game1 = CardGame.new   
game1.deal   
game1.display    
puts 
game1.deal(4)  
game1.display   
puts   
game2 = CardGame.new(2)   
game2.deal(6,10)   
game2.display