yeah 0.2.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +52 -20
  4. data/bin/yeah +30 -0
  5. data/lib/yeah.rb +6 -9
  6. data/lib/yeah/_platform/asset.rb +24 -0
  7. data/lib/yeah/_platform/display.rb +181 -0
  8. data/lib/yeah/_platform/image.rb +16 -0
  9. data/lib/yeah/_platform/keyboard.rb +37 -0
  10. data/lib/yeah/_platform/mouse.rb +30 -0
  11. data/lib/yeah/_platform/sound.rb +15 -0
  12. data/lib/yeah/_platform/ticker.rb +21 -0
  13. data/lib/yeah/_template/Gemfile +4 -0
  14. data/lib/yeah/_template/code/code.rb +4 -0
  15. data/lib/yeah/_template/code/game.rb +7 -0
  16. data/lib/yeah/_web.rb +9 -0
  17. data/lib/yeah/color.rb +45 -13
  18. data/lib/yeah/constants.rb +6 -0
  19. data/lib/yeah/game.rb +89 -41
  20. data/lib/yeah/vector.rb +122 -99
  21. data/lib/yeah/version.rb +3 -0
  22. data/lib/yeah/web/dependencies.rb +2 -0
  23. data/lib/yeah/web/runner.html.erb +61 -0
  24. data/lib/yeah/web/server.rb +60 -0
  25. data/lib/yeah/web/setup.rb +5 -0
  26. data/lib/yeah/web/start.rb +1 -0
  27. data/opal/yeah/web.rb +8 -0
  28. data/opal/yeah/web/asset.opal +38 -0
  29. data/opal/yeah/web/constants.opal +5 -0
  30. data/opal/yeah/web/display.opal +244 -0
  31. data/opal/yeah/web/image.opal +23 -0
  32. data/opal/yeah/web/keyboard.opal +139 -0
  33. data/opal/yeah/web/mouse.opal +58 -0
  34. data/opal/yeah/web/sound.opal +19 -0
  35. data/opal/yeah/web/ticker.opal +39 -0
  36. metadata +111 -19
  37. data/CHANGELOG.md +0 -28
  38. data/lib/monkey/numeric.rb +0 -6
  39. data/lib/yeah/basic_physics.rb +0 -11
  40. data/lib/yeah/desktop.rb +0 -72
  41. data/lib/yeah/entity.rb +0 -137
  42. data/lib/yeah/map.rb +0 -40
  43. data/lib/yeah/rectangle.rb +0 -21
  44. data/lib/yeah/surface.rb +0 -66
@@ -0,0 +1,21 @@
1
+ module Yeah
2
+
3
+ # The `Ticker` provides a way to start a game loop.
4
+ # @example Print elapsed time between each tick
5
+ # Ticker.new.on_tick { |elapsed| puts elapsed }
6
+ # @abstract Provided by a `Platform`.
7
+ class Ticker
8
+ # @param [Hash] options for new object
9
+ # @option options [Numeric] :rate (60) at ticks per second
10
+ def initialize(options = {})
11
+ raise NotImplementedError
12
+ end
13
+
14
+ # @!attribute [r] rate
15
+ # @return [Numeric] ticks per second intended
16
+
17
+ # @!method on_tick
18
+ # @yield [elapsed] block to execute per tick
19
+ # @yieldparam [Numeric] elapsed time since last tick
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'yeah', '~> 0.3.3'
4
+ gem 'opal', github: 'opal/opal', ref: '7e843b0'
@@ -0,0 +1,4 @@
1
+ # Entry point
2
+ # Specify dependencies and game code require order here.
3
+
4
+ require 'game'
@@ -0,0 +1,7 @@
1
+ class NewGame < Game
2
+ def setup
3
+ end
4
+
5
+ def update(elapsed)
6
+ end
7
+ end
data/lib/yeah/_web.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Yeah
2
+
3
+ # `Web`, through [Opal](http://opalrb.org/), compiles and runs Yeah games on
4
+ # the web.
5
+ #
6
+ # The `Display` is powered by HTML Canvas.
7
+ module Web
8
+ end
9
+ end
data/lib/yeah/color.rb CHANGED
@@ -1,25 +1,57 @@
1
- # Color.
2
- class Yeah::Color
3
- # @!attribute rgba_bytes
4
- # @return [Array<(Integer, Integer, Integer, Integer)>] red, green, blue,
5
- # alpha bytes
6
- attr_accessor :rgba_bytes
1
+ module Yeah
7
2
 
3
+ # A `Color` represents a color.
4
+ #
5
+ # `C` is an alias for `Color`.
6
+ # @example Comparing two colors
7
+ # C[100, 100, 100] == C['#646464']
8
+ # # => true
9
+ class Color
8
10
  class << self
9
- alias_method :[], :new
11
+ # @param arguments catch-all
12
+ # @return [Vector]
13
+ # Alias for ::new.
14
+ def [](*args)
15
+ new(*args)
16
+ end
10
17
  end
11
18
 
12
- def initialize(*values)
13
- default_values = [0, 0, 0, 255]
14
- values += default_values[values.size..-1]
15
- @rgba_bytes = values
19
+ # @return [Array] color value in RGB format
20
+ attr_reader :value
21
+
22
+ # @overload initialize(red, green, blue)
23
+ # @param [Numeric] red value, from 0 to 255
24
+ # @param [Numeric] green value, from 0 to 255
25
+ # @param [Numeric] blue value, from 0 to 255
26
+ # @overload initialize(hex_string)
27
+ # @param [String] hex string in '#dadada' or '#fff' format
28
+ def initialize(*args)
29
+ if args[0].respond_to?(:[]) && args[0][0] == '#' # hex string
30
+ if args[0].length == 4 # short hex
31
+ @value = args[0][1..3].chars.map { |h| "#{h}#{h}".to_i(16) }
32
+ else # normal hex
33
+ @value = args[0][1..6].scan(/../).map { |h| h.to_i(16) }
34
+ end
35
+ else
36
+ @value = args
37
+ end
16
38
  end
17
39
 
40
+ # @return [String] readable representation
18
41
  def inspect
19
- "#{self.class.name}[#{rgba_bytes.join(', ')}]"
42
+ "#{self.class.name}#{value.to_s}"
20
43
  end
21
44
 
45
+ # @return [Boolean] whether self matches other color
22
46
  def ==(other)
23
- self.class == other.class && @rgba_bytes == other.rgba_bytes
47
+ value == other.value
48
+ end
49
+
50
+ # @return [String] color value as a hex string
51
+ def to_hex
52
+ "##{value.map { |v| v.to_s(16).rjust(2, '0') }.join }"
24
53
  end
25
54
  end
55
+ end
56
+
57
+ Yeah::C = Yeah::Color
@@ -0,0 +1,6 @@
1
+ module Yeah
2
+ DEFAULT_DISPLAY_SIZE = V[1280, 720]
3
+ DEFAULT_DISPLAY_FONT_FAMILY = 'DejaVu Serif'
4
+ DEFAULT_DISPLAY_FONT_SIZE = 36
5
+ DEFAULT_TICKER_RATE = 60
6
+ end
data/lib/yeah/game.rb CHANGED
@@ -1,54 +1,102 @@
1
- # Manages entities.
2
- class Yeah::Game
3
- # @!attribute resolution
4
- # @return [Vector] size of screen
5
- # @!attribute screen
6
- # @return [Surface] visual render
7
- # @!attribute [r] platform
8
- # @return [Platform] underlying platform bindings
9
- # @!attribute entities
10
- # @return [Array] active entities
11
- attr_accessor :resolution, :screen
12
- attr_reader :entities, :platform
13
-
14
- def initialize
15
- @resolution = V[320, 180]
16
- @screen = Surface.new(@resolution)
17
- @platform = Desktop.new
18
- @entities = []
19
- end
1
+ module Yeah
2
+
3
+ # A `Game` is subclassed to create the core of a game project.
4
+ #
5
+ # A project's `Game` is instantiated at runtime. Its {#setup} is called one
6
+ # time, then {#update} is called repeatedly.
7
+ #
8
+ # I/O objects are provided and can be used to build a modern video game.
9
+ # @abstract Subclass this to make a game.
10
+ class Game
11
+ class << self
12
+ # @param [Hash] configuration used for new game instances
13
+ # @option configuration [Hash] :ticker options
14
+ # @option configuration [Hash] :display options
15
+ # @option configuration [Hash] :keyboard options
16
+ # @option configuration [Hash] :mouse options
17
+ # @return [Hash] configuration for new game instances
18
+ def config
19
+ @config ||= {
20
+ ticker: {},
21
+ display: {},
22
+ keyboard: {},
23
+ mouse: {}
24
+ }
25
+ end
26
+
27
+ # @return [Game] default subclass (i.e. project game)
28
+ def default
29
+ subclasses.last
30
+ end
20
31
 
21
- # Start the game loop.
22
- def start
23
- platform.each_tick do
24
- update
25
- draw
26
- break if @stopped
32
+ private
33
+
34
+ def inherited(klass)
35
+ subclasses << klass
36
+
37
+ super
27
38
  end
39
+
40
+ def subclasses
41
+ @@subclasses ||= []
42
+ end
43
+ end
44
+
45
+ # @return [Ticker] ticker used for game loop and one-shot input
46
+ attr_reader :ticker
47
+
48
+ # @return [Display] display for drawing
49
+ attr_reader :display
50
+
51
+ # @return [Keyboard] keyboard input
52
+ attr_reader :keyboard
53
+
54
+ # @return [Mouse] mouse input
55
+ attr_reader :mouse
56
+
57
+ # @param [Hash] options
58
+ # @option options [Ticker] :ticker
59
+ # @option options [Display] :display
60
+ # @option options [Keyboard] :keyboard
61
+ # @option options [Mouse] :mouse
62
+ def initialize(options = {})
63
+ options = defaults.merge(options)
64
+ @ticker = options[:ticker]
65
+ @display = options[:display]
66
+ @keyboard = options[:keyboard]
67
+ @mouse = options[:mouse]
68
+
69
+ setup
70
+
71
+ @ticker.on_tick { |e| update(e) }
28
72
  end
29
73
 
30
- # Stop the game loop.
31
- def stop
32
- @stopped = true
74
+ # @abstract
75
+ # Setup game at start.
76
+ def setup
33
77
  end
34
78
 
35
- def entities=(value)
36
- @entities = value
37
- @entities.each { |e| e.game = self }
79
+ # @param [Numeric] elapsed time since last update
80
+ # @abstract
81
+ # Update game on tick.
82
+ def update(elapsed)
38
83
  end
39
84
 
40
- protected
85
+ private
41
86
 
42
- def update
43
- @entities.each { |e| e.update }
87
+ def config
88
+ self.class.config
44
89
  end
45
90
 
46
- def draw
47
- screen.fill(Color[0, 0, 0, 0])
48
- @entities.each do |entity|
49
- surface = entity.draw
50
- screen.draw(surface, entity.position) unless surface.nil?
51
- end
52
- platform.render(screen)
91
+ def defaults
92
+ ticker = Ticker.new(config[:ticker])
93
+
94
+ {
95
+ ticker: ticker,
96
+ display: Display.new(config[:display]),
97
+ keyboard: Keyboard.new({ ticker: ticker }.merge(config[:keyboard])),
98
+ mouse: Mouse.new({ ticker: ticker }.merge(config[:mouse]))
99
+ }
53
100
  end
54
101
  end
102
+ end
data/lib/yeah/vector.rb CHANGED
@@ -1,137 +1,160 @@
1
- # Three-dimensional geometric vector. Used as position or size.
2
- class Yeah::Vector
3
- # @!attribute components
4
- # @return [Array<(Numeric, Numeric, Numeric)>]
5
- # @!attribute [r] to_a
6
- # @see components
7
- # @!attribute []
8
- # @param [Integer] *n* of component
9
- # @return [Numeric] *n*th component
10
- attr_reader :components
11
- alias_method :to_a, :components
1
+ require 'forwardable'
2
+
3
+ module Yeah
4
+
5
+ # The `Vector` represents a mathematical vector. It can be used to describe
6
+ # a position, velocity or direction.
7
+ #
8
+ # `V` is an alias for `Vector`.
9
+ #
10
+ # @example Basic vector math
11
+ # V[5, 5] + V[10, 20]
12
+ # # => Yeah::Vector[15, 25, 0]
13
+ #
14
+ # V[3, 6, 9] / 3
15
+ # # => Yeah::Vector[1, 2, 3]
16
+ class Vector
17
+ extend Forwardable
12
18
 
13
- def self.random(*component_maxes)
14
- components = component_maxes.map { |cm| Random.rand(cm) }
15
- self.new(*components)
19
+ class << self
20
+ # @param arguments catch-all
21
+ # @return [Vector]
22
+ # Alias for ::new.
23
+ def [](*args)
24
+ new(*args)
25
+ end
16
26
  end
17
27
 
18
- def initialize(*components)
19
- if components.size > 3
20
- error_message = "too many arguments (#{components.size} for up to 3)"
21
- raise ArgumentError, error_message
22
- end
28
+ # @return [Array] vector components
29
+ attr_accessor :components
23
30
 
24
- self.components = components
31
+ # @!attribute []
32
+ # @return [Numeric] *n*th component value
33
+ def_delegators :@components, :[]
34
+
35
+ # @param [Numeric] first component, defaults to 0
36
+ # @param [Numeric] second component, defaults to 0
37
+ # @param [Numeric] third component, defaults to 0
38
+ def initialize(*components)
39
+ @components = components + [0] * (3 - components.size)
25
40
  end
26
41
 
42
+ # @return [String] readable representation
27
43
  def inspect
28
- "#{self.class.name}[#{components.join(', ')}]"
44
+ "#{self.class.name}#{@components.to_s}"
29
45
  end
30
46
 
31
- def components=(values)
32
- if values.size > 3
33
- error_message = "too many elements (#{values.size} for up to 3)"
34
- raise ArgumentError, error_message
35
- end
47
+ # @!attribute x
48
+ # @param [Numeric] first component value
49
+ # @return [Numeric] first component value
36
50
 
37
- @components = values + [0] * (3 - values.size)
38
- end
51
+ # @!attribute y
52
+ # @param [Numeric] second component value
53
+ # @return [Numeric] second component value
39
54
 
40
- class << self
41
- alias_method :[], :new
42
-
43
- def define_component_helpers
44
- component_name_sets = [[:x, :width], [:y, :height], [:z, :depth]]
45
- component_name_sets.each_with_index do |set, ci|
46
- set.each do |name|
47
- define_method(name) { @components[ci] }
48
- define_method("#{name}=") { |val| @components[ci] = val }
49
- end
50
- end
51
- end
55
+ # @!attribute z
56
+ # @param [Numeric] third component value
57
+ # @return [Numeric] third component value
58
+ %i[x y z].each_with_index do |component, i|
59
+ define_method(component) { @components[i] }
60
+ define_method("#{component}=") { |v| @components[i] = v }
52
61
  end
53
62
 
54
- define_component_helpers
63
+ # @return [Numeric] vector's length
64
+ def length
65
+ Math.sqrt(@components[0] ** 2 + @components[1] ** 2 + @components[2] ** 2)
66
+ end
55
67
 
56
- def ==(other)
57
- other.class == self.class && @components == other.components ? true : false
68
+ # @return [Vector] vector of same direction whose length is 1
69
+ def normalize
70
+ self / magnitude
58
71
  end
59
72
 
60
- def [](index)
61
- @components[index]
73
+ # @param [Vector] vector to add
74
+ # @return [Vector] vector added into other vector
75
+ def +(vector)
76
+ self.class.new(*
77
+ [@components, vector.components].transpose.map { |cc|
78
+ cc.reduce(:+) })
62
79
  end
63
80
 
64
- def []=(index, value)
65
- @components[index] = value
81
+ # @param [Vector] vector to subtract
82
+ # @return [Vector] vector from which other vector was subtracted
83
+ def -(vector)
84
+ self.class.new(*
85
+ [@components, vector.components].transpose.map { |cc|
86
+ cc.reduce(:-) })
66
87
  end
67
88
 
68
- def +(addend)
69
- case addend
70
- when self.class
71
- comp_addends = addend.components
72
- when Numeric
73
- comp_addends = [addend] * 3
74
- end
75
- components = @components.zip(comp_addends).map { |c| c.reduce(:+) }
89
+ # @param [Numeric] numeric value to multiply vector
90
+ # @return [Vector] vector multiplied by a numeric value
91
+ def *(numeric)
92
+ self.class.new(*@components.map { |c| c * numeric })
93
+ end
76
94
 
77
- self.class[*components]
95
+ # @param [Numeric] numeric value to divide vector
96
+ # @return [Vector] vector divided by a numeric value
97
+ def /(numeric)
98
+ self.class.new(*@components.map { |c| c / numeric })
78
99
  end
79
100
 
80
- def -(subtrahend)
81
- case subtrahend
82
- when self.class
83
- comp_subtrahends = subtrahend.components
84
- when Numeric
85
- comp_subtrahends = [subtrahend] * 3
86
- end
87
- components = @components.zip(comp_subtrahends).map { |c| c.reduce(:-)}
101
+ # @return [Vector] identical vector
102
+ def +@
103
+ self.class.new(*@components)
104
+ end
88
105
 
89
- self.class[*components]
106
+ # @return [Vector] negative vector
107
+ def -@
108
+ self.class.new(*@components.map(&:-@))
90
109
  end
91
110
 
92
- def *(multiple)
93
- case multiple
94
- when self.class
95
- comp_multiples = multiple.components
96
- when Numeric
97
- comp_multiples = [multiple] * 3
98
- end
111
+ # @param [Vector] position
112
+ # @return [Numeric] distance to a position
113
+ def distance_to(position)
114
+ Math.sqrt((x - position.x) ** 2 +
115
+ (y - position.y) ** 2 +
116
+ (z - position.z) ** 2)
117
+ end
99
118
 
100
- components = @components.zip(comp_multiples).map { |c| c.reduce(:*) }
101
- self.class[*components]
119
+ # @param [Vector] position
120
+ # @return [Numeric] angle to 2D position, in radians
121
+ def angle_to(position)
122
+ diff = position - self
123
+ Math.atan2(diff.y, diff.x)
102
124
  end
103
125
 
104
- def /(divisor)
105
- case divisor
106
- when self.class
107
- comp_divisors = divisor.components
108
- when Numeric
109
- comp_divisors = [divisor] * 3
110
- end
126
+ # @param [Numeric] angle to move along, in radians
127
+ # @param [Numeric] distance to move
128
+ # @return [Vector] position moved along an angle for a distance in 2D
129
+ def along(angle, distance)
130
+ self.class.new(x + Math.cos(angle) * distance,
131
+ y + Math.sin(angle) * distance)
132
+ end
111
133
 
112
- components = @components.zip(comp_divisors).map { |c| c.reduce(:/) }
113
- self.class[*components]
134
+ # @param (see #along)
135
+ # @return [Vector] self after moving along an angle for a distance in 2D
136
+ def along!(angle, distance)
137
+ self.x += Math.cos(angle) * distance
138
+ self.y += Math.sin(angle) * distance
139
+ self
114
140
  end
115
141
 
116
- # @return [Numeric]
117
- def magnitude
118
- Math.sqrt(@components.reduce(0) { |m, c| m + c*c })
142
+ # @param [Vector] position to move to
143
+ # @param [Vector] distance to move
144
+ # @return [Vector] position moved toward other position for a distance in 2D
145
+ # @todo Make work in 3D.
146
+ def toward(position, distance)
147
+ along angle_to(position), amount
119
148
  end
120
- # @!attribute length
121
- # @see magnitude
122
- # @!attribute distance
123
- # @see magnitude
124
- # @!attribute speed
125
- # @see magnitude
126
- alias_method :length, :magnitude
127
- alias_method :distance, :magnitude
128
- alias_method :speed, :magnitude
129
149
 
130
- # Reset every component to 0.
131
- def reset
132
- @components = [0, 0, 0]
150
+ # @param (see #toward)
151
+ # @return [Vector] self after moving toward other position for a distance in
152
+ # 2D
153
+ # @todo Make work in 3D.
154
+ def toward!(position, amount)
155
+ along! angle_to(position), amount
133
156
  end
134
157
  end
158
+ end
135
159
 
136
- # Shorthand for Vector.
137
160
  Yeah::V = Yeah::Vector