game_2d 0.0.1
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.
- data/.gitignore +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +84 -0
- data/Rakefile +1 -0
- data/bin/game_2d_client.rb +17 -0
- data/bin/game_2d_server.rb +21 -0
- data/game_2d.gemspec +32 -0
- data/lib/game_2d/client_connection.rb +127 -0
- data/lib/game_2d/client_engine.rb +227 -0
- data/lib/game_2d/complex_move.rb +45 -0
- data/lib/game_2d/entity.rb +371 -0
- data/lib/game_2d/entity/block.rb +73 -0
- data/lib/game_2d/entity/owned_entity.rb +29 -0
- data/lib/game_2d/entity/pellet.rb +27 -0
- data/lib/game_2d/entity/titanium.rb +11 -0
- data/lib/game_2d/entity_constants.rb +14 -0
- data/lib/game_2d/game.rb +213 -0
- data/lib/game_2d/game_space.rb +462 -0
- data/lib/game_2d/game_window.rb +260 -0
- data/lib/game_2d/hash.rb +11 -0
- data/lib/game_2d/menu.rb +82 -0
- data/lib/game_2d/move/rise_up.rb +77 -0
- data/lib/game_2d/player.rb +251 -0
- data/lib/game_2d/registerable.rb +25 -0
- data/lib/game_2d/serializable.rb +69 -0
- data/lib/game_2d/server_connection.rb +104 -0
- data/lib/game_2d/server_port.rb +74 -0
- data/lib/game_2d/storage.rb +42 -0
- data/lib/game_2d/version.rb +3 -0
- data/lib/game_2d/wall.rb +21 -0
- data/lib/game_2d/zorder.rb +3 -0
- data/media/Beep.wav +0 -0
- data/media/Space.png +0 -0
- data/media/Star.png +0 -0
- data/media/Starfighter.bmp +0 -0
- data/media/brick.gif +0 -0
- data/media/cement.gif +0 -0
- data/media/crosshair.gif +0 -0
- data/media/dirt.gif +0 -0
- data/media/pellet.png +0 -0
- data/media/pellet.xcf +0 -0
- data/media/player.png +0 -0
- data/media/player.xcf +0 -0
- data/media/rock.png +0 -0
- data/media/rock.xcf +0 -0
- data/media/steel.gif +0 -0
- data/media/tele.gif +0 -0
- data/media/titanium.gif +0 -0
- data/media/unlikelium.gif +0 -0
- data/spec/client_engine_spec.rb +235 -0
- data/spec/game_space_spec.rb +347 -0
- metadata +246 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
class NilClass
|
2
|
+
def nullsafe_registry_id; self; end
|
3
|
+
end
|
4
|
+
|
5
|
+
module Registerable
|
6
|
+
def registry_id?
|
7
|
+
@registry_id
|
8
|
+
end
|
9
|
+
|
10
|
+
def registry_id
|
11
|
+
@registry_id or raise("No ID set for #{self}")
|
12
|
+
end
|
13
|
+
def nullsafe_registry_id; registry_id; end
|
14
|
+
|
15
|
+
# For use in to_s
|
16
|
+
def registry_id_safe
|
17
|
+
@registry_id || :NO_ID
|
18
|
+
end
|
19
|
+
|
20
|
+
def registry_id=(id)
|
21
|
+
raise "#{self}: Already have ID #{@registry_id}, cannot set to #{id}" if @registry_id
|
22
|
+
raise "#{self}: Invalid ID #{id}" unless id
|
23
|
+
@registry_id = id.to_sym
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class NilClass
|
2
|
+
def as_json; self end
|
3
|
+
end
|
4
|
+
|
5
|
+
module Serializable
|
6
|
+
# Flat list of all object state
|
7
|
+
# For sorting purposes, most significant goes first
|
8
|
+
def all_state
|
9
|
+
[]
|
10
|
+
end
|
11
|
+
|
12
|
+
# Based on all_state
|
13
|
+
def <=>(other)
|
14
|
+
self.all_state <=> other.all_state
|
15
|
+
end
|
16
|
+
def ==(other)
|
17
|
+
other.class.equal?(self.class) && other.all_state == self.all_state
|
18
|
+
end
|
19
|
+
def hash; self.class.hash ^ all_state.hash; end
|
20
|
+
def eql?(other); self == other; end
|
21
|
+
|
22
|
+
# Returns a hash which becomes the JSON
|
23
|
+
def self.as_json(thing)
|
24
|
+
{ :class => thing.class.to_s }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Based on as_json
|
28
|
+
def to_json(*args)
|
29
|
+
as_json.to_json(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Make our state match what's in the hash
|
33
|
+
def update_from_json(hash)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
self.class.name
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.from_json(json, generate_id=false)
|
41
|
+
return nil unless json
|
42
|
+
class_name = json[:class]
|
43
|
+
binding.pry unless class_name
|
44
|
+
raise "Suspicious class name: #{class_name}" unless
|
45
|
+
(class_name == 'Player') ||
|
46
|
+
(class_name.start_with? 'Entity::') ||
|
47
|
+
(class_name.start_with? 'Move::')
|
48
|
+
require "game_2d/#{class_name.pathize}"
|
49
|
+
clazz = constant(class_name)
|
50
|
+
it = clazz.new
|
51
|
+
|
52
|
+
# A registry ID must be specified either in the JSON or by the caller, but
|
53
|
+
# not both
|
54
|
+
if it.is_a? Registerable
|
55
|
+
if generate_id
|
56
|
+
fail("Entity #{it} (from #{json.inspect}) already has " +
|
57
|
+
"ID #{it.registry_id}, cannot generate") if it.registry_id?
|
58
|
+
# Leave it nil - it will be populated when added to a space
|
59
|
+
else
|
60
|
+
it.registry_id = json[:registry_id]
|
61
|
+
end
|
62
|
+
elsif generate_id
|
63
|
+
fail("#{clazz} is not Registerable")
|
64
|
+
end
|
65
|
+
|
66
|
+
it.update_from_json(json)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'game_2d/hash'
|
3
|
+
|
4
|
+
# An instance of this class is created by ServerPort whenever an
|
5
|
+
# incoming connection is accepted.
|
6
|
+
|
7
|
+
class ServerConnection
|
8
|
+
|
9
|
+
def initialize(port, game, server, id, remote_addr)
|
10
|
+
@port, @game, @server, @id, @remote_addr = port, game, server, id, remote_addr
|
11
|
+
puts "ServerConnection: New connection #{id} from #{remote_addr}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def answer_handshake(handshake)
|
15
|
+
player_name = handshake[:player_name]
|
16
|
+
player = @game.add_player(player_name)
|
17
|
+
@player_id = player.registry_id
|
18
|
+
@port.register_player @player_id, self
|
19
|
+
|
20
|
+
response = {
|
21
|
+
:you_are => @player_id,
|
22
|
+
:world => {
|
23
|
+
:world_name => @game.world_name,
|
24
|
+
:world_id => @game.world_id,
|
25
|
+
:highest_id => @game.world_highest_id,
|
26
|
+
:cell_width => @game.world_cell_width,
|
27
|
+
:cell_height => @game.world_cell_height,
|
28
|
+
},
|
29
|
+
:add_players => @game.get_all_players,
|
30
|
+
:add_npcs => @game.get_all_npcs,
|
31
|
+
:at_tick => @game.tick,
|
32
|
+
}
|
33
|
+
puts "#{player} logs in from #{@remote_addr} at <#{@game.tick}>"
|
34
|
+
send_record response, true # answer handshake reliably
|
35
|
+
end
|
36
|
+
|
37
|
+
def player
|
38
|
+
@game[@player_id]
|
39
|
+
end
|
40
|
+
|
41
|
+
def answer_ping(ping)
|
42
|
+
send_record :pong => ping
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_npc(npc, at_tick)
|
46
|
+
send_record :add_npcs => [ npc ], :at_tick => at_tick
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_player(player, at_tick)
|
50
|
+
send_record :add_players => [ player ], :at_tick => at_tick
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete_entity(entity, at_tick)
|
54
|
+
send_record :delete_entities => [ entity.registry_id ], :at_tick => at_tick
|
55
|
+
end
|
56
|
+
|
57
|
+
def update_entities(entities, at_tick)
|
58
|
+
send_record :update_entities => entities, :at_tick => at_tick
|
59
|
+
end
|
60
|
+
|
61
|
+
# Not called yet...
|
62
|
+
def update_score(player, at_tick)
|
63
|
+
send_record :update_score => { player.registry_id => player.score }, :at_tick => at_tick
|
64
|
+
end
|
65
|
+
|
66
|
+
def close
|
67
|
+
@port.deregister_player @player_id
|
68
|
+
toast = player
|
69
|
+
puts "#{toast} -- #{@remote_addr} disconnected at <#{@game.tick}>"
|
70
|
+
@game.delete_entity toast
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_packet(data, channel)
|
74
|
+
hash = JSON.parse(data).fix_keys
|
75
|
+
debug_packet('Received', hash)
|
76
|
+
if (handshake = hash[:handshake])
|
77
|
+
answer_handshake(handshake)
|
78
|
+
elsif (hash[:save])
|
79
|
+
@game.save
|
80
|
+
elsif (ping = hash[:ping])
|
81
|
+
answer_ping ping
|
82
|
+
else
|
83
|
+
@game.add_player_action @player_id, hash
|
84
|
+
@port.broadcast_player_action @id,
|
85
|
+
hash.merge(:player_id => @player_id),
|
86
|
+
channel
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def send_record(hash, reliable=false, channel=0)
|
91
|
+
debug_packet('Sending', hash)
|
92
|
+
send_str = hash.to_json
|
93
|
+
# Send data to the client (client ID, data, reliable or not, channel ID)
|
94
|
+
@server.send_packet(@id, send_str, reliable, channel)
|
95
|
+
@server.flush
|
96
|
+
end
|
97
|
+
|
98
|
+
def debug_packet(direction, hash)
|
99
|
+
return unless $debug_traffic
|
100
|
+
at_tick = hash[:at_tick] || 'NO TICK'
|
101
|
+
keys = hash.keys - [:at_tick]
|
102
|
+
puts "#{direction} #{keys.join(', ')} <#{at_tick}>"
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'renet'
|
2
|
+
require 'json'
|
3
|
+
require 'game_2d/server_connection'
|
4
|
+
|
5
|
+
class ServerPort
|
6
|
+
def initialize(game, port_number, max_clients)
|
7
|
+
@game = game
|
8
|
+
@server = _create_enet_server port_number, max_clients, 2, 0, 0
|
9
|
+
puts "ENet server listening on #{port_number}"
|
10
|
+
|
11
|
+
@clients = {}
|
12
|
+
@player_connections = {}
|
13
|
+
|
14
|
+
@server.on_connection method(:on_connection)
|
15
|
+
@server.on_packet_receive method(:on_packet_receive)
|
16
|
+
@server.on_disconnection method(:on_disconnection)
|
17
|
+
end
|
18
|
+
|
19
|
+
def _create_enet_server(*args)
|
20
|
+
ENet::Server.new *args
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_connection(id, ip)
|
24
|
+
puts "New ENet connection #{id} from #{ip}"
|
25
|
+
@clients[id] = ServerConnection.new(self, @game, @server, id, ip)
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_packet_receive(id, data, channel)
|
29
|
+
@clients[id].on_packet(data, channel)
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_disconnection(id)
|
33
|
+
puts "ENet connection #{id} disconnected"
|
34
|
+
gone = @clients.delete id
|
35
|
+
gone.close
|
36
|
+
puts "Remaining connection IDs: #{@clients.keys.sort.join(', ')}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def broadcast(data, reliable=false, channel=1)
|
40
|
+
@server.broadcast_packet data.to_json, reliable, channel
|
41
|
+
@server.flush
|
42
|
+
end
|
43
|
+
|
44
|
+
def register_player(player_id, conn)
|
45
|
+
@player_connections[player_id] = conn
|
46
|
+
end
|
47
|
+
|
48
|
+
def deregister_player(player_id)
|
49
|
+
@player_connections.delete player_id
|
50
|
+
end
|
51
|
+
|
52
|
+
def player_connection(player_id)
|
53
|
+
@player_connections[player_id]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Re-broadcast to everyone except the original sender
|
57
|
+
def broadcast_player_action(sender_id, hash, channel)
|
58
|
+
data = hash.to_json
|
59
|
+
@clients.keys.each do |id|
|
60
|
+
@server.send_packet(id, data, false, channel) unless id == sender_id
|
61
|
+
end
|
62
|
+
@server.flush
|
63
|
+
end
|
64
|
+
|
65
|
+
def update(timeout=0) # non-blocking by default
|
66
|
+
@server.update(timeout)
|
67
|
+
end
|
68
|
+
|
69
|
+
def update_until(stop_time)
|
70
|
+
while Time.now.to_r < stop_time do
|
71
|
+
update
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
require 'game_2d/hash'
|
4
|
+
|
5
|
+
class Storage
|
6
|
+
def self.in_home_dir(name)
|
7
|
+
Storage.new "#{Dir.home}/#{name}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(dir)
|
11
|
+
@dir = dir
|
12
|
+
FileUtils.mkdir_p @dir
|
13
|
+
end
|
14
|
+
|
15
|
+
def dir(subdir)
|
16
|
+
Storage.new("#{@dir}/#{subdir}")
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](name)
|
20
|
+
Settings.new("#{@dir}/#{name}")
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s; "Storage(#{@dir})"; end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Settings
|
27
|
+
def initialize(name)
|
28
|
+
@name = name
|
29
|
+
@values = File.exist?(name) ? JSON.parse(IO.read(name)).fix_keys : {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def save
|
33
|
+
puts "Writing to #{self}"
|
34
|
+
File.open(@name, 'w') {|f| f.write(@values.to_json) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def [](key); @values[key]; end
|
38
|
+
def []=(key, value); @values[key] = value; end
|
39
|
+
def empty?; @values.empty?; end
|
40
|
+
|
41
|
+
def to_s; "Settings(#{@name})"; end
|
42
|
+
end
|
data/lib/game_2d/wall.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'game_2d/entity'
|
2
|
+
|
3
|
+
class Wall < Entity
|
4
|
+
def initialize(space, cell_x, cell_y)
|
5
|
+
super(cell_x * Entity::WIDTH, cell_y * Entity::HEIGHT)
|
6
|
+
self.space = space
|
7
|
+
end
|
8
|
+
|
9
|
+
def moving?; false; end
|
10
|
+
def moving=(moving); end
|
11
|
+
|
12
|
+
def sleep_now?; true; end
|
13
|
+
def wake!; end
|
14
|
+
|
15
|
+
def registry_id; "Wall[#{left_cell_x}x#{top_cell_y}]"; end
|
16
|
+
def to_s
|
17
|
+
"Wall at #{left_cell_x}x#{top_cell_y} (#{x}x#{y})"
|
18
|
+
end
|
19
|
+
|
20
|
+
def all_state; [x, y]; end
|
21
|
+
end
|
data/media/Beep.wav
ADDED
Binary file
|
data/media/Space.png
ADDED
Binary file
|
data/media/Star.png
ADDED
Binary file
|
Binary file
|
data/media/brick.gif
ADDED
Binary file
|
data/media/cement.gif
ADDED
Binary file
|
data/media/crosshair.gif
ADDED
Binary file
|
data/media/dirt.gif
ADDED
Binary file
|
data/media/pellet.png
ADDED
Binary file
|
data/media/pellet.xcf
ADDED
Binary file
|
data/media/player.png
ADDED
Binary file
|
data/media/player.xcf
ADDED
Binary file
|
data/media/rock.png
ADDED
Binary file
|
data/media/rock.xcf
ADDED
Binary file
|
data/media/steel.gif
ADDED
Binary file
|
data/media/tele.gif
ADDED
Binary file
|
data/media/titanium.gif
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1,235 @@
|
|
1
|
+
$LOAD_PATH << '.'
|
2
|
+
require 'game'
|
3
|
+
require 'server_port'
|
4
|
+
require 'client_engine'
|
5
|
+
require 'client_connection'
|
6
|
+
|
7
|
+
IP_ADDRESS = '1.1.1.1'
|
8
|
+
CONNECTION_ID = 666
|
9
|
+
|
10
|
+
class FakeENetServer
|
11
|
+
def initialize(*args)
|
12
|
+
$stderr.puts "FakeENetServer.new(#{args.inspect})"
|
13
|
+
@queue = []
|
14
|
+
end
|
15
|
+
def broadcast_packet(data, reliable, channel)
|
16
|
+
$stderr.puts "FakeENetServer.broadcast_packet"
|
17
|
+
$fake_client.xmit data, channel
|
18
|
+
end
|
19
|
+
def send_packet(id, data, reliable, channel)
|
20
|
+
$fake_client.xmit data, channel
|
21
|
+
end
|
22
|
+
def update(timeout)
|
23
|
+
$stderr.puts "FakeENetServer.update(#{timeout}) - processing #{@queue.size} packets"
|
24
|
+
@queue.each do |data, channel|
|
25
|
+
$fake_server_port.on_packet_receive(CONNECTION_ID, data, channel)
|
26
|
+
end
|
27
|
+
@queue.clear
|
28
|
+
end
|
29
|
+
def on_connection(a_method) # accepts (id, ip)
|
30
|
+
end
|
31
|
+
def on_packet_receive(a_method) # accepts (id, data, channel)
|
32
|
+
end
|
33
|
+
def on_disconnection(a_method) # accepts (id)
|
34
|
+
end
|
35
|
+
def flush
|
36
|
+
$stderr.puts "FakeENetServer.flush"
|
37
|
+
end
|
38
|
+
|
39
|
+
def xmit(data, channel)
|
40
|
+
$stderr.puts "Client->Server on ##{channel}: #{data}"
|
41
|
+
@queue << [data, channel]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class FakeServerPort < ServerPort
|
46
|
+
def _create_enet_server(*args)
|
47
|
+
$fake_server_port = self
|
48
|
+
$fake_server = FakeENetServer.new *args
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class FakeGame < Game
|
53
|
+
attr_reader :space
|
54
|
+
|
55
|
+
def _create_server_port(*args)
|
56
|
+
FakeServerPort.new *args
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class FakeENetConnection
|
61
|
+
def initialize(*args)
|
62
|
+
$stderr.puts "FakeENetConnection.new(#{args.inspect})"
|
63
|
+
@queue = []
|
64
|
+
end
|
65
|
+
def connect(timeout)
|
66
|
+
$stderr.puts "FakeENetConnection.connect(#{timeout})"
|
67
|
+
$fake_server_port.on_connection(CONNECTION_ID, IP_ADDRESS)
|
68
|
+
$fake_client_conn.on_connect
|
69
|
+
end
|
70
|
+
def send_packet(data, reliable, channel)
|
71
|
+
$fake_server.xmit data, channel
|
72
|
+
end
|
73
|
+
def update(timeout)
|
74
|
+
$stderr.puts "FakeENetConnection.update(#{timeout}) - processing #{@queue.size} packets"
|
75
|
+
@queue.each do |data, channel|
|
76
|
+
$fake_client_conn.on_packet(data, channel)
|
77
|
+
end
|
78
|
+
@queue.clear
|
79
|
+
end
|
80
|
+
def on_connection(a_method) # accepts no args
|
81
|
+
end
|
82
|
+
def on_packet_receive(a_method) # accepts (data, channel)
|
83
|
+
end
|
84
|
+
def on_disconnection(a_method) # accepts no args
|
85
|
+
end
|
86
|
+
def flush
|
87
|
+
$stderr.puts "FakeENetConnection.flush"
|
88
|
+
end
|
89
|
+
|
90
|
+
def xmit(data, channel)
|
91
|
+
$stderr.puts "Server->Client on ##{channel}: #{data}"
|
92
|
+
@queue << [data, channel]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class FakeClientConnection < ClientConnection
|
97
|
+
def _create_connection(*args)
|
98
|
+
$fake_client_conn = self
|
99
|
+
$fake_client = FakeENetConnection.new(*args)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class FakeGameWindow
|
104
|
+
attr_accessor :player_id, :conn, :engine
|
105
|
+
|
106
|
+
def initialize(host, port, player_name)
|
107
|
+
@conn = FakeClientConnection.new(host, port, self, player_name)
|
108
|
+
@conn.engine = @engine = ClientEngine.new(self)
|
109
|
+
end
|
110
|
+
|
111
|
+
def space
|
112
|
+
@engine.space
|
113
|
+
end
|
114
|
+
|
115
|
+
def player
|
116
|
+
space[@player_id]
|
117
|
+
end
|
118
|
+
|
119
|
+
def update
|
120
|
+
@conn.update
|
121
|
+
@engine.update
|
122
|
+
end
|
123
|
+
|
124
|
+
def generate_move(move)
|
125
|
+
@conn.send_move move
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe FakeGame do
|
130
|
+
let(:hostname) { 'localhost' }
|
131
|
+
let(:port_number) { 9998 }
|
132
|
+
let(:player_name) { 'Ed' }
|
133
|
+
let(:max_clients) { 2 }
|
134
|
+
let(:storage) { '.test' }
|
135
|
+
let(:level) { 'test-level' }
|
136
|
+
let(:cell_width) { 3 }
|
137
|
+
let(:cell_height) { 3 }
|
138
|
+
let(:self_check) { false }
|
139
|
+
let(:profile) { false }
|
140
|
+
|
141
|
+
let(:game) { FakeGame.new(
|
142
|
+
:port => port_number,
|
143
|
+
:max_clients => max_clients,
|
144
|
+
:storage => storage,
|
145
|
+
:level => level,
|
146
|
+
:width => cell_width,
|
147
|
+
:height => cell_height,
|
148
|
+
:self_check => self_check,
|
149
|
+
:profile => profile,
|
150
|
+
:registry_broadcast_every => registry_broadcast_every
|
151
|
+
) }
|
152
|
+
let(:window) { game; FakeGameWindow.new(hostname, port_number, player_name) }
|
153
|
+
|
154
|
+
def update_both
|
155
|
+
game.update
|
156
|
+
window.update
|
157
|
+
end
|
158
|
+
|
159
|
+
def expect_spaces_to_match
|
160
|
+
expect(window.space).to eq(game.space)
|
161
|
+
end
|
162
|
+
|
163
|
+
context "with default registry syncs" do
|
164
|
+
let(:registry_broadcast_every) { nil }
|
165
|
+
it "is in sync after one update" do
|
166
|
+
window
|
167
|
+
|
168
|
+
update_both
|
169
|
+
|
170
|
+
expect_spaces_to_match
|
171
|
+
end
|
172
|
+
it "is in sync after a fall and a build" do
|
173
|
+
window
|
174
|
+
|
175
|
+
expect(game.tick).to eq(-1)
|
176
|
+
expect(window.engine.tick).to be_nil
|
177
|
+
|
178
|
+
28.times do |n|
|
179
|
+
update_both
|
180
|
+
expect(game.tick).to eq(n)
|
181
|
+
expect(window.engine.tick).to eq(n)
|
182
|
+
expect_spaces_to_match
|
183
|
+
end
|
184
|
+
|
185
|
+
expect(game.space.players.size).to eq(1)
|
186
|
+
expect(game.space.npcs.size).to eq(0)
|
187
|
+
|
188
|
+
plr = game.space.players.first
|
189
|
+
expect(plr.y).to eq(800)
|
190
|
+
|
191
|
+
# Command generated at tick 27, scheduled for tick 33
|
192
|
+
window.generate_move :build
|
193
|
+
|
194
|
+
5.times do # ticks 28 - 32
|
195
|
+
update_both
|
196
|
+
expect_spaces_to_match
|
197
|
+
expect(game.space.npcs.size).to eq(0)
|
198
|
+
end
|
199
|
+
|
200
|
+
# tick 33
|
201
|
+
update_both
|
202
|
+
expect(game.tick).to eq(33)
|
203
|
+
expect(game.space.npcs.size).to eq(1)
|
204
|
+
|
205
|
+
expect_spaces_to_match
|
206
|
+
|
207
|
+
# Command generated at tick 33, scheduled for tick 39
|
208
|
+
window.generate_move :rise_up
|
209
|
+
5.times do # ticks 34 - 38
|
210
|
+
update_both
|
211
|
+
$stderr.puts "TICK ##{game.tick}"
|
212
|
+
expect_spaces_to_match
|
213
|
+
expect(plr.y).to eq(800)
|
214
|
+
end
|
215
|
+
41.times do |n| # ticks 39 - 79
|
216
|
+
update_both
|
217
|
+
$stderr.puts "TICK ##{game.tick}"
|
218
|
+
expect(plr.y).to eq(800 - (10 * n))
|
219
|
+
cplr = window.engine.space.players.first
|
220
|
+
binding.pry unless cplr == plr
|
221
|
+
expect(cplr).to eq(plr)
|
222
|
+
expect_spaces_to_match
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
context "with no registry syncs" do
|
227
|
+
let(:registry_broadcast_every) { 0 }
|
228
|
+
it "is in sync after one update" do
|
229
|
+
window
|
230
|
+
update_both
|
231
|
+
|
232
|
+
expect_spaces_to_match
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|