cardgame 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +4 -0
- data/Rakefile +9 -0
- data/TODO.md +6 -0
- data/bin/game +78 -0
- data/cardgame.gemspec +25 -0
- data/lib/cardgame.rb +6 -0
- data/lib/cardgame/card.rb +43 -0
- data/lib/cardgame/deck.rb +45 -0
- data/lib/cardgame/french.rb +17 -0
- data/lib/cardgame/player.rb +57 -0
- data/lib/cardgame/uno.rb +17 -0
- data/lib/cardgame/version.rb +3 -0
- data/test/fixtures/french.json +24 -0
- data/test/fixtures/uno.yaml +41 -0
- data/test/helper.rb +50 -0
- data/test/unit/test_card.rb +11 -0
- data/test/unit/test_french.rb +13 -0
- data/test/unit/test_uno.rb +15 -0
- metadata +84 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
data/TODO.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
* Extract the playing engine
|
|
2
|
+
* Finish the retry-mode after a player violated the rules
|
|
3
|
+
* Build a rules engine that enforces the game rules and prevents cheating (e.g. it must not be possible to add new cards to the deck)
|
|
4
|
+
* Implement swapping of cards when a zero was played
|
|
5
|
+
* Implement reverse order if Reverse card was played
|
|
6
|
+
* Can we detect the endless loop that occasionally occurs?
|
data/bin/game
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
|
4
|
+
require 'cardgame'
|
|
5
|
+
include CardGame
|
|
6
|
+
|
|
7
|
+
class RuleViolation < StandardError; end
|
|
8
|
+
|
|
9
|
+
#
|
|
10
|
+
# Ensure that everyone follows the rules
|
|
11
|
+
#
|
|
12
|
+
def matches_rules(played, pile)
|
|
13
|
+
true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class UnoUno < StandardError
|
|
17
|
+
attr_reader :player, :card
|
|
18
|
+
def initialize(player, card)
|
|
19
|
+
@player, @card = player, card
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
players = []
|
|
24
|
+
('A'..'C').each{|i| players << Player.new(i)}
|
|
25
|
+
|
|
26
|
+
stack = Uno.new.deal
|
|
27
|
+
|
|
28
|
+
Array.class_eval do
|
|
29
|
+
def replenish(pile)
|
|
30
|
+
concat(pile)
|
|
31
|
+
pile.clear
|
|
32
|
+
pile.push(pop) # the last card stays on the pile
|
|
33
|
+
sort_by!{rand}
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# deal out cards to the players
|
|
38
|
+
7.times{
|
|
39
|
+
players.each{|player|
|
|
40
|
+
player << stack.pop
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# init the game
|
|
45
|
+
pile = []
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
pile.push(stack.pop)
|
|
49
|
+
end while !pile.first && pile.first.trump? # do not allow trumps (jokers) as initial card
|
|
50
|
+
|
|
51
|
+
# puts "Starting the game with #{players.size} players"
|
|
52
|
+
rounds = 0
|
|
53
|
+
|
|
54
|
+
# main game loop - ask each player to play
|
|
55
|
+
loop do
|
|
56
|
+
begin
|
|
57
|
+
rounds += 1
|
|
58
|
+
players.each{|player|
|
|
59
|
+
begin
|
|
60
|
+
played = player.play(pile.last)
|
|
61
|
+
|
|
62
|
+
if played && matches_rules(played, pile.last)
|
|
63
|
+
pile.push(played)
|
|
64
|
+
else
|
|
65
|
+
stack.replenish(pile) if stack.empty? # replenish stack from pile
|
|
66
|
+
player << stack.pop # draw a card from the stack
|
|
67
|
+
end
|
|
68
|
+
rescue RuleViolation => v
|
|
69
|
+
puts v.message
|
|
70
|
+
player << played # Push the offending card back to the player
|
|
71
|
+
retry
|
|
72
|
+
end
|
|
73
|
+
}
|
|
74
|
+
rescue UnoUno => u
|
|
75
|
+
puts "#{u.player} wins with #{u.card} after #{rounds} rounds."
|
|
76
|
+
exit
|
|
77
|
+
end
|
|
78
|
+
end
|
data/cardgame.gemspec
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "cardgame/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "cardgame"
|
|
7
|
+
s.version = CardGame::VERSION
|
|
8
|
+
s.authors = ["Nicholas E. Rabenau"]
|
|
9
|
+
s.email = ["nerab@gmx.net"]
|
|
10
|
+
s.homepage = ""
|
|
11
|
+
s.summary = %q{Models card games, e.g. Uno}
|
|
12
|
+
s.description = %q{This gem is a small exercise in OO modeling. It models card games, especially Uno.}
|
|
13
|
+
|
|
14
|
+
s.rubyforge_project = "cardgame"
|
|
15
|
+
|
|
16
|
+
s.files = `git ls-files`.split("\n")
|
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
19
|
+
s.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
# specify any dependencies here; for example:
|
|
22
|
+
# s.add_development_dependency "rspec"
|
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
|
24
|
+
s.add_runtime_dependency 'activesupport', '~> 3.2'
|
|
25
|
+
end
|
data/lib/cardgame.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module CardGame
|
|
2
|
+
class BaseCard
|
|
3
|
+
attr_reader :rank
|
|
4
|
+
|
|
5
|
+
def initialize(rank)
|
|
6
|
+
@rank = rank
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def to_s
|
|
10
|
+
@rank
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def trump?
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Trump < BaseCard
|
|
19
|
+
# When playing a trump card, the player can set which suit it has so that the next player has to follow it
|
|
20
|
+
attr_accessor :suit
|
|
21
|
+
|
|
22
|
+
def trump?
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_s
|
|
27
|
+
"#{super} requiring #{@suit}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Card < BaseCard
|
|
32
|
+
attr_reader :suit
|
|
33
|
+
|
|
34
|
+
def initialize(rank, suit)
|
|
35
|
+
super(rank)
|
|
36
|
+
@suit = suit
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_s
|
|
40
|
+
"#{@suit} #{super}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
|
|
3
|
+
module CardGame
|
|
4
|
+
class Deck
|
|
5
|
+
class << self
|
|
6
|
+
def ranks
|
|
7
|
+
[]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def trumps
|
|
11
|
+
[]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def suits
|
|
15
|
+
[]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
extend Forwardable
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
@cards = []
|
|
23
|
+
|
|
24
|
+
self.class.ranks.each{|name|
|
|
25
|
+
self.class.suits.each{|color|
|
|
26
|
+
push Card.new(name, color)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
self.class.trumps.each{|j| push Trump.new(j)}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# Deal out a shuffled deck
|
|
35
|
+
#
|
|
36
|
+
def deal
|
|
37
|
+
@cards.sort_by{rand}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def_delegator :@cards, :size
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
def_delegator :@cards, :push
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
|
|
3
|
+
module CardGame
|
|
4
|
+
class Player
|
|
5
|
+
extend Forwardable
|
|
6
|
+
attr_reader :name
|
|
7
|
+
|
|
8
|
+
def initialize(name)
|
|
9
|
+
@name = name
|
|
10
|
+
@hand = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
"Player #{@name}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# Select one or more of our cards (according to our playing strategy)
|
|
19
|
+
#
|
|
20
|
+
# Note the the player has no further knowledge about the state of the game except the topmost card of the pile that is passed.
|
|
21
|
+
# If a player wants to implement more advanced strategies (e.g. by observing and counting the cards already played),
|
|
22
|
+
# we need to implement a notification mechanism that tells every player which card the current player played.
|
|
23
|
+
#
|
|
24
|
+
# TODO Need to obey if top_card is a trump, unless we can trump that one
|
|
25
|
+
# TODO If the game system allows it, return two identical cards
|
|
26
|
+
#
|
|
27
|
+
def play(top_card)
|
|
28
|
+
raise "There must be a card on the pile" if top_card.nil?
|
|
29
|
+
|
|
30
|
+
# puts
|
|
31
|
+
# puts "#{self} plays:"
|
|
32
|
+
# puts "#{@hand.size} in hand and sees the top card #{top_card}"
|
|
33
|
+
|
|
34
|
+
candidates = @hand.select{|card| top_card.rank == card.rank || top_card.suit == card.suit || card.trump?}
|
|
35
|
+
|
|
36
|
+
selected = candidates.sort_by{rand}.pop
|
|
37
|
+
|
|
38
|
+
if selected.nil?
|
|
39
|
+
# puts "Decided NOT to play"
|
|
40
|
+
else
|
|
41
|
+
selected.suit = Uno.suits.sample if selected.trump? # TODO Improve suit selection to take our current hand into account
|
|
42
|
+
# puts "Decided to play #{selected}"
|
|
43
|
+
@hand.delete_if{|card| selected == card}
|
|
44
|
+
# puts "Has #{@hand.size} left in hand."
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# raise Uno.new(selected) if 1 == @hand.size
|
|
48
|
+
raise UnoUno.new(self, selected) if 0 == @hand.size
|
|
49
|
+
|
|
50
|
+
selected
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def_delegator :@hand, :size
|
|
54
|
+
def_delegator :@hand, :<<
|
|
55
|
+
def_delegator :@hand, :empty?
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/cardgame/uno.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module CardGame
|
|
2
|
+
class Uno < Deck
|
|
3
|
+
class << self
|
|
4
|
+
def ranks
|
|
5
|
+
['0'].concat(%w{1 2 3 4 5 6 7 8 9 Reverse Skip} * 2).concat(['Draw 2'] * 2)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def trumps
|
|
9
|
+
["Wild Draw 4"] * 4 + ["Wild"] * 4
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def suits
|
|
13
|
+
%w{Blue Green Red Yellow}
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
:ranks:
|
|
3
|
+
- '0'
|
|
4
|
+
- '1'
|
|
5
|
+
- '2'
|
|
6
|
+
- '3'
|
|
7
|
+
- '4'
|
|
8
|
+
- '5'
|
|
9
|
+
- '6'
|
|
10
|
+
- '7'
|
|
11
|
+
- '8'
|
|
12
|
+
- '9'
|
|
13
|
+
- Reverse
|
|
14
|
+
- Skip
|
|
15
|
+
- '1'
|
|
16
|
+
- '2'
|
|
17
|
+
- '3'
|
|
18
|
+
- '4'
|
|
19
|
+
- '5'
|
|
20
|
+
- '6'
|
|
21
|
+
- '7'
|
|
22
|
+
- '8'
|
|
23
|
+
- '9'
|
|
24
|
+
- Reverse
|
|
25
|
+
- Skip
|
|
26
|
+
- Draw 2
|
|
27
|
+
- Draw 2
|
|
28
|
+
:trumps:
|
|
29
|
+
- Wild Draw 4
|
|
30
|
+
- Wild Draw 4
|
|
31
|
+
- Wild Draw 4
|
|
32
|
+
- Wild Draw 4
|
|
33
|
+
- Wild
|
|
34
|
+
- Wild
|
|
35
|
+
- Wild
|
|
36
|
+
- Wild
|
|
37
|
+
:suits:
|
|
38
|
+
- Blue
|
|
39
|
+
- Green
|
|
40
|
+
- Red
|
|
41
|
+
- Yellow
|
data/test/helper.rb
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
2
|
+
|
|
3
|
+
require 'test/unit'
|
|
4
|
+
require 'cardgame'
|
|
5
|
+
require 'yaml'
|
|
6
|
+
require 'json'
|
|
7
|
+
require 'active_support/core_ext/string'
|
|
8
|
+
|
|
9
|
+
class Test::Unit::TestCase
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module CardGameTest
|
|
13
|
+
class TestCase < Test::Unit::TestCase
|
|
14
|
+
FIXTURES_DIR = File.join(File.dirname(__FILE__), 'fixtures')
|
|
15
|
+
|
|
16
|
+
def fixture(name = self.class.name.underscore.match(/test_(.*)/)[1])
|
|
17
|
+
file_name = nil
|
|
18
|
+
|
|
19
|
+
%w{.yaml .yml .json}.each{|ext|
|
|
20
|
+
file_name = File.join(FIXTURES_DIR, "#{name}#{ext}")
|
|
21
|
+
break if File.exist?(file_name)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
raw = File.read(file_name)
|
|
25
|
+
|
|
26
|
+
case File.extname(file_name)
|
|
27
|
+
when '.yaml'
|
|
28
|
+
YAML.load(raw)
|
|
29
|
+
when '.json'
|
|
30
|
+
JSON.parse(raw, :symbolize_names => true)
|
|
31
|
+
else
|
|
32
|
+
raw
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
module DeckTests
|
|
38
|
+
def test_suits
|
|
39
|
+
assert_equal(fixture[:suits], @deck.class.suits)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_trumps
|
|
43
|
+
assert_equal(fixture[:trumps], @deck.class.trumps)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_ranks
|
|
47
|
+
assert_equal(fixture[:ranks], @deck.class.ranks)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: cardgame
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Nicholas E. Rabenau
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-03-31 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: activesupport
|
|
16
|
+
requirement: &2152230380 !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ~>
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '3.2'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: *2152230380
|
|
25
|
+
description: This gem is a small exercise in OO modeling. It models card games, especially
|
|
26
|
+
Uno.
|
|
27
|
+
email:
|
|
28
|
+
- nerab@gmx.net
|
|
29
|
+
executables:
|
|
30
|
+
- game
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- .gitignore
|
|
35
|
+
- Gemfile
|
|
36
|
+
- README.md
|
|
37
|
+
- Rakefile
|
|
38
|
+
- TODO.md
|
|
39
|
+
- bin/game
|
|
40
|
+
- cardgame.gemspec
|
|
41
|
+
- lib/cardgame.rb
|
|
42
|
+
- lib/cardgame/card.rb
|
|
43
|
+
- lib/cardgame/deck.rb
|
|
44
|
+
- lib/cardgame/french.rb
|
|
45
|
+
- lib/cardgame/player.rb
|
|
46
|
+
- lib/cardgame/uno.rb
|
|
47
|
+
- lib/cardgame/version.rb
|
|
48
|
+
- test/fixtures/french.json
|
|
49
|
+
- test/fixtures/uno.yaml
|
|
50
|
+
- test/helper.rb
|
|
51
|
+
- test/unit/test_card.rb
|
|
52
|
+
- test/unit/test_french.rb
|
|
53
|
+
- test/unit/test_uno.rb
|
|
54
|
+
homepage: ''
|
|
55
|
+
licenses: []
|
|
56
|
+
post_install_message:
|
|
57
|
+
rdoc_options: []
|
|
58
|
+
require_paths:
|
|
59
|
+
- lib
|
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
61
|
+
none: false
|
|
62
|
+
requirements:
|
|
63
|
+
- - ! '>='
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '0'
|
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
|
+
none: false
|
|
68
|
+
requirements:
|
|
69
|
+
- - ! '>='
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '0'
|
|
72
|
+
requirements: []
|
|
73
|
+
rubyforge_project: cardgame
|
|
74
|
+
rubygems_version: 1.8.15
|
|
75
|
+
signing_key:
|
|
76
|
+
specification_version: 3
|
|
77
|
+
summary: Models card games, e.g. Uno
|
|
78
|
+
test_files:
|
|
79
|
+
- test/fixtures/french.json
|
|
80
|
+
- test/fixtures/uno.yaml
|
|
81
|
+
- test/helper.rb
|
|
82
|
+
- test/unit/test_card.rb
|
|
83
|
+
- test/unit/test_french.rb
|
|
84
|
+
- test/unit/test_uno.rb
|