joyce 0.1.2

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: 17b6e2acc99201cb764f1396588e676e9ea884d9
4
+ data.tar.gz: 8b762837b8aaffcfe6383e2634132e4050d3858e
5
+ SHA512:
6
+ metadata.gz: 83f8879e432dfd00cfaaa771d71f55d87112d6ec631af93ce5bcb46bbc91d4415b73dcc47ef06cee20f07b32b36315723783357322441b052eec750df8f2f387
7
+ data.tar.gz: 36053b131206f044b7e6587f09c34e166d04df90c01648d068149da0373f7fc2988821116236821d8eb98abc4a6b47fc97f2cd8dcaa59fb1319c887a6eef7429
@@ -0,0 +1,3 @@
1
+ -
2
+ ChangeLog.md
3
+ LICENSE.txt
@@ -0,0 +1,6 @@
1
+ /.bundle
2
+ /.yardoc/
3
+ /Gemfile.lock
4
+ /doc/
5
+ /pkg/
6
+ /vendor/cache/*.gem
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour --format documentation --tag ~@flaky
@@ -0,0 +1 @@
1
+ --markup markdown --title "joyce Documentation" --protected
@@ -0,0 +1,4 @@
1
+ ### 0.1.0 / 2016-04-07
2
+
3
+ * Initial release:
4
+
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'gosu'
6
+ gem 'metacosm'
7
+
8
+ group :development do
9
+ gem 'kramdown'
10
+ gem 'pry'
11
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2016 Joseph Weissman
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,241 @@
1
+ # joyce
2
+
3
+
4
+ * [Homepage](https://rubygems.org/gems/joyce)
5
+ * [Documentation](http://rubydoc.info/gems/joyce/frames)
6
+ * [Email](mailto:jweissman1986 at gmail.com)
7
+
8
+ [![Code Climate GPA](https://codeclimate.com/github//joyce/badges/gpa.svg)](https://codeclimate.com/github//joyce)
9
+
10
+ ## Description
11
+
12
+ Joyce is a multiplayer game server framework built on top of Metacosm
13
+
14
+ ## What is the big idea?
15
+
16
+ The idea is to build 'isomorphic' Ruby games: to target both a Gosu game client as well as a game server
17
+ running in the cloud. Both are running the same code, but the responsibilities are split:
18
+
19
+ - The game server runs game business logic, and publishes events that the clients consume to hydrate their local views
20
+ - Clients publish commands the the server validates and handles, updating game state and generating events
21
+
22
+ One upshot of this is that all game processing is kept on the server, and clients are doing nothing but
23
+ rendering views and when necessary figuring out what commands to publish (when the user interacts).
24
+
25
+ ## Features
26
+
27
+ ## Examples
28
+
29
+ Below is the source for a toy app that displays a list of other connected players and how long they've been connected. The server uses a ping command from clients to determine if players have disconnected.
30
+
31
+ ```ruby
32
+ require 'joyce'
33
+ module Example
34
+ class SampleAppView < Joyce::ApplicationView
35
+ def render
36
+ game_view.render(window, font)
37
+ end
38
+
39
+ def game_view
40
+ GameView.find_by active_player_id: application.player_id
41
+ end
42
+ end
43
+
44
+ class SampleServer < Joyce::Server
45
+ def setup
46
+ # p [ :sample_server_setup ]
47
+ Game.create
48
+ end
49
+
50
+ def tick
51
+ # p [ :sample_server_tick ]
52
+ Game.all.each(&:iterate!)
53
+ end
54
+ end
55
+
56
+ class Application < Joyce::Application
57
+ viewed_with Example::SampleAppView
58
+
59
+ def setup
60
+ # p [ :sample_app_setup ]
61
+ GameView.create(active_player_id: player_id)
62
+ sim.params[:active_player_id] = player_id
63
+ end
64
+
65
+ def tick
66
+ @ticks ||= 0
67
+ @ticks += 1
68
+ # p [ :sample_app_tick ]
69
+ if (@ticks % 30 == 0)
70
+ fire(PingCommand.create(player_id: player_id, player_name: player_name))
71
+ end
72
+ end
73
+
74
+ def player_id
75
+ @player_id ||= SecureRandom.uuid
76
+ end
77
+
78
+ def player_name
79
+ @player_name ||= %w[ Alice Bob Cardiff Danielle Echo Fargo ].sample
80
+ end
81
+ end
82
+
83
+ class BulletPointAtom
84
+ def render(color: 0xf0f0f0f0, location:,size: 10,window:)
85
+ x,y = location
86
+ window.draw_quad(x,y,color,
87
+ x,y+size,color,
88
+ x+size,y,color,
89
+ x+size,y+size,color)
90
+ end
91
+ end
92
+
93
+ class TextBoxAtom
94
+ def render(message:, location:, font:)
95
+ x,y = *location
96
+ font.draw(message, x, y, 1)
97
+ end
98
+ end
99
+
100
+ class ListMolecule
101
+ def render(title: '', elements:, location:, font:, window:)
102
+ x0,y0 = location
103
+ font.draw(title, x0, y0, 1)
104
+ elements.each_with_index do |element, index|
105
+ x, y = x0, y0 + ((index+1)*20)
106
+
107
+ bullet_point = BulletPointAtom.new
108
+ bullet_point.render(location: [x-10,y], window: window)
109
+
110
+ text_box = TextBoxAtom.new
111
+ text_box.render(message: element, location: [x,y], font: font)
112
+ end
113
+ end
114
+ end
115
+
116
+ class PlayerView < Metacosm::View
117
+ belongs_to :game_view
118
+ attr_accessor :name, :joined_at, :player_id
119
+ end
120
+
121
+ class GameView < Metacosm::View
122
+ has_many :player_views
123
+
124
+ attr_accessor :active_player_id, :game_id
125
+ # attr_accessor :player_names
126
+
127
+ def render(window, font)
128
+ # p [ :render_game_view, pinged_at: pinged_at ]
129
+ window.draw_quad(10,10, 0xf0f0f0f0,
130
+ 10,20, 0xf0f0f0f0,
131
+ 20,10, 0xf0f0f0f0,
132
+ 20,20, 0xf0f0f0f0)
133
+
134
+ if player_names && player_names.any?
135
+ ListMolecule.new.render(
136
+ title: "Connected Users:",
137
+ elements: player_names,
138
+ location: [40,40],
139
+ font: font,
140
+ window: window
141
+ )
142
+ end
143
+ end
144
+
145
+ private
146
+ def player_names
147
+ self.player_views.map { |p| "#{p.name} (#{time_ago_in_words(p.joined_at)})" } #(&:name)
148
+ end
149
+ end
150
+
151
+ class PingCommand < Metacosm::Command
152
+ attr_accessor :player_id, :player_name
153
+ end
154
+
155
+ class PingCommandHandler
156
+ def handle(player_id:, player_name:)
157
+ p [ :ping, from_player: player_id ]
158
+ game = Game.find_by(players: { id: player_id })
159
+ if game.nil?
160
+ # we could try to create a new game for the player?
161
+ # or add them to an existing one?
162
+ p [ :no_game_yet! ]
163
+
164
+ game = Game.first || Game.create
165
+ game.admit_player(player_id: player_id, player_name: player_name)
166
+ end
167
+
168
+ game.ping(player_id: player_id)
169
+ end
170
+ end
171
+
172
+ #
173
+ # class GameController
174
+ # def ping(player_id:, player_name:)
175
+ # game = Game.find_by ...
176
+ # end
177
+ # end
178
+ #
179
+
180
+ class ApplicationEventListener < Metacosm::EventListener
181
+ def game_view
182
+ GameView.find_by(active_player_id: active_player_id)
183
+ end
184
+
185
+ def active_player_id
186
+ self.simulation.params[:active_player_id]
187
+ end
188
+ end
189
+
190
+ class PlayerAdmittedEvent < Metacosm::Event
191
+ attr_accessor :player_id, :player_name, :connected_player_list
192
+ end
193
+
194
+ class PlayerAdmittedEventListener < ApplicationEventListener
195
+ def receive(player_id:, player_name:, connected_player_list:)
196
+ p [ :player_admitted_event_listener ]
197
+ if game_view
198
+ # game_view.create_player_view(player_id: player_id, name: player_name, joined_at: Time.now)
199
+ # go through connected player list and make views...
200
+ connected_player_list.each do |id:, name:, joined_at:|
201
+ player_view = game_view.player_views.where(player_id: id).first_or_create
202
+ player_view.update(name: name, joined_at: joined_at)
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ class PlayerDroppedEvent < Metacosm::Event
209
+ attr_accessor :player_id, :connected_player_list
210
+ end
211
+
212
+ class PlayerDroppedEventListener < ApplicationEventListener
213
+ def receive(player_id:, connected_player_list:)
214
+ p [ :player_dropped_event_listener! ]
215
+ if game_view
216
+ game_view.player_views.map(&:destroy)
217
+ connected_player_list.each do |id:, name:, joined_at:|
218
+ player_view = game_view.player_views.where(player_id: id).first_or_create
219
+ player_view.update(name: name, joined_at: joined_at)
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
225
+ ```
226
+
227
+ ## Requirements
228
+
229
+ ## Install
230
+
231
+ $ gem install joyce
232
+
233
+ ## Synopsis
234
+
235
+ $ joyce
236
+
237
+ ## Copyright
238
+
239
+ Copyright (c) 2016 Joseph Weissman
240
+
241
+ See {file:LICENSE.txt} for details.
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'bundler/setup'
7
+ rescue LoadError => e
8
+ abort e.message
9
+ end
10
+
11
+ require 'rake'
12
+
13
+
14
+ require 'rubygems/tasks'
15
+ Gem::Tasks.new
16
+
17
+ require 'rspec/core/rake_task'
18
+ RSpec::Core::RakeTask.new
19
+
20
+ task :test => :spec
21
+ task :default => :spec
22
+
23
+ require 'yard'
24
+ YARD::Rake::YardocTask.new
25
+ task :doc => :yard
26
+
27
+ require 'cucumber/rake/task'
28
+
29
+ Cucumber::Rake::Task.new do |t|
30
+ t.cucumber_opts = %w[--format pretty]
31
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ root = File.expand_path(File.join(File.dirname(__FILE__),'..'))
4
+ if File.directory?(File.join(root,'.git'))
5
+ Dir.chdir(root) do
6
+ begin
7
+ require 'bundler/setup'
8
+ rescue LoadError => e
9
+ warn e.message
10
+ warn "Run `gem install bundler` to install Bundler"
11
+ exit -1
12
+ end
13
+ end
14
+ end
@@ -0,0 +1 @@
1
+ REDIS0006�ܳC�Z��V
@@ -0,0 +1,203 @@
1
+ require_relative 'models/game'
2
+ require_relative 'models/player'
3
+
4
+ module Example
5
+ class SampleAppView < Joyce::ApplicationView
6
+ def render
7
+ game_view.render(window, font)
8
+ end
9
+
10
+ def game_view
11
+ GameView.find_by active_player_id: application.player_id
12
+ end
13
+ end
14
+
15
+ class SampleServer < Joyce::Server
16
+ def setup
17
+ # p [ :sample_server_setup ]
18
+ Game.create
19
+ end
20
+
21
+ def tick
22
+ # p [ :sample_server_tick ]
23
+ Game.all.each(&:iterate!)
24
+ end
25
+ end
26
+
27
+ class Application < Joyce::Application
28
+ viewed_with Example::SampleAppView
29
+
30
+ def setup
31
+ # p [ :sample_app_setup ]
32
+ GameView.create(active_player_id: player_id)
33
+ sim.params[:active_player_id] = player_id
34
+ end
35
+
36
+ def tick
37
+ @ticks ||= 0
38
+ @ticks += 1
39
+ # p [ :sample_app_tick ]
40
+ if (@ticks % 30 == 0)
41
+ fire(PingCommand.create(player_id: player_id, player_name: player_name))
42
+ end
43
+ end
44
+
45
+ def player_id
46
+ @player_id ||= SecureRandom.uuid
47
+ end
48
+
49
+ def player_name
50
+ @player_name ||= %w[ Alice Bob Cardiff Danielle Echo Fargo ].sample
51
+ end
52
+ end
53
+
54
+ class BulletPointAtom
55
+ def render(color: 0xf0f0f0f0, location:,size: 10,window:)
56
+ x,y = location
57
+ window.draw_quad(x,y,color,
58
+ x,y+size,color,
59
+ x+size,y,color,
60
+ x+size,y+size,color)
61
+ end
62
+ end
63
+
64
+ class TextBoxAtom
65
+ def render(message:, location:, font:)
66
+ x,y = *location
67
+ font.draw(message, x, y, 1)
68
+ end
69
+ end
70
+
71
+ class ListMolecule
72
+ def render(title: '', elements:, location:, font:, window:)
73
+ x0,y0 = location
74
+ font.draw(title, x0, y0, 1)
75
+ elements.each_with_index do |element, index|
76
+ x, y = x0, y0 + ((index+1)*20)
77
+
78
+ bullet_point = BulletPointAtom.new
79
+ bullet_point.render(location: [x-10,y], window: window)
80
+
81
+ text_box = TextBoxAtom.new
82
+ text_box.render(message: element, location: [x,y], font: font)
83
+ end
84
+ end
85
+ end
86
+
87
+ class PlayerView < Metacosm::View
88
+ belongs_to :game_view
89
+ attr_accessor :name, :joined_at, :player_id
90
+ end
91
+
92
+ class GameView < Metacosm::View
93
+ has_many :player_views
94
+
95
+ attr_accessor :active_player_id, :game_id
96
+ # attr_accessor :player_names
97
+
98
+ def render(window, font)
99
+ # p [ :render_game_view, pinged_at: pinged_at ]
100
+ window.draw_quad(10,10, 0xf0f0f0f0,
101
+ 10,20, 0xf0f0f0f0,
102
+ 20,10, 0xf0f0f0f0,
103
+ 20,20, 0xf0f0f0f0)
104
+
105
+ if player_names && player_names.any?
106
+ ListMolecule.new.render(
107
+ title: "Connected Users:",
108
+ elements: player_names,
109
+ location: [40,40],
110
+ font: font,
111
+ window: window
112
+ )
113
+ end
114
+ end
115
+
116
+ private
117
+ def player_names
118
+ self.player_views.map { |p| "#{p.name} (#{time_ago_in_words(p.joined_at)})" } #(&:name)
119
+ end
120
+ end
121
+
122
+ class PingCommand < Metacosm::Command
123
+ attr_accessor :player_id, :player_name
124
+ end
125
+
126
+ class PingCommandHandler
127
+ def handle(player_id:, player_name:)
128
+ p [ :ping, from_player: player_id ]
129
+ game = Game.find_by(players: { id: player_id })
130
+ if game.nil?
131
+ # we could try to create a new game for the player?
132
+ # or add them to an existing one?
133
+ p [ :no_game_yet! ]
134
+
135
+ game = Game.first || Game.create
136
+ game.admit_player(player_id: player_id, player_name: player_name)
137
+ end
138
+
139
+ game.ping(player_id: player_id)
140
+ end
141
+ end
142
+
143
+ #
144
+ # class GameController
145
+ # def ping(player_id:, player_name:)
146
+ # game = Game.find_by ...
147
+ # end
148
+ # end
149
+ #
150
+
151
+ class ApplicationEventListener < Metacosm::EventListener
152
+ def game_view
153
+ GameView.find_by(active_player_id: active_player_id)
154
+ end
155
+
156
+ def active_player_id
157
+ self.simulation.params[:active_player_id]
158
+ end
159
+ end
160
+
161
+ class PlayerAdmittedEvent < Metacosm::Event
162
+ attr_accessor :player_id, :player_name, :connected_player_list
163
+ end
164
+
165
+ class PlayerAdmittedEventListener < ApplicationEventListener
166
+ def receive(player_id:, player_name:, connected_player_list:)
167
+ p [ :player_admitted_event_listener ]
168
+ if game_view
169
+ # game_view.create_player_view(player_id: player_id, name: player_name, joined_at: Time.now)
170
+ # go through connected player list and make views...
171
+ connected_player_list.each do |id:, name:, joined_at:|
172
+ player_view = game_view.player_views.where(player_id: id).first_or_create
173
+ player_view.update(name: name, joined_at: joined_at)
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ class PlayerDroppedEvent < Metacosm::Event
180
+ attr_accessor :player_id, :connected_player_list
181
+ end
182
+
183
+ class PlayerDroppedEventListener < ApplicationEventListener
184
+ def receive(player_id:, connected_player_list:)
185
+ p [ :player_dropped_event_listener! ]
186
+ if game_view
187
+ game_view.player_views.map(&:destroy)
188
+ connected_player_list.each do |id:, name:, joined_at:|
189
+ player_view = game_view.player_views.where(player_id: id).first_or_create
190
+ player_view.update(name: name, joined_at: joined_at)
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ # TODO sagas in metacosm (also controllers for commands...)
197
+ # class PlayerSaga < Metacosm::Saga
198
+ # def player_dropped(player_id:, connected_player_list:)
199
+ # end
200
+ # def player_admitted(player_id:, player_name:, connected_player_list:)
201
+ # end
202
+ # end
203
+ end
@@ -0,0 +1,66 @@
1
+ module Example
2
+ class Game < Metacosm::Model
3
+ has_many :players
4
+
5
+ def iterate!
6
+ # check for dropped players
7
+ players_to_drop = self.players.all.select do |player|
8
+ player.pinged_at < 3.seconds.ago
9
+ end
10
+
11
+ players_to_drop.each do |player|
12
+ drop_player(player)
13
+ end
14
+
15
+ self
16
+ end
17
+
18
+ def ping(player_id:)
19
+ player = self.players.where(id: player_id).first
20
+ player.update(pinged_at: Time.now)
21
+ self
22
+ end
23
+
24
+ def admit_player(player_name:, player_id:)
25
+ self.players.create(
26
+ name: player_name,
27
+ id: player_id,
28
+ joined_at: Time.now,
29
+ pinged_at: Time.now
30
+ )
31
+
32
+ emit(
33
+ PlayerAdmittedEvent.create(
34
+ player_id: player_id,
35
+ player_name: player_name,
36
+ connected_player_list: connected_player_list,
37
+ )
38
+ )
39
+
40
+ self
41
+ end
42
+
43
+ private
44
+ def drop_player(player)
45
+ lost_player_id = player.id
46
+ player.destroy
47
+
48
+ emit(
49
+ PlayerDroppedEvent.create(
50
+ player_id: lost_player_id,
51
+ connected_player_list: connected_player_list
52
+ )
53
+ )
54
+ end
55
+
56
+ def connected_player_list
57
+ self.players.map do |player|
58
+ {
59
+ id: player.id,
60
+ name: player.name,
61
+ joined_at: player.joined_at
62
+ }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,6 @@
1
+ module Example
2
+ class Player < Metacosm::Model
3
+ belongs_to :game
4
+ attr_accessor :name, :joined_at, :pinged_at
5
+ end
6
+ end
File without changes
@@ -0,0 +1 @@
1
+ Feature: Blah blah blah
File without changes
@@ -0,0 +1,21 @@
1
+ name: joyce
2
+ summary: "multiplayer game server framework"
3
+ description: "isomorphic ruby games"
4
+ license: MIT
5
+ authors: Joseph Weissman
6
+ email: jweissman1986@gmail.com
7
+ homepage: https://rubygems.org/gems/joyce
8
+
9
+ dependencies:
10
+ gosu: ~> 0.10
11
+ metacosm: ~> 0.3
12
+ actionpack: ~> 4.2
13
+
14
+ development_dependencies:
15
+ bundler: ~> 1.10
16
+ codeclimate-test-reporter: ~> 0.1
17
+ cucumber: ~> 0.10.2
18
+ rake: ~> 10.0
19
+ rspec: ~> 3.0
20
+ rubygems-tasks: ~> 0.2
21
+ yard: ~> 0.8
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gemspec = YAML.load_file('gemspec.yml')
7
+
8
+ gem.name = gemspec.fetch('name')
9
+ gem.version = gemspec.fetch('version') do
10
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
11
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'joyce/version'
14
+ Joyce::VERSION
15
+ end
16
+
17
+ gem.summary = gemspec['summary']
18
+ gem.description = gemspec['description']
19
+ gem.licenses = Array(gemspec['license'])
20
+ gem.authors = Array(gemspec['authors'])
21
+ gem.email = gemspec['email']
22
+ gem.homepage = gemspec['homepage']
23
+
24
+ glob = lambda { |patterns| gem.files & Dir[*patterns] }
25
+
26
+ gem.files = `git ls-files`.split($/)
27
+ gem.files = glob[gemspec['files']] if gemspec['files']
28
+
29
+ gem.executables = gemspec.fetch('executables') do
30
+ glob['bin/*'].map { |path| File.basename(path) }
31
+ end
32
+ gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
33
+
34
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
35
+ gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb']
36
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
37
+
38
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
39
+ %w[ext lib].select { |dir| File.directory?(dir) }
40
+ })
41
+
42
+ gem.requirements = Array(gemspec['requirements'])
43
+ gem.required_ruby_version = gemspec['required_ruby_version']
44
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
45
+ gem.post_install_message = gemspec['post_install_message']
46
+
47
+ split = lambda { |string| string.split(/,\s*/) }
48
+
49
+ if gemspec['dependencies']
50
+ gemspec['dependencies'].each do |name,versions|
51
+ gem.add_dependency(name,split[versions])
52
+ end
53
+ end
54
+
55
+ if gemspec['development_dependencies']
56
+ gemspec['development_dependencies'].each do |name,versions|
57
+ gem.add_development_dependency(name,split[versions])
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,137 @@
1
+ require 'gosu'
2
+ require 'redis' # TODO move require to mc/remote_sim?
3
+ require 'metacosm'
4
+ require 'metacosm/remote_simulation'
5
+
6
+ require 'action_view' # just for distance of time in words..
7
+ include ActionView::Helpers::DateHelper
8
+
9
+ require 'joyce/version'
10
+ require 'joyce/application'
11
+
12
+ module Joyce
13
+ EVENT_STREAM = :joyce_event_stream
14
+ COMMAND_QUEUE = :joyce_command_queue
15
+
16
+ class ApplicationView
17
+ attr_reader :application
18
+
19
+ def initialize(application)
20
+ @application ||= application
21
+ end
22
+
23
+ def render
24
+ # ...
25
+ end
26
+
27
+ def window
28
+ @application.window
29
+ end
30
+
31
+ def font
32
+ @font ||= Gosu::Font.new(18)
33
+ end
34
+ end
35
+
36
+ class ApplicationWindow < Gosu::Window
37
+ attr_accessor :width, :height
38
+ attr_reader :app
39
+
40
+ def initialize(app, width: 800, height: 600)
41
+ @app = app
42
+ self.width = width
43
+ self.height = height
44
+
45
+ super(self.width, self.height)
46
+ end
47
+
48
+ def draw
49
+ app.view.render
50
+ end
51
+
52
+ def update
53
+ app.tick
54
+ end
55
+ end
56
+
57
+ class NullWindow
58
+ attr_reader :app
59
+ def initialize(app, width: 800, height: 600)
60
+ # p [ :created_null_window, provided_dimensions: [ width, height ] ]
61
+ @app = app
62
+ end
63
+
64
+ def show
65
+ app.tick
66
+ false
67
+ end
68
+
69
+ def draw_quad(*)
70
+ # p [ :null_window, :draw_quad ]
71
+ self
72
+ end
73
+ end
74
+
75
+ class Server
76
+ def setup
77
+ # p [ :server_setup ]
78
+ end
79
+
80
+ def boot
81
+ sim.on_event(publish_to: EVENT_STREAM)
82
+ @cmd_thread = sim.subscribe_for_commands(channel: COMMAND_QUEUE)
83
+ setup
84
+ sim.conduct!
85
+ drive!
86
+ # cmd_thread.join
87
+ end
88
+
89
+ def join
90
+ @cmd_thread.join
91
+ end
92
+
93
+ def tick
94
+ # p [ :server_tick ]
95
+ end
96
+
97
+ def drive!
98
+ @driving = true
99
+ Thread.new do
100
+ while @driving
101
+ tick
102
+ sleep 0.05
103
+ end
104
+ end
105
+ end
106
+
107
+ def halt!
108
+ @driving = false
109
+ end
110
+
111
+ def sim
112
+ @simulation ||= Metacosm::Simulation.current
113
+ end
114
+
115
+ def received_commands
116
+ sim.received_commands
117
+ end
118
+
119
+ class << self
120
+ def kickstart!
121
+ server = new
122
+ server.boot
123
+ server
124
+ end
125
+ end
126
+ end
127
+
128
+ class RemoteSim < Metacosm::RemoteSimulation
129
+ def initialize
130
+ super(COMMAND_QUEUE, EVENT_STREAM)
131
+ end
132
+
133
+ def redis_connection
134
+ ::Redis.new
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,61 @@
1
+ module Joyce
2
+ class Application
3
+ def initialize(headless: false)
4
+ @headless = headless
5
+ end
6
+
7
+ def setup
8
+ # ...
9
+ end
10
+
11
+ def tick
12
+ # ...
13
+ end
14
+
15
+ def launch
16
+ sim.conduct!
17
+ setup
18
+ window.show
19
+ self
20
+ end
21
+
22
+ def fire(cmd)
23
+ sim.fire(cmd)
24
+ end
25
+
26
+ def received_events
27
+ sim.received_events
28
+ end
29
+
30
+ def sim
31
+ @simulation ||= RemoteSim.current
32
+ end
33
+
34
+ def view
35
+ @view ||= construct_view
36
+ end
37
+
38
+ def window
39
+ @window ||= @headless ? NullWindow.new(self) : ApplicationWindow.new(self)
40
+ end
41
+
42
+ private
43
+ def construct_view
44
+ self.class.view_class.new(self)
45
+ end
46
+
47
+ class << self
48
+ attr_reader :view_class
49
+ def viewed_with(view_class)
50
+ @view_class ||= view_class
51
+ self
52
+ end
53
+
54
+ def kickstart!(headless: false)
55
+ app = new(headless: headless)
56
+ app.launch
57
+ app
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,4 @@
1
+ module Joyce
2
+ # joyce version
3
+ VERSION = "0.1.2"
4
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe Joyce do
4
+ it "should have a VERSION constant" do
5
+ expect(subject.const_get('VERSION')).to_not be_empty
6
+ end
7
+ end
8
+
9
+ describe Joyce::Application do
10
+ describe '.kickstart' do
11
+ subject(:application) { instance_double(Joyce::Application) }
12
+ it 'should launch a new app instance' do
13
+ expect(Application).to receive(:new).and_return(application)
14
+ expect(application).to receive(:launch)
15
+ Application.kickstart!(headless: true)
16
+ end
17
+ end
18
+
19
+ describe "#launch" do
20
+ subject(:application) { Joyce::Application.new(headless: true) }
21
+ it 'should call #show on app window' do
22
+ expect(application.window).to receive(:show)
23
+ application.launch
24
+ end
25
+ end
26
+ end
27
+
28
+ describe Joyce::Server do
29
+ describe ".kickstart" do
30
+ subject(:server) { instance_double(Joyce::Server) }
31
+ it 'should call boot on a new server instance' do
32
+ expect(Server).to receive(:new).and_return(server)
33
+ expect(server).to receive(:boot)
34
+ Server.kickstart!
35
+ end
36
+ end
37
+ end
38
+
39
+ describe Example::Application do
40
+ subject(:app) { Example::Application.new(headless: true) }
41
+
42
+ it 'should have a view with the indicated class' do
43
+ expect(app.view.class).to eq(Example::SampleAppView)
44
+ end
45
+
46
+ context "#launch" do
47
+ it 'should call setup' do
48
+ expect(app).to receive(:setup)
49
+ app.launch
50
+ end
51
+ end
52
+ end
53
+
54
+ describe Example::SampleAppView do
55
+ let(:sample_app) { Example::Application.new(headless: true) }
56
+ let(:window) { instance_double(ApplicationWindow) }
57
+
58
+ subject(:sample_app_view) { sample_app.view }
59
+ before { sample_app.setup }
60
+
61
+ it 'should render to the app window' do
62
+ expect(sample_app).to receive(:window).and_return(window)
63
+ expect(window).to receive(:draw_quad)
64
+ sample_app_view.render
65
+ end
66
+ end
67
+
68
+ describe Example::SampleServer, redis: true, flaky: true do
69
+ subject(:server) { Example::SampleServer.new }
70
+ let(:app) { Example::Application.new(headless: true) }
71
+ let(:command) { Example::PingCommand.create(player_id: 'the_player_id', player_name: 'Alice') }
72
+ let(:event) { Example::PlayerAdmittedEvent.create( player_name: "Alice", player_id: 'the_player_id', connected_player_list: connected_player_list) }
73
+ let(:connected_player_list) do
74
+ [
75
+ { id: 'the_player_id', name: 'Alice', joined_at: 1.day.ago }
76
+ ]
77
+ end
78
+
79
+ before do
80
+ app.launch
81
+ server.boot
82
+ end
83
+
84
+ it 'should receive commands from app' do
85
+ expect { app.fire(command); sleep 3 }.to change{server.received_commands.count}.by(1)
86
+ end
87
+
88
+ it 'should propagate events to app' do
89
+ expect { server.sim.receive(event); sleep 3 }.to change{app.received_events.count}.by(1)
90
+ end
91
+ end
@@ -0,0 +1,10 @@
1
+ require 'rspec'
2
+ require 'pry'
3
+
4
+ require 'joyce/version'
5
+ require 'joyce/application'
6
+ require 'joyce'
7
+
8
+ include Joyce
9
+
10
+ require_relative '../example/application'
metadata ADDED
@@ -0,0 +1,212 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: joyce
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Joseph Weissman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gosu
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: metacosm
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: actionpack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.10'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.10'
69
+ - !ruby/object:Gem::Dependency
70
+ name: codeclimate-test-reporter
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: cucumber
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.10.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.10.2
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '10.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '10.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubygems-tasks
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.2'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.2'
139
+ - !ruby/object:Gem::Dependency
140
+ name: yard
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.8'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.8'
153
+ description: isomorphic ruby games
154
+ email: jweissman1986@gmail.com
155
+ executables:
156
+ - joyce
157
+ extensions: []
158
+ extra_rdoc_files:
159
+ - ChangeLog.md
160
+ - LICENSE.txt
161
+ - README.md
162
+ files:
163
+ - ".document"
164
+ - ".gitignore"
165
+ - ".rspec"
166
+ - ".yardopts"
167
+ - ChangeLog.md
168
+ - Gemfile
169
+ - LICENSE.txt
170
+ - README.md
171
+ - Rakefile
172
+ - bin/joyce
173
+ - dump.rdb
174
+ - example/application.rb
175
+ - example/models/game.rb
176
+ - example/models/player.rb
177
+ - features/.gitkeep
178
+ - features/joyce.feature
179
+ - features/step_definitions/.gitkeep
180
+ - features/step_definitions/joyce_steps.rb
181
+ - gemspec.yml
182
+ - joyce.gemspec
183
+ - lib/joyce.rb
184
+ - lib/joyce/application.rb
185
+ - lib/joyce/version.rb
186
+ - spec/joyce_spec.rb
187
+ - spec/spec_helper.rb
188
+ homepage: https://rubygems.org/gems/joyce
189
+ licenses:
190
+ - MIT
191
+ metadata: {}
192
+ post_install_message:
193
+ rdoc_options: []
194
+ require_paths:
195
+ - lib
196
+ required_ruby_version: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ required_rubygems_version: !ruby/object:Gem::Requirement
202
+ requirements:
203
+ - - ">="
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ requirements: []
207
+ rubyforge_project:
208
+ rubygems_version: 2.4.5.1
209
+ signing_key:
210
+ specification_version: 4
211
+ summary: multiplayer game server framework
212
+ test_files: []