functional-yahtzee 0.0.3

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ee9515ae7d6e62b2c338eaa390b462f0a00fd47f
4
+ data.tar.gz: 41387d21afb1951765a3d9c8cf5aee97ada9ec8a
5
+ SHA512:
6
+ metadata.gz: 763b40ce6d6b310e281eb5b8ad5c56f50827c06048576d6313f2c3ffb836c480c5c7fa869cdb74e76b3450c1e52222c2851330ae0217c8cd9db296c8d849074b
7
+ data.tar.gz: 806e1457646e3748eb377893e0f34b55c38fecc7fa9b351aa8905e4c31efee611124ad191efa19533231116de11eecc89aa6bd62e9254a1e9ad592058f32ead2
@@ -0,0 +1 @@
1
+ --- {}
@@ -0,0 +1,2 @@
1
+ coverage
2
+ *.gem
@@ -0,0 +1 @@
1
+ 2.1.0-dev
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ notifications:
6
+ email: false
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,71 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ functional-yahtzee (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ celluloid (0.15.2)
10
+ timers (~> 1.1.0)
11
+ coderay (1.1.0)
12
+ coveralls (0.7.0)
13
+ multi_json (~> 1.3)
14
+ rest-client
15
+ simplecov (>= 0.7)
16
+ term-ansicolor
17
+ thor
18
+ docile (1.1.0)
19
+ ffi (1.9.3)
20
+ formatador (0.2.4)
21
+ guard (2.2.3)
22
+ formatador (>= 0.2.4)
23
+ listen (~> 2.1)
24
+ lumberjack (~> 1.0)
25
+ pry (>= 0.9.12)
26
+ thor (>= 0.18.1)
27
+ guard-minitest (2.0.0)
28
+ guard (~> 2.0)
29
+ minitest (>= 3.0)
30
+ listen (2.2.0)
31
+ celluloid (>= 0.15.2)
32
+ rb-fsevent (>= 0.9.3)
33
+ rb-inotify (>= 0.9)
34
+ lockfile (2.1.0)
35
+ lumberjack (1.0.4)
36
+ method_source (0.8.2)
37
+ mime-types (2.0)
38
+ minitest (5.0.8)
39
+ multi_json (1.8.2)
40
+ pry (0.9.12.3)
41
+ coderay (~> 1.0)
42
+ method_source (~> 0.8)
43
+ slop (~> 3.4)
44
+ rake (10.1.0)
45
+ rb-fsevent (0.9.3)
46
+ rb-inotify (0.9.2)
47
+ ffi (>= 0.5.0)
48
+ rest-client (1.6.7)
49
+ mime-types (>= 1.16)
50
+ simplecov (0.8.1)
51
+ docile (~> 1.1.0)
52
+ lockfile (>= 2.1.0)
53
+ multi_json
54
+ simplecov-html (~> 0.8.0)
55
+ simplecov-html (0.8.0)
56
+ slop (3.4.7)
57
+ term-ansicolor (1.2.2)
58
+ tins (~> 0.8)
59
+ thor (0.18.1)
60
+ timers (1.1.0)
61
+ tins (0.13.1)
62
+
63
+ PLATFORMS
64
+ ruby
65
+
66
+ DEPENDENCIES
67
+ bundler (~> 1.3)
68
+ coveralls (~> 0.7)
69
+ functional-yahtzee!
70
+ guard-minitest (~> 2.0)
71
+ rake (~> 10.1)
@@ -0,0 +1,6 @@
1
+ # Units
2
+ guard :minitest do
3
+ watch(%r{^test/(.*)\/?(.*)_test\.rb})
4
+ watch(%r{^lib/(.*/)?([^/]+)\.rb}) { |m| "test/unit/#{m[1]}#{m[2]}_test.rb" }
5
+ watch(%r{^test/test_helper\.rb}) { 'test' }
6
+ end
@@ -0,0 +1,115 @@
1
+ # Functional Yahztee in RUBY
2
+
3
+ The goal of this project is to build the dice game Yahtzee in a functional, concise, testable manner. The code within is
4
+ highly opinionated, and is ahallmark of my style. This is my perfect world where code and beauty collide.
5
+
6
+ If you are like me, and I know I am... you have something to learn from this code here. This is how you write clean, decoupled, testable, functional ruby code.
7
+
8
+ Expect a series of screencasts discussing the choices I have made here to get the results I ended up with.
9
+
10
+ [rubyquiz #19](http://rubyquiz.com/quiz19.html) proposed this
11
+ problem, and a very imperative solution was provided by James
12
+ Edward Gray II, one my ruby heros. I doubt he would write this the same way could he revisit the problem.
13
+
14
+ This is my contribution to the same problem, with the additional task of being as functional as possible and downright pretty code.
15
+
16
+ ## Ruby is dead
17
+
18
+ 1.8 was the Ruby of your Father. It was for a world with single cores and CPUs growing at a predictable rate each year. Ruby 1.8 is dead....
19
+
20
+ ## Long Live Ruby
21
+
22
+ Thankfully the teams behind Rubinius, JRuby, and even MRI have been
23
+ working on this problem. I am happy to say that Ruby 2.0+ is
24
+ perfectly suited as a stepping stone to get you into the new
25
+ world, a world where CPU matters not, and cores own.
26
+
27
+ ## Requirements
28
+
29
+ This is being developed in Ruby 2.1 with CI running on 2.0 and 1.9.3. My style tends to stay away from external dependencies whenever I can. (read: I am not afraid to code what I need)
30
+
31
+ I haven't gemmified this as of yet, so a clone and bundle with ruby
32
+ 1.9.3+ and you can run the tests. I would love to get this running on rubinius as well.
33
+
34
+ ```bash
35
+ cd <cloned_path>
36
+ rake
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```ruby
42
+ score_card = ScoreCard.new
43
+ writer = ScoreCard.persist(score_card)
44
+
45
+ Round.new do
46
+ roll1 = first_roll # => [1,2,3,5,5]
47
+ roll2 = second_roll([1,2,3,5]) # => [1,2,3,5,3]
48
+ roll3 = third_roll([1,2,3,5]) # => [1,2,3,5,4]
49
+
50
+ score_card = Scoring.score(roll3, :large_straight, &writer)
51
+ end
52
+
53
+ Round.new do
54
+ roll1 = first_roll # => [1,1,3,5,5]
55
+ roll2 = second_roll([1,1,5,5]) # => [1,1,5,5,1]
56
+
57
+ score_card = Scoring.score(roll2, :full_house, &writer)
58
+ end
59
+
60
+ # ...
61
+
62
+ upper_scores = score_card.to_hash.select do |k,_|
63
+ ScoreCard.upper_keys.include? k
64
+ end
65
+ score_card = Scoring.score_upper_total(upper_scores, &writer)
66
+
67
+ lower_scores = score_card.to_hash.select do |k,_|
68
+ ScoreCard.lower_keys.include? k
69
+ end
70
+
71
+ upper_total = score_card.upper_total
72
+ game_scores = lower_scores.merge(upper_total: upper_total)
73
+ score_card = Scoring.score_game_total(game_scores, &writer)
74
+
75
+ puts "Grand total: #{score_card.game_total}"
76
+
77
+ ```
78
+
79
+ ## Contributions
80
+
81
+ I am not accepting contributions at this time, this is my showcase code. This is my own highly opinionated software, inspired by all those listed in the section below *Many Thanks*.
82
+
83
+ ## Many Thanks
84
+
85
+ I would like to say thank you to all the people I have sucked off of like a vampiric zombie since 2007. Some of these were from videos of talks, some were pairing sessions, some were just friends on irc who liked to bang Ruby.
86
+
87
+ * James Edward Gray II
88
+ * Josh Susser
89
+ * Ben Curtis
90
+ * Avdi Grimm
91
+ * Zed
92
+ * Gregg Pollack
93
+ * Daniel Fischer
94
+ * Jose Valim
95
+ * Aaron Patterson
96
+ * Corey Haines
97
+ * Ryan Bates
98
+ * Steve Klabnik
99
+ * Brian Guthrie
100
+ * Fabio Akita
101
+ * Rick Olsen
102
+ * Obie Fernandez
103
+ * Sandi Metz
104
+ * Phil Cohen
105
+ * Jessica Kerr
106
+
107
+ Whew.. Sorry if I missed you!
108
+
109
+ ## Health
110
+
111
+ * My code can beat your code up
112
+
113
+ [![Build
114
+ Status](https://travis-ci.org/dreamr/functional_yahtzee.png)](https://travis-ci.org/dreamr/functional_yahtzee)
115
+ [![Coverage Status](https://coveralls.io/repos/dreamr/functional_yahtzee/badge.png?branch=master)](https://coveralls.io/r/dreamr/functional_yahtzee?branch=master) [![CodeClimate Grade](https://codeclimate.com/github/dreamr/functional_yahtzee.png)](https://codeclimate.com/github/dreamr/functional_yahtzee/code)
@@ -0,0 +1,13 @@
1
+ require 'rake/testtask'
2
+
3
+ desc "Open a console with the basic libs loaded"
4
+ task :console do
5
+ sh "irb -rubygems -Ilib -r game.rb"
6
+ end
7
+
8
+ Rake::TestTask.new do |t|
9
+ t.test_files = FileList['test/**/*_test.rb']
10
+ t.libs = ["lib", "test"]
11
+ end
12
+
13
+ task :default => :test
Binary file
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ lib = File.expand_path("../../lib",__FILE__)
4
+ $:.unshift lib
5
+
6
+ module Yahtzee
7
+ require 'game'
8
+
9
+ module_function
10
+
11
+ def repl
12
+ -> prompt do
13
+ print prompt
14
+ handle_input(gets.chomp!)
15
+ end
16
+ end
17
+
18
+ def handle_input(input)
19
+ begin
20
+ result = eval(input)
21
+ rescue Exception => e
22
+ result = e.message
23
+ end
24
+ puts result.to_s
25
+ end
26
+ end
27
+
28
+ loop do
29
+ Yahtzee.repl[">> "]
30
+ end
31
+
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'yahtzee'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "functional-yahtzee"
8
+ spec.version = Yahtzee::VERSION
9
+ spec.authors = ["Dreamr"]
10
+ spec.email = ["dreamr.okelly@gmail.com"]
11
+ spec.description = %q{Functional yahtzee}
12
+ spec.summary = %q{Yahztee in functional ruby}
13
+ spec.homepage = "http://github.com/dreamr/functional_yahtzee"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake", "~> 10.1"
23
+ spec.add_development_dependency "guard-minitest", "~> 2.0"
24
+ spec.add_development_dependency "coveralls", "~> 0.7"
25
+ end
@@ -0,0 +1,19 @@
1
+ require_relative 'yahtzee'
2
+
3
+ # Yahtzee::Dice is just a dice dsl for yahtzee
4
+ module Yahtzee::Dice
5
+ DIE_FACES = 6
6
+ MAX_DIE_COUNT = 5
7
+
8
+ def self.roll(num)
9
+ limit = num > MAX_DIE_COUNT ? MAX_DIE_COUNT : num
10
+ 1.upto(limit).map {|die| rand(1..DIE_FACES) }
11
+ end
12
+
13
+ def self.reroll(keepers)
14
+ new_roll = keepers + roll(MAX_DIE_COUNT-keepers.count)
15
+ new_roll[0..(MAX_DIE_COUNT-1)]
16
+ end
17
+ end
18
+
19
+
@@ -0,0 +1,5 @@
1
+ module Yahtzee
2
+ module Errors
3
+ class NotAScoreboardPlacementError < Exception; end
4
+ end
5
+ end
@@ -0,0 +1,33 @@
1
+ require 'yahtzee'
2
+ require 'dice'
3
+ require 'round'
4
+ require 'scoring'
5
+
6
+ # This module is the imperative shell of sorts. It provides
7
+ # the 'DSL' of the Yahtzee library and is mostly a wrapper
8
+ # for internals, 'gluing' the application together from a
9
+ # bunch of functions into a cohesive playable game.
10
+ #
11
+ module Yahtzee
12
+ module Game
13
+ include Yahtzee::Errors
14
+ include Yahtzee::Scoring
15
+ include Yahtzee::Dice
16
+
17
+ module_function
18
+
19
+ def first_roll
20
+ Dice.roll(5)
21
+ end
22
+
23
+ def second_roll(keepers)
24
+ Dice.reroll(keepers)
25
+ end
26
+ # tricky - todo: alias_method :third_roll, :second_roll
27
+
28
+ def third_roll(keepers)
29
+ Dice.reroll(keepers)
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ require 'yahtzee'
2
+ require 'dice'
3
+
4
+ # I will need some kind of UI, Yahtzee::Probability aids in
5
+ # the decisions an AI will have to make based on their score
6
+ # card, the dice, and the probability vs possible score
7
+ module Yahtzee::Probability
8
+ module_function
9
+
10
+ def small_straight(dice, rolls_left)
11
+ possibilities = dice.sort.each_cons(3).select do |a,b,c|
12
+ a+1==b && b+1==c
13
+ end.flatten
14
+ straight_possibility_from(dice, possibilities, rolls_left)
15
+ end
16
+
17
+ def large_straight(dice, rolls_left)
18
+ possibilities = dice.sort.each_cons(4).select do |a,b,c,d|
19
+ a+1==b && b+1==c && c+1==d
20
+ end.flatten
21
+ straight_possibility_from(dice, possibilities, rolls_left)
22
+ end
23
+
24
+ private
25
+
26
+ def self.straight_possibility_from(dice, possibilities, rolls_left)
27
+ dice_left = (dice-possibilities).count
28
+ possible_faces = dice_left*Yahtzee::Dice::DIE_FACES
29
+ rolls_left.to_f/possible_faces.to_f
30
+ end
31
+
32
+ end
@@ -0,0 +1,11 @@
1
+ require 'yahtzee'
2
+
3
+ module Yahtzee
4
+ class Round
5
+
6
+ def initialize(&block)
7
+ block.call
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,59 @@
1
+ require 'yahtzee'
2
+
3
+ # Immutable via API, I will work on freezing this later,
4
+ # but notice that when you 'save' you are not altering
5
+ # THIS instance, you return a new instance with the old
6
+ # and the new attributes
7
+ module Yahtzee
8
+ SCORINGS = [:aces, :twos, :threes,
9
+ :fours, :fives, :sixes,
10
+ :upper_subtotal, :upper_total,
11
+ :small_straight, :large_straight,
12
+ :full_house, :three_of_a_kind, :four_of_a_kind,
13
+ :yahtzee, :chance, :bonus_yahtzee_1,
14
+ :bonus_yahtzee_2, :bonus_yahtzee_3,
15
+ :lower_subtotal, :game_total]
16
+
17
+ class ScoreCard < Struct.new(*SCORINGS)
18
+ def initialize(attrs={})
19
+ super *attrs.values_at(*self.class.members)
20
+ end
21
+
22
+ def to_hash
23
+ Yahtzee::SCORINGS.reduce({}) do |hash, attr|
24
+ hash.merge(attr => send(attr))
25
+ end
26
+ end
27
+
28
+ def self.upper_keys
29
+ [:aces, :twos, :threes, :fours, :fives, :sixes]
30
+ end
31
+
32
+ def self.lower_keys
33
+ [:yahtzee, :bonus_yahtzee_1, :bonus_yahtzee_2,
34
+ :bonus_yahtzee_3, :three_of_a_kind,
35
+ :four_of_a_kind, :full_house, :small_straight,
36
+ :large_straight, :chance]
37
+ end
38
+
39
+
40
+ def self.persist(score_card)
41
+ ->(placement, value) {
42
+ old_values = score_card.to_hash
43
+ new_values = { placement => value }
44
+ score_card = new(old_values.merge(new_values))
45
+ }
46
+ end
47
+
48
+ def self.placement_keys
49
+ Yahtzee::SCORINGS - meta_keys
50
+ end
51
+
52
+ def self.meta_keys
53
+ [:bonus_yahtzee_1, :bonus_yahtzee_2,
54
+ :bonus_yahtzee_3, :upper_subtotal,
55
+ :upper_total, :lower_subtotal,
56
+ :game_total]
57
+ end
58
+ end
59
+ end