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.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cardgame.gemspec
4
+ gemspec
@@ -0,0 +1,4 @@
1
+ Uno Card Game
2
+ =============
3
+
4
+ This is a programming exercise created for my son. It models some card games and their rules, especially Uno.
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.test_files = FileList['test/**/test_*.rb']
7
+ end
8
+
9
+ task :default => :test
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?
@@ -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
@@ -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
@@ -0,0 +1,6 @@
1
+ require 'cardgame/version'
2
+ require 'cardgame/deck'
3
+ require 'cardgame/card'
4
+ require 'cardgame/player'
5
+ require 'cardgame/uno'
6
+ require 'cardgame/french'
@@ -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,17 @@
1
+ module CardGame
2
+ class French < Deck
3
+ class << self
4
+ def ranks
5
+ %w{Ace 2 3 4 5 6 7 8 9 10 Knave Queen King}
6
+ end
7
+
8
+ def trumps
9
+ []
10
+ end
11
+
12
+ def suits
13
+ %w{Clubs Spades Hearts Diamonds}
14
+ end
15
+ end
16
+ end
17
+ 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
@@ -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,3 @@
1
+ module CardGame
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ {
2
+ "trumps": [],
3
+ "ranks": [
4
+ "Ace",
5
+ "2",
6
+ "3",
7
+ "4",
8
+ "5",
9
+ "6",
10
+ "7",
11
+ "8",
12
+ "9",
13
+ "10",
14
+ "Knave",
15
+ "Queen",
16
+ "King"
17
+ ],
18
+ "suits": [
19
+ "Clubs",
20
+ "Spades",
21
+ "Hearts",
22
+ "Diamonds"
23
+ ]
24
+ }
@@ -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
@@ -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
@@ -0,0 +1,11 @@
1
+ require 'helper'
2
+
3
+ class TestCard < CardGameTest::TestCase
4
+ def setup
5
+ @card = CardGame::Card.new('foo', 'bar')
6
+ end
7
+
8
+ def test_to_s
9
+ assert_equal('bar foo', @card.to_s)
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ require 'helper'
2
+
3
+ class TestFrench < CardGameTest::TestCase
4
+ include CardGameTest::DeckTests
5
+
6
+ def setup
7
+ @deck = CardGame::French.new
8
+ end
9
+
10
+ def test_size
11
+ assert_equal(52, @deck.size)
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ require 'helper'
2
+ require 'awesome_print'
3
+
4
+ class TestUno < CardGameTest::TestCase
5
+ include CardGame
6
+ include CardGameTest::DeckTests
7
+
8
+ def setup
9
+ @deck = Uno.new
10
+ end
11
+
12
+ def test_size
13
+ assert_equal(108, @deck.size)
14
+ end
15
+ 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