yeah 0.1.0 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f0c6e5215bc7f40ed78d1fba8db16ff7b0adf9d3
4
- data.tar.gz: ddc2c5ebf36519ba42d7ddffd788dc4d20092534
3
+ metadata.gz: d01dc8962df50d909356224581bb6c5e566991f5
4
+ data.tar.gz: 789fa6d989ad7422f05e1b13fd1c2bea2718c128
5
5
  SHA512:
6
- metadata.gz: 00dcbd4572d660d377624cc12b49412ef26b56844734c3bd43fe93fb24049179531fd956ed63aef47e584b48accbe465239cf5747663ab98db2a9d2773ab206e
7
- data.tar.gz: 50f5ad56743ee6291682f66302cdddb75931502f2a205926dce1041b3012ee6df461453d5f9bbcd00c8d22f71e04f996b4202bf0cf7a12ee0cf83804ee44f30c
6
+ metadata.gz: a0ae07e88b7f9334db886be3caf5497d95528bdeaa69eb82ed24a56885f04426088e1878758732afd0ca33d4d327a640fe30fa38ed156f7ff425591717600ef9
7
+ data.tar.gz: 82e2bdb785e40f172378eeafc06e6c091ec0769fd06e7e8cd64a1806270526e3d18f811e50720efadd816617f73875d6d9f8d62f1702ceea84c4131dd626959b
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## 0.1.0 [2013-09-05]
2
+ * Added basic Vector, Entity and Game classes.
3
+
4
+ ## 0.2.0 [2013-09-21]
5
+ * Added Color, Surface, Rectangle, Map and Desktop classes.
6
+ * Extended Vector, Entity and Game classes.
7
+ * Added Happy Rectangle demo.
data/Gemfile CHANGED
@@ -3,3 +3,4 @@ source 'https://rubygems.org'
3
3
  gem 'rspec'
4
4
  gem 'guard-rspec'
5
5
  gem 'libnotify'
6
+ gem 'rubygame'
data/Gemfile.lock CHANGED
@@ -22,10 +22,13 @@ GEM
22
22
  rb-kqueue (>= 0.2)
23
23
  lumberjack (1.0.4)
24
24
  method_source (0.8.1)
25
+ nice-ffi (0.4)
26
+ ffi (>= 0.5.0)
25
27
  pry (0.9.12.2)
26
28
  coderay (~> 1.0.5)
27
29
  method_source (~> 0.8)
28
30
  slop (~> 3.4)
31
+ rake (10.1.0)
29
32
  rb-fsevent (0.9.3)
30
33
  rb-inotify (0.9.0)
31
34
  ffi (>= 0.5.0)
@@ -39,6 +42,11 @@ GEM
39
42
  rspec-expectations (2.14.2)
40
43
  diff-lcs (>= 1.1.3, < 2.0)
41
44
  rspec-mocks (2.14.3)
45
+ ruby-sdl-ffi (0.4)
46
+ nice-ffi (>= 0.2)
47
+ rubygame (2.6.4)
48
+ rake (>= 0.7.0)
49
+ ruby-sdl-ffi (>= 0.1.0)
42
50
  slop (3.4.5)
43
51
  thor (0.18.1)
44
52
 
@@ -49,3 +57,4 @@ DEPENDENCIES
49
57
  guard-rspec
50
58
  libnotify
51
59
  rspec
60
+ rubygame
data/README.md CHANGED
@@ -1,99 +1,12 @@
1
1
  Yeah
2
2
  ====
3
- The positive video game framework
3
+ A Ruby video game framework that prioritizes happiness and agility over raw performance.
4
4
 
5
- Ruby is a good fit for game development for the following reasons:
6
- * Ruby is optimized for developer happiness, and this framework is, too. A happy developer is a productive developer, which translates into more games made and with greater passion.
7
- * Ruby has great object-oriented syntax that makes a lot of sense for game constructs.
8
- * Ruby's expressiveness reduces development time and makes it easier to experiment.
9
- * Ruby supports operator overloading, which is very handy for the linear algebra that is typically abundant in game code.
10
- * Code testing is wonderful and ubiquitous in the Ruby community, but the game development community has not embraced it. I hope this framework will help change that.
11
- * [Rubygame](https://github.com/rubygame/rubygame/), [Opal](https://github.com/opal/opal), [Ruboto](https://github.com/ruboto/ruboto) and [MobiRuby](https://github.com/mobiruby/mobiruby-ios) would allow games to target the desktop, web, and mobile using the same codebase.
12
- * The Ruby and game development communities are both particularly artistic as far as software development communities go.
5
+ > Premature optimization is the root of all evil. - Donald Knuth
13
6
 
14
- So here's the plan
15
- ------------------
16
-
17
- ### Vector
18
- `Vector` instances represent position, distance, velocity, etc. For the sake of simplicity, all `Vector` instances are three-dimensional and missing components default to 0. This is not problematic for 2D game development because one can treat a three-dimensional vector with a third component of 0 as a two-dimensional vector in practice. `Vector` has method aliases for various synonyms and contexts.
19
-
20
- ```ruby
21
- Vector[] #=> Vector[0, 0, 0]
22
- v1 = Vector[2, 4, 16] #=> Vector[2, 4, 16]
23
- v1[0] #=> 2
24
- v1.height #=> 4
25
- v1.z #=> 16
26
- v2 = Vector[1.5, 2] * 2 #=> Vector[3, 4, 0]
27
- v2.norm #=> 5
28
- v2.magnitude #=> 5
29
- v2.length #=> 5
30
- v2.distance #=> 5
31
- v2.speed #=> 5
32
- ```
33
-
34
- ### Entity
35
- `Entity` instances are objects that behave in the context of a `Game` instance. Each has a `Vector` `position`, and may have a `Vector` `size` and `Visual` `visual`. An `Entity` instance has an `update` method which is continuously called by its `Game` instance. `Entity` is meant to be built upon.
36
-
37
- ```ruby
38
- class Paddle < Entity
39
- def initialize(*args)
40
- super(*args) # attach to Game instance and set up position
41
- @visual = Image.new("gfx/paddle.png")
42
- @speed = 5
43
- @velocity = Vector[]
44
- end
45
-
46
- def update
47
- @velocity.reset # same as `@velocity = Vector[0, 0, 0]`
48
-
49
- if pressing? :left || pressing? :a # if the left arrow key or A key is pressed...
50
- @velocity.x -= @speed # increase @velocity to the left
51
- end
52
-
53
- if pressing? :right || pressing? :d # if right arrow or D is pressed...
54
- @velocity.x += @speed # increase @velocity to the right
55
- end
56
-
57
- if touching? Wall # if next to or intersecting a Wall...
58
- unintersect Wall # fancy function to unintersect from any Wall
59
- @velocity.reset # all of our previous button pressing was for naught
60
- end
61
-
62
- @position += @velocity # move (or perhaps not)
63
- end
64
- end
65
-
66
- p = Paddle.new(20, 30) #=> Paddle at (20, 30, 0)
67
- p.position #=> Vector[20, 30, 0]
68
- p.y #=> 30 # method alias!
69
- ```
70
-
71
- ### Map
72
- Maps are Ruby hashes that can be parsed from JSON. Upon loading a map into a `Game` instance, the game looks for an `entities` key with a value of a hash with keys that correspond to `Entity` subclass names. Each of these keys have a value that is an array of up to 3 numeric elements or an array of said arrays. These numeric arrays represent coordinates at which to instantiate said `Entity` subclasses. Maps can also contain arbitrary data.
73
-
74
- ```ruby
75
- level4 = {
76
- entities: {
77
- Teal: [10, 10], # instantiate Entity subclass Teal at (10, 10, 0)
78
- Behemoth: [ # instantiate Behemoth at three different coordinates
79
- [50, 10],
80
- [100, 10],
81
- [150, 10]
82
- ]
83
- },
84
- name: "Level 4", # arbitrary data
85
- time_limit: 300 # this too
86
- }
87
- ```
88
-
89
- ### Game
90
- A `Game` instance holds `Entity` instances in an `entities` array and runs their `update` methods on each frame. Assigning a map to `map` replaces `entities` with an array of new instances according to the map.
91
-
92
- ```ruby
93
- g = Game.new #=> Game
94
- g.entities #=> []
95
- g.entities << Paddle.new #=> [Paddle]
96
- g.entities #=> [Paddle]
97
- g.map = level4
98
- g.entities #=> [Teal, Behemoth, Behemoth, Behemoth]
99
- ```
7
+ Planned features
8
+ ----------------
9
+ * An opinionated, modular API that makes common tasks easy and everything else possible.
10
+ * Command line initialization script to help one hit the ground running.
11
+ * Graphical tools for creating video game resources like sprites and maps.
12
+ * Multiple target platforms, starting with desktop and web.
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Happy Rectangle
4
+ # A barebones demo for Yeah.
5
+
6
+ lib = File.expand_path('../../lib/', __FILE__)
7
+ $:.unshift lib unless $:.include?(lib)
8
+
9
+ require 'yeah'
10
+ include Yeah
11
+
12
+ class HappyRectangle < Entity
13
+ def initialize(position)
14
+ super position
15
+ @visual = Rectangle.new(Vector[16, 24], Color[255, 255, 0, 255])
16
+ @updates = 0
17
+ @radius = 60
18
+ @center = (Game.new.resolution - @visual.size) / 2
19
+ end
20
+
21
+ def update
22
+ @updates += 1
23
+ @position.x = @center.x + (Math.cos(@updates) * @radius).floor
24
+ @position.y = @center.y + (Math.sin(@updates) * @radius).floor
25
+ end
26
+ end
27
+
28
+ class HappyRectangleGame < Game
29
+ def initialize
30
+ super
31
+ @updates = 0
32
+ @entities << HappyRectangle.new(resolution/2)
33
+ end
34
+
35
+ def update
36
+ super
37
+ @updates += 1
38
+ puts "#{@updates}/60 updates."
39
+ abort if @updates == 60
40
+ end
41
+ end
42
+
43
+ g = HappyRectangleGame.new
44
+ g.start
data/lib/yeah/color.rb ADDED
@@ -0,0 +1,22 @@
1
+ class Yeah::Color
2
+ attr_accessor :rgba_bytes
3
+
4
+ class << self
5
+ alias_method :[], :new
6
+ end
7
+
8
+ def initialize(*arguments)
9
+ arguments = [0, 0, 0, 255] if arguments.empty?
10
+
11
+ case arguments[0]
12
+ when Numeric
13
+ @rgba_bytes = [*arguments]
14
+ when Array
15
+ @rgba_bytes = arguments[0]
16
+ end
17
+ end
18
+
19
+ def ==(other)
20
+ self.class == other.class && @rgba_bytes == other.rgba_bytes ? true : false
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ require 'rubygame'
2
+
3
+ class Yeah::Desktop
4
+ attr_reader :screen, :resolution, :tickrate
5
+
6
+ def initialize(resolution=Vector[320, 240])
7
+ self.resolution = resolution
8
+ @clock = Rubygame::Clock.new
9
+ self.tickrate = 30
10
+ end
11
+
12
+ def resolution=(value)
13
+ @screen = Rubygame::Screen.new(value.components[0..1])
14
+ @resolution = value
15
+ end
16
+
17
+ def render(surface)
18
+ struct = screen.send(:struct)
19
+ struct.pixels.write_string(surface.data(:bgra), surface.data.length)
20
+ screen.update
21
+ end
22
+
23
+ def tickrate=(value)
24
+ @clock.target_framerate = value
25
+ @tickrate = value
26
+ end
27
+
28
+ def each_tick
29
+ loop do
30
+ yield
31
+ @clock.tick
32
+ end
33
+ end
34
+ end
data/lib/yeah/entity.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  class Yeah::Entity
2
- attr_accessor :position
2
+ attr_accessor :position, :visual
3
3
 
4
- def initialize(*position)
5
- @position = Yeah::Vector[*position]
4
+ def initialize(position=Vector[])
5
+ @position = position
6
6
  end
7
7
 
8
8
  def x
@@ -28,4 +28,8 @@ class Yeah::Entity
28
28
  def z=(value)
29
29
  @position.z = value
30
30
  end
31
+
32
+ def draw
33
+ visual.draw if visual
34
+ end
31
35
  end
data/lib/yeah/game.rb CHANGED
@@ -1,11 +1,30 @@
1
1
  class Yeah::Game
2
- attr_accessor :entities
2
+ attr_accessor :entities, :screen, :platform, :resolution
3
3
 
4
4
  def initialize
5
+ @resolution = Vector[320, 240]
6
+ @screen = Surface.new(@resolution)
7
+ @platform = Desktop.new
5
8
  @entities = []
6
9
  end
7
10
 
8
11
  def update
9
12
  @entities.each { |e| e.update }
10
13
  end
14
+
15
+ def draw
16
+ screen.fill(Color[0, 0, 0, 0])
17
+ @entities.each do |entity|
18
+ surface = entity.draw
19
+ screen.draw(surface, entity.position) unless surface.nil?
20
+ end
21
+ platform.render(screen)
22
+ end
23
+
24
+ def start
25
+ platform.each_tick do
26
+ update
27
+ draw
28
+ end
29
+ end
11
30
  end
data/lib/yeah/map.rb ADDED
@@ -0,0 +1,7 @@
1
+ class Yeah::Map
2
+ attr_accessor :background
3
+
4
+ def initialize
5
+ @background = Color[]
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ class Yeah::Rectangle
2
+ attr_accessor :size, :color
3
+
4
+ def initialize(size=Vector[], color=Color[*[255]*4])
5
+ @size = size
6
+ @color = color
7
+ end
8
+
9
+ def draw
10
+ surface = Surface.new(size)
11
+ surface.fill(color)
12
+ surface
13
+ end
14
+ end
@@ -0,0 +1,59 @@
1
+ class Yeah::Surface
2
+ attr_reader :size
3
+ attr_writer :data
4
+
5
+ def initialize(size=Vector[])
6
+ self.size = size
7
+ end
8
+
9
+ def size=(value)
10
+ @size = value
11
+ @data = "\x00" * 4 * size.x * size.y
12
+ end
13
+
14
+ def data(format=:rgba)
15
+ case format
16
+ when :rgba
17
+ @data
18
+ when :bgra
19
+ @data.scan(/.{4}/).map { |p| p[2] + p[1] + p[0] + p[3] }.join
20
+ end
21
+ end
22
+
23
+ def color_at(position)
24
+ data_lines = data.scan(/.{#{size.x*4}}/)
25
+ line = data_lines[position.y]
26
+ color_string = line[position.x*4..position.x*4+3]
27
+ color_bytes = color_string.unpack('H*')[0].
28
+ scan(/.{2}/).map { |b| b.to_i(16) }
29
+ Color[*color_bytes]
30
+ end
31
+
32
+ def fill(color, position1=Vector[0, 0], position2=size-1)
33
+ color_byte_string = color.rgba_bytes.pack('C*')
34
+ data_lines = data.scan(/.{#{size.x*4}}/)
35
+
36
+ rect_width = (position2.x - position1.x).abs + 1
37
+ (position1.y..position2.y).each do |i|
38
+ line = data_lines[i]
39
+ color_bytes_row = color_byte_string * rect_width
40
+ line[position1.x*4...(position2.x+1)*4] = color_bytes_row
41
+ end
42
+
43
+ @data = data_lines.join
44
+ end
45
+
46
+ def draw(surface, position=Vector[0, 0])
47
+ data_lines = data.scan(/.{#{size.x*4}}/)
48
+ surface_data_lines = surface.data.scan(/.{#{surface.size.x*4}}/)
49
+
50
+ (position.y...position.y+surface.size.height).each_with_index do |y, i|
51
+ line = data_lines[y]
52
+ surface_line = surface_data_lines[i]
53
+
54
+ line[position.x*4...(position.x+surface.size.width)*4] = surface_line
55
+ end
56
+
57
+ @data = data_lines.join
58
+ end
59
+ end
data/lib/yeah.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  module Yeah
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
4
4
 
5
- require 'yeah/vector'
6
- require 'yeah/entity'
7
- require 'yeah/game'
5
+ requires = %i(vector color surface rectangle entity map desktop game)
6
+ requires.each do |req|
7
+ require "yeah/#{req}"
8
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Color do
4
+ let(:klass) { described_class }
5
+ let(:instance) { klass.new }
6
+
7
+ it { klass.should be_kind_of Class }
8
+
9
+ [:new, :[]].each do |method_name|
10
+ describe "::#{method_name}" do
11
+ subject(:method) { klass.method(method_name) }
12
+
13
+ it { method.call.should be_instance_of klass }
14
+ it { method.call.rgba_bytes.should eq [0, 0, 0, 255] }
15
+ it { method.call([10, 20, 30, 40]).rgba_bytes.should eq [10, 20, 30, 40] }
16
+ it { method.call([55, 25, 55, 25]).rgba_bytes.should eq [55, 25, 55, 25] }
17
+ it { method.call(10, 20, 30, 40).rgba_bytes.should eq [10, 20, 30, 40] }
18
+ it { method.call(55, 25, 55, 25).rgba_bytes.should eq [55, 25, 55, 25] }
19
+ end
20
+ end
21
+
22
+ describe '#==' do
23
+ subject(:method) { instance.method(:==).unbind }
24
+
25
+ it "is true for itself" do
26
+ color = klass[55, 54, 53, 52]
27
+ method.bind(color).call(color).should eq true
28
+ end
29
+
30
+ it "is true for Color of same value" do
31
+ value = (1..4).map { Random.rand(255) }
32
+ method.bind(klass[value]).call(klass[value]).should eq true
33
+ end
34
+
35
+ it "is false for Color of different value" do
36
+ value = (1..4).map { Random.rand(255) }
37
+ method.bind(klass[value]).call(klass[value.reverse]).should eq false
38
+ end
39
+
40
+ it "is false for nil" do
41
+ method.bind(klass[]).call(nil).should eq false
42
+ end
43
+ end
44
+
45
+ describe '#rgba_bytes' do
46
+ subject(:rgba_bytes) { instance.rgba_bytes }
47
+
48
+ it { should eq [0, 0, 0, 255] }
49
+ end
50
+
51
+ describe '#rgba_bytes=' do
52
+ subject(:method) { instance.method(:rgba_bytes=) }
53
+
54
+ it_behaves_like 'writer', (1..4).map { Random.rand(255) }
55
+ end
56
+ end
@@ -0,0 +1,123 @@
1
+ require 'spec_helper'
2
+
3
+ describe Desktop do
4
+ let(:klass) { described_class }
5
+ let(:instance) { klass.new }
6
+
7
+ it { klass.should be_instance_of Class }
8
+
9
+ describe '::new' do
10
+ subject(:method) { klass.method(:new) }
11
+
12
+ it { method.call.resolution.should eq Vector[320, 240] }
13
+
14
+ it "accepts Vector as resolution" do
15
+ vector = Vector[Random.rand(250), Random.rand(250)]
16
+ desktop = method.call(vector)
17
+ desktop.resolution.should eq vector
18
+ desktop.screen.size.should eq vector[0..1]
19
+ end
20
+
21
+ it { method.call.instance_variables.should include :@clock }
22
+ it do
23
+ clock = method.call.instance_variable_get(:@clock)
24
+ clock.should be_instance_of Rubygame::Clock
25
+ end
26
+ end
27
+
28
+ describe '#screen' do
29
+ subject { instance.screen }
30
+
31
+ it { should be_instance_of Rubygame::Screen }
32
+ its(:size) { should eq [320, 240] }
33
+ end
34
+
35
+ describe '#resolution' do
36
+ subject { instance.resolution }
37
+
38
+ it { should eq Vector[320, 240] }
39
+ end
40
+
41
+ describe '#resolution=' do
42
+ subject(:method) { instance.method(:resolution=) }
43
+
44
+ it_behaves_like 'writer', Vector[Random.rand(250), Random.rand(250)]
45
+
46
+ it "changes screen size" do
47
+ resolution = Vector[Random.rand(250)+1, Random.rand(250)+1]
48
+ method.call(resolution)
49
+ instance.screen.size.should eq resolution.components[0..1]
50
+ end
51
+ end
52
+
53
+ describe '#render' do
54
+ subject(:method) { instance.method(:render) }
55
+
56
+ it { expect { method.call }.to raise_error ArgumentError }
57
+
58
+ it "renders a Surface" do
59
+ surface = Surface.new(instance.resolution)
60
+ surface.fill(Color[255, 255, 0, 255], Vector[0, 0], Vector[1, 1])
61
+
62
+ screen_update_count = 0
63
+ allow(instance.screen).to receive(:update) { screen_update_count += 1 }
64
+
65
+ method.call(surface)
66
+ instance.screen.get_at([0, 0]).should eq [255, 255, 0, 255]
67
+ screen_update_count.should eq 1
68
+ end
69
+ end
70
+
71
+ describe '#each_tick' do
72
+ subject(:method) { instance.method(:each_tick) }
73
+
74
+ it { expect { method.call }.to raise_error LocalJumpError }
75
+
76
+ it "repeatedly calls passed block" do
77
+ call_count = 0
78
+
79
+ method.call do
80
+ call_count += 1
81
+ break if call_count == 5
82
+ end
83
+
84
+ call_count.should eq 5
85
+ end
86
+
87
+ it "calls Rubygame::Clock#tick per call" do
88
+ call_count = 0
89
+ tick_call_count = 0
90
+ allow(instance.instance_variable_get(:@clock)).to receive(:tick) do
91
+ tick_call_count += 1
92
+ end
93
+
94
+ method.call do
95
+ break if tick_call_count == 5
96
+ call_count += 1
97
+ end
98
+
99
+ call_count.should eq 5
100
+ tick_call_count.should eq 5
101
+ end
102
+ end
103
+
104
+ describe '#tickrate' do
105
+ subject { instance.tickrate }
106
+
107
+ its(:round) { should eq 30 }
108
+ end
109
+
110
+ describe '#tickrate=' do
111
+ subject(:method) { instance.method(:tickrate=) }
112
+
113
+ it_behaves_like 'writer', 60
114
+
115
+ it "sets @clock#target_framerate" do
116
+ tickrate = Random.rand(30) + 30
117
+ clock = instance.instance_variable_get(:@clock)
118
+ method.call(tickrate)
119
+
120
+ clock.target_framerate.round.should eq tickrate
121
+ end
122
+ end
123
+ end
data/spec/entity_spec.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Yeah::Entity do
4
- let(:klass) { Yeah::Entity }
3
+ describe Entity do
4
+ let(:klass) { described_class }
5
5
  let(:instance) { klass.new }
6
6
 
7
7
  it { klass.should be_instance_of Class }
@@ -10,23 +10,21 @@ describe Yeah::Entity do
10
10
  subject(:method) { klass.method(:new) }
11
11
 
12
12
  it { method.call.should be_instance_of klass }
13
- it { method.call.position.should eq Yeah::Vector[0, 0, 0] }
14
- it { method.call(2, 4, 8).position.should eq Yeah::Vector[2, 4, 8] }
13
+ it { method.call.position.should eq Vector[0, 0, 0] }
14
+ it { method.call(Vector[2, 4, 8]).position.should eq Vector[2, 4, 8] }
15
15
  end
16
16
 
17
17
  describe '#position' do
18
18
  subject(:position) { instance.position }
19
19
 
20
- it { should be_instance_of Yeah::Vector }
20
+ it { should be_instance_of Vector }
21
21
  it { position.components.should eq [0, 0, 0] }
22
22
  end
23
23
 
24
24
  describe '#position=' do
25
- it "assigns position" do
26
- vector = Yeah::Vector[Random.rand(100)]
27
- instance.position = vector
28
- instance.position.should eq vector
29
- end
25
+ subject(:method) { instance.method(:position=) }
26
+
27
+ it_behaves_like 'writer', Vector[Random.rand(100)]
30
28
  end
31
29
 
32
30
  [:x, :y, :z].each do |method_name|
@@ -45,4 +43,28 @@ describe Yeah::Entity do
45
43
  end
46
44
  end
47
45
  end
46
+
47
+ describe '#visual' do
48
+ subject { instance.visual }
49
+
50
+ it { should eq nil }
51
+ end
52
+
53
+ describe '#visual=' do
54
+ subject { instance.method(:visual=) }
55
+
56
+ it_behaves_like 'writer', Rectangle.new(Vector[50, 50])
57
+ end
58
+
59
+ describe '#draw' do
60
+ subject(:method) { instance.method(:draw) }
61
+
62
+ its(:call) { should eq nil }
63
+
64
+ it "calls #draw method of #visual if it exists" do
65
+ instance.visual = Rectangle.new
66
+ instance.visual.should receive(:draw)
67
+ method.call
68
+ end
69
+ end
48
70
  end
data/spec/game_spec.rb CHANGED
@@ -1,11 +1,29 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Yeah::Game do
4
- let(:klass) { Yeah::Game }
3
+ describe Game do
4
+ let(:klass) { described_class }
5
5
  let(:instance) { klass.new }
6
6
 
7
7
  it { klass.should be_instance_of Class }
8
8
 
9
+ describe '#platform' do
10
+ subject { instance.platform }
11
+
12
+ it { should be_instance_of Desktop }
13
+ end
14
+
15
+ describe '#resolution' do
16
+ subject { instance.resolution }
17
+
18
+ it { should eq Vector[320, 240] }
19
+ end
20
+
21
+ describe '#resolution=' do
22
+ subject { instance.method(:resolution=) }
23
+
24
+ it_behaves_like 'writer', Vector[512, 384]
25
+ end
26
+
9
27
  describe '#entities' do
10
28
  subject(:entities) { instance.entities }
11
29
 
@@ -13,20 +31,73 @@ describe Yeah::Game do
13
31
  end
14
32
 
15
33
  describe '#entities=' do
16
- it "assigns #entities" do
17
- value = [Yeah::Entity.new(Random.rand(100))]
18
- instance.entities = value
19
- instance.entities.should eq value
20
- end
34
+ subject(:method) { instance.method(:entities=) }
35
+
36
+ it_behaves_like 'writer', [Entity.new(Random.rand(10))]
21
37
  end
22
38
 
23
39
  describe '#update' do
40
+ subject(:method) { instance.method(:update) }
41
+
24
42
  it "calls #update of each element in #entities" do
25
- instance.entities = (1..3).map { DummyEntity.new }
26
- update_count = Random.rand(5)
27
- update_count.times { instance.update }
43
+ instance.entities = (1..3).map { Entity.new }
44
+ instance.entities.each { |e| e.should receive(:update) }
45
+ method.call
46
+ end
47
+ end
48
+
49
+ describe '#draw' do
50
+ subject(:method) { instance.method(:draw) }
51
+
52
+ it "clears #screen" do
53
+ instance.screen.should receive(:fill).with(Color[0, 0, 0, 0])
54
+ method.call
55
+ end
56
+
57
+ it "calls #draw of each element in #entities" do
58
+ instance.entities = (1..3).map { Entity.new }
59
+ instance.entities.each { |e| e.should receive(:draw) }
60
+ method.call
61
+ end
62
+
63
+ it "draws entities on #screen" do
64
+ color = Color[0, 255, 0, 255]
65
+ entity = Entity.new
66
+ entity.visual = Rectangle.new(Vector[1, 1], color)
67
+ entity.position = Vector[Random.rand(10), Random.rand(10)]
68
+ instance.entities << entity
69
+ method.call
70
+
71
+ instance.screen.color_at(entity.position).should eq color
72
+ end
73
+
74
+ it "writes to #platform#screen#struct#pixels" do
75
+ pixels = instance.platform.screen.send(:struct).pixels
76
+ pixel_data = pixels.read_string(instance.screen.data.length)
77
+ pixel_data.should eq instance.screen.data
78
+ end
79
+
80
+ it "calls #platform#screen#update" do
81
+ instance.platform.screen.should receive(:update)
82
+ method.call
83
+ end
84
+ end
85
+
86
+ describe '#screen' do
87
+ subject(:screen) { instance.screen }
88
+
89
+ it { should be_instance_of Surface }
90
+ its(:size) { should eq instance.resolution }
91
+ end
92
+
93
+ describe '#start' do
94
+ subject(:method) { instance.method(:start) }
28
95
 
29
- instance.entities.each { |e| e.update_count.should eq update_count }
96
+ it "calls #platform#each_tick with a block with #update and #draw calls" do
97
+ instance.platform.instance_eval "def each_tick; yield; end"
98
+ instance.should receive(:update)
99
+ instance.should receive(:draw)
100
+ method.call
30
101
  end
31
102
  end
32
103
  end
data/spec/map_spec.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Map do
4
+ let(:klass) { described_class }
5
+ let(:instance) { klass.new }
6
+
7
+ it { klass.should be_instance_of Class }
8
+
9
+ describe '#background' do
10
+ subject(:background) { instance.background }
11
+
12
+ it { should eq Color[] }
13
+ end
14
+
15
+ describe '#background=' do
16
+ subject(:method) { instance.method(:background=) }
17
+
18
+ it_behaves_like 'writer', Color[*[Random.rand(255)]*4]
19
+ end
20
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rectangle do
4
+ let(:klass) { described_class }
5
+ let(:instance) { klass.new }
6
+
7
+ it { klass.should be_kind_of Class }
8
+
9
+ describe '::new' do
10
+ subject(:method) { klass.method(:new) }
11
+
12
+ it { method.call.should be_instance_of klass }
13
+ it { method.call.size.should eq Vector[] }
14
+ it { method.call.color.should eq Color[255, 255, 255, 255] }
15
+
16
+ it "assigns Vector first argument as #size" do
17
+ vector = Vector[Random.rand(40)]
18
+ method.call(vector).size.should eq vector
19
+ end
20
+
21
+ it "assigns second argument Color as #color" do
22
+ byte_array = (1..4).map { Random.rand(255) }
23
+ color = Color[*byte_array]
24
+ method.call(Vector[], color).color.should eq color
25
+ end
26
+ end
27
+
28
+ describe '#size' do
29
+ subject(:size) { instance.size }
30
+
31
+ it { should eq Vector[] }
32
+ end
33
+
34
+ describe '#size=' do
35
+ subject(:method) { instance.method(:size=) }
36
+
37
+ it_behaves_like 'writer', Vector[Random.rand(40)]
38
+ end
39
+
40
+ describe '#color' do
41
+ subject(:color) { instance.color }
42
+
43
+ it { should eq Color[255, 255, 255, 255] }
44
+ end
45
+
46
+ describe '#color=' do
47
+ subject(:method) { instance.method(:color=) }
48
+
49
+ it_behaves_like 'writer', Color[*[Random.rand(255)]*4]
50
+ end
51
+
52
+ describe '#draw' do
53
+ subject(:method) { instance.method(:draw) }
54
+
55
+ its(:call) { should be_instance_of Surface }
56
+
57
+ it "matches size" do
58
+ instance.size = Vector[Random.rand(49)+1, 50]
59
+ method.call.size.should eq instance.size
60
+ end
61
+
62
+ it "matches color" do
63
+ instance.size = Vector[10, 10]
64
+ instance.color = Color[*[Random.rand(255)]*4]
65
+ surface = method.call
66
+ surface.color_at(Vector[]).should eq instance.color
67
+ surface.color_at(instance.size/2).should eq instance.color
68
+ surface.color_at(instance.size-Vector[1, 1]).should eq instance.color
69
+ end
70
+ end
71
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,14 +1,13 @@
1
1
  require 'yeah'
2
+ include Yeah
2
3
 
3
- class DummyEntity < Yeah::Entity
4
- attr_reader :update_count
4
+ shared_examples 'writer' do |value|
5
+ it "assigns its reader" do
6
+ writer = subject
7
+ reader_name = writer.name[0..-2].to_sym
8
+ reader = writer.receiver.method(reader_name)
5
9
 
6
- def initialize
7
- super
8
- @update_count = 0
9
- end
10
-
11
- def update
12
- @update_count += 1
10
+ writer.call(value)
11
+ reader.call.should eq value
13
12
  end
14
13
  end
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+
3
+ describe Surface do
4
+ let(:klass) { described_class }
5
+ let(:vector) { Vector[Random.rand(48)+2, Random.rand(48)+2] }
6
+ let(:instance) { klass.new(vector) }
7
+
8
+ describe '::new' do
9
+ subject(:method) { klass.method :new }
10
+
11
+ it { method.call.size.should eq Vector[] }
12
+
13
+ it "accepts a Vector size" do
14
+ surface = method.call(vector)
15
+ surface.size.should eq vector
16
+ end
17
+ end
18
+
19
+ describe '#size' do
20
+ subject { instance.size }
21
+
22
+ it { should eq vector }
23
+ end
24
+
25
+ describe '#size=' do
26
+ subject(:method) { instance.method(:size=) }
27
+
28
+ it_behaves_like 'writer', Vector[20, 20]
29
+ end
30
+
31
+ describe '#data' do
32
+ subject(:method) { instance.method(:data) }
33
+ let(:data) { instance.data }
34
+
35
+ it "has length of #size.x * #size.y * 4" do
36
+ instance.size = instance.size * 2
37
+ expected_length = instance.size.x * instance.size.y * 4
38
+ data.length.should eq expected_length
39
+ end
40
+
41
+ it "is a series of \x00\x00\x00\x00 by default" do
42
+ pixels = data.unpack('H*')[0].scan(/.{8}/)
43
+ pixels.uniq.size.should eq 1
44
+ pixels.uniq.last.should eq "00000000"
45
+ end
46
+
47
+ it "accepts format param" do
48
+ instance.data = "\x00\x11\x22\x33"
49
+ method.call(:rgba).should eq "\x00\x11\x22\x33"
50
+ method.call(:bgra).should eq "\x22\x11\x00\x33"
51
+ end
52
+ end
53
+
54
+ describe '#data=' do
55
+ subject(:method) { instance.method(:data=) }
56
+
57
+ it "assigns hex data of length size.x * size.y * 4" do
58
+ data = "\xFF" * instance.size.x * instance.size.y * 4
59
+ method.call(data)
60
+ instance.data.should eq data
61
+ end
62
+ end
63
+
64
+ describe '#color_at' do
65
+ subject(:method) { instance.method(:color_at) }
66
+
67
+ it { expect { method.call }.to raise_error ArgumentError }
68
+
69
+ it "matches the color of the pixel at position" do
70
+ method.call(vector/2).should eq Color[0, 0, 0, 0]
71
+ end
72
+ end
73
+
74
+ describe '#fill' do
75
+ subject(:method) { instance.method(:fill) }
76
+ let(:color2) { Color[0, 255, 0, 255] }
77
+
78
+ it { expect { method.call }.to raise_error ArgumentError }
79
+
80
+ it "changes color of rectangular area with position args" do
81
+ method.call(color2, Vector[], vector/2)
82
+ instance.color_at(Vector[]).should eq color2
83
+ instance.color_at(vector/2).should eq color2
84
+ instance.color_at(vector/2 + Vector[1, 0]).should eq Color[0, 0, 0, 0]
85
+ instance.color_at(vector/2 + Vector[0, 1]).should eq Color[0, 0, 0, 0]
86
+ instance.data.length.should eq instance.size.x * instance.size.y * 4
87
+ end
88
+
89
+ it "changes color of entire surface without position args" do
90
+ method.call(color2)
91
+ instance.color_at(Vector[]).should eq color2
92
+ instance.color_at(vector/2).should eq color2
93
+ instance.color_at(vector-1).should eq color2
94
+ instance.data.length.should eq instance.size.x * instance.size.y * 4
95
+ end
96
+ end
97
+
98
+ describe '#draw' do
99
+ subject(:method) { instance.method(:draw) }
100
+ let(:color) { Color[0, Random.rand(255), Random.rand(255), 255] }
101
+
102
+ it { expect { method.call }.to raise_error ArgumentError }
103
+
104
+ it "draws surface at position" do
105
+ surface = Surface.new(Vector[1, 1])
106
+ surface.fill(color)
107
+ surface2 = Surface.new(Vector[10, 10])
108
+ surface2.draw(surface, Vector[1, 1])
109
+
110
+ surface2.color_at(Vector[0, 0]).should eq Color[0, 0, 0, 0]
111
+ surface2.color_at(Vector[1, 1]).should eq color
112
+ surface2.color_at(Vector[2, 2]).should eq Color[0, 0, 0, 0]
113
+ end
114
+
115
+ it "draws surface at (0, 0) by default" do
116
+ surface = Surface.new(Vector[1, 1])
117
+ surface.fill(color)
118
+ surface2 = Surface.new(Vector[10, 10])
119
+ surface2.draw(surface)
120
+
121
+ surface2.color_at(Vector[0, 0]).should eq color
122
+ surface2.color_at(Vector[1, 1]).should eq Color[0, 0, 0, 0]
123
+ end
124
+
125
+ it "draws a rectangular area" do
126
+ surface = Surface.new(Vector[3, 3])
127
+ surface.fill(color)
128
+ surface2 = Surface.new(Vector[5, 5])
129
+ surface2.draw(surface, Vector[1, 1])
130
+
131
+ surface2.color_at(Vector[1, 1]).should eq color
132
+ surface2.color_at(Vector[1, 3]).should eq color
133
+ surface2.color_at(Vector[3, 1]).should eq color
134
+ surface2.color_at(Vector[3, 3]).should eq color
135
+ surface2.color_at(Vector[0, 3]).should eq Color[0, 0, 0, 0]
136
+ surface2.color_at(Vector[3, 0]).should eq Color[0, 0, 0, 0]
137
+ surface2.color_at(Vector[4, 3]).should eq Color[0, 0, 0, 0]
138
+ surface2.color_at(Vector[3, 4]).should eq Color[0, 0, 0, 0]
139
+ end
140
+ end
141
+ end
data/spec/vector_spec.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Yeah::Vector do
4
- let(:klass) { Yeah::Vector }
5
- let(:arguments) { (1..3).map { Random.rand(100) } }
3
+ describe Vector do
4
+ let(:klass) { described_class }
5
+ let(:arguments) { (1..3).map { Random.rand(100)+1 } }
6
6
  let(:instance) { klass.new(*arguments) }
7
7
 
8
8
  it { klass.should be_instance_of Class }
@@ -104,10 +104,10 @@ describe Yeah::Vector do
104
104
 
105
105
  it "subtracts Numeric" do
106
106
  subtrahend = Random.rand(100)
107
- difference = instance + subtrahend
107
+ difference = instance - subtrahend
108
108
 
109
109
  difference.components.each_with_index do |component, i|
110
- component.should eq instance.components[i] + subtrahend
110
+ component.should eq instance.components[i] - subtrahend
111
111
  end
112
112
  end
113
113
  end
@@ -144,7 +144,7 @@ describe Yeah::Vector do
144
144
  end
145
145
 
146
146
  it "divides by Numeric" do
147
- divisor = Random.rand(100)
147
+ divisor = Random.rand(100) + 1
148
148
  quotient = instance / divisor
149
149
 
150
150
  quotient.components.each_with_index do |component, i|
data/spec/yeah_spec.rb CHANGED
@@ -1,12 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Yeah do
4
- it "is a Module" do
5
- Yeah.should be_instance_of Module
6
- end
4
+ let(:modjul) { described_class }
5
+
6
+ it { modjul.should be_instance_of Module }
7
7
 
8
8
  describe '::VERSION' do
9
- subject { Yeah::VERSION }
9
+ subject { modjul::VERSION }
10
10
 
11
11
  it { should be_instance_of String }
12
12
  it { should match /[0-9]+\.[0-9]+\.[0-9]+/ }
data/yeah-0.1.0.gem ADDED
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yeah
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artur Ostrega
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-05 00:00:00.000000000 Z
11
+ date: 2013-09-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Programming video games should have a positive impact on your health!
14
14
  email: skoofoo@gmail.com
@@ -18,18 +18,30 @@ extra_rdoc_files: []
18
18
  files:
19
19
  - Gemfile
20
20
  - Guardfile
21
+ - lib/yeah/map.rb
21
22
  - lib/yeah/game.rb
23
+ - lib/yeah/color.rb
24
+ - lib/yeah/surface.rb
22
25
  - lib/yeah/vector.rb
26
+ - lib/yeah/desktop.rb
27
+ - lib/yeah/rectangle.rb
23
28
  - lib/yeah/entity.rb
24
29
  - lib/yeah.rb
25
30
  - README.md
26
- - yeah-0.0.2.gem
31
+ - CHANGELOG.md
27
32
  - tmp/rspec_guard_result
28
33
  - Gemfile.lock
29
34
  - yeah.gemspec
30
35
  - LICENSE.txt
36
+ - demo/happy_rectangle.rb
37
+ - yeah-0.1.0.gem
31
38
  - spec/yeah_spec.rb
39
+ - spec/color_spec.rb
40
+ - spec/desktop_spec.rb
32
41
  - spec/game_spec.rb
42
+ - spec/rectangle_spec.rb
43
+ - spec/surface_spec.rb
44
+ - spec/map_spec.rb
33
45
  - spec/spec_helper.rb
34
46
  - spec/entity_spec.rb
35
47
  - spec/vector_spec.rb
data/yeah-0.0.2.gem DELETED
Binary file