cardgame 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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