yeah 0.1.0 → 0.2.0

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