settlers 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/settlers.rb CHANGED
@@ -1,3 +1,28 @@
1
- require 'settlers/java_command'
2
- require 'settlers/jar'
3
- require 'settlers/application'
1
+ require 'pathname'
2
+ require 'rbconfig'
3
+ require 'rbconfig/datadir'
4
+
5
+ module Settlers
6
+ VERSION = '0.3.0'
7
+
8
+ autoload :Address, 'settlers/address'
9
+ autoload :Application, 'settlers/application'
10
+ autoload :Bonjour, 'settlers/bonjour'
11
+ autoload :Client, 'settlers/client'
12
+ autoload :Collector, 'settlers/collector'
13
+ autoload :Java, 'settlers/java'
14
+ autoload :Observer, 'settlers/observer'
15
+ autoload :Server, 'settlers/server'
16
+ autoload :UI, 'settlers/ui'
17
+
18
+ def self.datadir
19
+ @@datadir ||= begin
20
+ datadir = RbConfig.datadir('settlers')
21
+ if !File.exist?(datadir)
22
+ warn "#{datadir} does not exist. Trying again with data directory relative to __FILE__."
23
+ datadir = File.expand_path('../../data/settlers', __FILE__)
24
+ end
25
+ Pathname.new(datadir)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ require 'ipaddr'
2
+
3
+ module Settlers
4
+ class Address < Struct.new(:name, :host, :port)
5
+ include Comparable
6
+
7
+ def self.from(reply, info)
8
+ new reply.name, info.address, reply.port
9
+ end
10
+
11
+ def <=>(other)
12
+ [name, ip, port] <=> [other.name, other.ip, other.port]
13
+ end
14
+
15
+ def ip
16
+ IPAddr.new(host)
17
+ end
18
+
19
+ def to_s
20
+ "#{name} (#{host}:#{port})"
21
+ end
22
+ end
23
+ end
@@ -1,131 +1,66 @@
1
- require 'optparse'
2
- require 'set'
3
- require 'shellwords'
4
-
5
- require 'rubygems'
6
- require 'dnssd'
7
- require 'highline/import'
8
-
9
1
  module Settlers
10
2
  class Application
11
- attr_accessor :server, :client, :style
12
-
13
- def initialize(*args)
14
- self.server = Server.new
15
- self.client = Client.new
16
- self.style = StandaloneGame
17
- parse_options(args)
18
- end
3
+ DEFAULT_PORT = 7777
19
4
 
20
- def run
21
- style.new(self).run
22
- end
5
+ def initialize
6
+ @collector = Collector.new
7
+ @ui = UI.new
23
8
 
24
- def start_server
25
- server.start
26
- end
9
+ @defaults = %w(--server)
27
10
 
28
- def start_client(server)
29
- client.start(server)
30
- end
11
+ @options = OptionParser.new do |opts|
12
+ opts.banner = "Usage: #{File.basename($0)} [--client]"
13
+ opts.separator ''
14
+ opts.version = Settlers::VERSION
31
15
 
32
- def find_server
33
- choose(*available_servers)
34
- end
16
+ opts.on('-c', '--client', 'Discover and connect to a game server.') do
17
+ @announcer = NullObject.new
18
+ @discoverer = Bonjour.new
19
+ @server = NullObject.new
20
+ end
35
21
 
36
- private
22
+ opts.on('-s', '--server[=PORT]', Integer, 'Launch, announce, and connect to a game server.', ". PORT defaults to #{DEFAULT_PORT}.") do |port|
23
+ @announcer = Bonjour.new
24
+ @discoverer = NullObject.new
25
+ @server = Server.new(port || DEFAULT_PORT)
26
+ end
37
27
 
38
- def parse_options(args)
39
- # TODO it would be nice to provide a quick tabtab definition here.
40
- OptionParser.new do |opts|
41
- opts.on('-c', '--client') { self.style = BonjourClientGame }
42
- opts.parse!(args)
28
+ opts.separator 'Defaults:'
29
+ opts.separator "#{opts.summary_indent}#{@defaults.join(' ')}"
43
30
  end
44
31
  end
45
32
 
46
- class StandaloneGame < Struct.new(:app)
47
- def run
48
- app.start_server
49
- app.start_client(app.server)
33
+ def run(args)
34
+ begin
35
+ @options.parse!(@defaults.dup.concat(args))
36
+ rescue OptionParser::ParseError
37
+ @options.abort($!)
50
38
  end
51
- end
52
39
 
53
- class BonjourClientGame < Struct.new(:app)
54
- def run
55
- app.start_client(app.find_server)
56
- end
57
- end
40
+ @server.add_observer(@announcer)
41
+ @server.add_observer(@collector)
42
+ @server.start
58
43
 
59
- def available_servers(timeout = 3)
60
- servers = Set.new
44
+ @discoverer.add_observer(@collector)
45
+ @discoverer.start
61
46
 
62
- dns = DNSSD.browse('_settlers._tcp') do |reply|
63
- DNSSD.resolve(reply.name, reply.type, reply.domain) do |resolve_reply|
64
- servers << RemoteServer.new(reply.name, resolve_reply.target, resolve_reply.port)
65
- end
47
+ @ui.choose_server(@collector) do |address|
48
+ Client.new(address).start
66
49
  end
67
-
68
- puts 'Looking for settlers servers nearby...'
69
- sleep timeout
70
- dns.stop
71
-
72
- return servers
73
50
  end
74
51
 
75
- class Server
76
- # Maybe we can change these to attr_writer as we'd like to make them command-line configurable.
77
- attr_reader :name, :host, :port, :startup_delay, :maximum_connections, :username, :password, :robot_names
78
-
79
- def initialize
80
- @name = "#{`hostname -s`.chomp}-#{`whoami`.chomp}"
81
- @host = 'localhost'
82
- @port = 8880
83
- @startup_delay = 4
84
- @maximum_connections = 12
85
- @username = 'root'
86
- @password = ''
87
- @robot_names = %w(Leonardo Humperdink Elwood)
88
- end
89
-
90
- def start
91
- announce
92
- serve
93
- sleep(startup_delay)
94
- start_robots
95
- end
96
-
97
- private
98
-
99
- def announce
100
- # Maybe it would be nice to include a TextRecord here providing something of a description?
101
- DNSSD.register(name, '_settlers._tcp', 'local', port) do |rr|
102
- puts "Announcing settlers server available on port #{port}."
103
- end
104
- end
52
+ private
105
53
 
106
- def serve
107
- # TODO move the Shellwords.escape call inside of JavaCommand.start
108
- Jar.new('JSettlersServer.jar').running('soc.server.SOCServer').start(port, maximum_connections, username, Shellwords.escape(password))
54
+ class NullObject #:nodoc:
55
+ def add_observer(observer)
109
56
  end
110
57
 
111
- def start_robots
112
- robot_names.each do |name|
113
- # TODO move the Shellwords.escape call inside of JavaCommand.start
114
- Jar.new('JSettlersServer.jar').running('soc.robot.SOCRobotClient').start(host, port, name, Shellwords.escape(password))
115
- end
58
+ def update(*args)
116
59
  end
117
- end
118
60
 
119
- class RemoteServer < Struct.new(:name, :host, :port)
120
- def to_s
121
- "#{name} (#{host}:#{port})"
61
+ def start
122
62
  end
123
63
  end
124
64
 
125
- class Client
126
- def start(server)
127
- Jar.new('JSettlers.jar').running('soc.client.SOCPlayerClient').run(server.host, server.port)
128
- end
129
- end
130
65
  end
131
66
  end
@@ -0,0 +1,35 @@
1
+ require 'dnssd'
2
+ require 'observer'
3
+
4
+ module Settlers
5
+ class Bonjour
6
+ TYPE = '_settlers._tcp'
7
+
8
+ include Observable
9
+ include Observer
10
+
11
+ # To confirm registration is working, run `mDNS -B _settlers._tcp`.
12
+ def server_exists_at(address)
13
+ DNSSD.register!(address.name, TYPE, nil, address.port)
14
+ end
15
+
16
+ def start(timeout=5)
17
+ puts "Looking for servers for up to #{timeout} seconds..."
18
+
19
+ DNSSD.browse(TYPE) do |browse|
20
+ DNSSD.resolve(browse) do |reply|
21
+ DNSSD.getaddrinfo!(reply.target, DNSSD::Service::IPv4) do |info|
22
+ changed
23
+ notify_observers :server_exists_at, Address.from(reply, info)
24
+ end
25
+ end
26
+ end
27
+
28
+ sleep timeout
29
+ end
30
+
31
+ def DNSSD.getaddrinfo!(*args, &block) #:nodoc:
32
+ run(DNSSD::Service.new, :getaddrinfo, *args, &block)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ module Settlers
2
+ class Client
3
+ def initialize(address)
4
+ @address = address
5
+ end
6
+
7
+ def start
8
+ Java.client.run(@address.host, @address.port)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ require 'set'
2
+
3
+ module Settlers
4
+ class Collector
5
+ include Enumerable
6
+ include Observer
7
+
8
+ def initialize
9
+ @addresses = Set.new
10
+ end
11
+
12
+ def server_exists_at(address)
13
+ @addresses.add(address)
14
+ end
15
+
16
+ def each(&block)
17
+ @addresses.sort.each(&block)
18
+ end
19
+
20
+ def size
21
+ @addresses.size
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,39 @@
1
+ module Settlers
2
+ class Java
3
+ def self.server
4
+ new('soc.server.SOCServer')
5
+ end
6
+
7
+ def self.client
8
+ new('soc.client.SOCPlayerClient')
9
+ end
10
+
11
+ JAR = Settlers.datadir.join('jsettlers-1.1.09', 'JSettlers.jar')
12
+
13
+ def initialize(class_name)
14
+ @class_name = class_name
15
+ end
16
+
17
+ def run(*args)
18
+ pid = quietly_fork(args)
19
+ Process.waitpid(pid)
20
+ end
21
+
22
+ def start(*args)
23
+ pid = quietly_fork(args)
24
+ at_exit { Process.kill 'INT', pid }
25
+ end
26
+
27
+ private
28
+
29
+ def quietly_fork(args)
30
+ args.map! { |arg| arg.to_s }
31
+
32
+ fork do
33
+ STDOUT.reopen '/dev/null'
34
+ STDERR.reopen '/dev/null'
35
+ exec 'java', '-cp', JAR, @class_name, *args
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ module Settlers
2
+ module Observer
3
+ def update(*args)
4
+ send(*args) if respond_to?(args.first)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ require 'observer'
2
+
3
+ module Settlers
4
+ class Server
5
+ include Observable
6
+
7
+ def initialize(port)
8
+ @address = Address.new(name, '127.0.0.1', port)
9
+ end
10
+
11
+ def start
12
+ Java.server.start(@address.port, 10, 'root', '')
13
+ changed
14
+ notify_observers :server_exists_at, @address
15
+ end
16
+
17
+ private
18
+
19
+ def name
20
+ login = Etc.getlogin
21
+ name = Etc.getpwnam(login).gecos
22
+ "#{name}'s Settlers Server"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ require 'highline'
2
+
3
+ module Settlers
4
+ class UI
5
+ def initialize
6
+ @console = HighLine.new
7
+ end
8
+
9
+ def choose_server(list)
10
+ yield case list.size
11
+ when 0
12
+ abort 'No servers to choose from.'
13
+ when 1
14
+ list.first
15
+ else
16
+ choose_from_many(list)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def choose_from_many(list)
23
+ begin
24
+ @console.choose(*list)
25
+ rescue Interrupt
26
+ abort "\nGoodbye."
27
+ end
28
+ end
29
+ end
30
+ end
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: settlers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ hash: 19
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
5
11
  platform: ruby
6
12
  authors:
7
13
  - Matthew Todd
@@ -9,39 +15,51 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2010-01-09 00:00:00 +01:00
13
- default_executable: settlers
18
+ date: 2010-06-18 00:00:00 +03:00
19
+ default_executable:
14
20
  dependencies:
15
21
  - !ruby/object:Gem::Dependency
16
- name: shoe
17
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
23
+ none: false
20
24
  requirements:
21
25
  - - ">="
22
26
  - !ruby/object:Gem::Version
27
+ hash: 3
28
+ segments:
29
+ - 0
23
30
  version: "0"
24
- version:
31
+ requirement: *id001
32
+ name: shoe
33
+ prerelease: false
34
+ type: :development
25
35
  - !ruby/object:Gem::Dependency
26
- name: dnssd
27
- type: :runtime
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
36
+ version_requirements: &id002 !ruby/object:Gem::Requirement
37
+ none: false
30
38
  requirements:
31
39
  - - ">="
32
40
  - !ruby/object:Gem::Version
41
+ hash: 3
42
+ segments:
43
+ - 0
33
44
  version: "0"
34
- version:
35
- - !ruby/object:Gem::Dependency
45
+ requirement: *id002
36
46
  name: highline
47
+ prerelease: false
37
48
  type: :runtime
38
- version_requirement:
39
- version_requirements: !ruby/object:Gem::Requirement
49
+ - !ruby/object:Gem::Dependency
50
+ version_requirements: &id003 !ruby/object:Gem::Requirement
51
+ none: false
40
52
  requirements:
41
53
  - - ">="
42
54
  - !ruby/object:Gem::Version
55
+ hash: 3
56
+ segments:
57
+ - 0
43
58
  version: "0"
44
- version:
59
+ requirement: *id003
60
+ name: dnssd
61
+ prerelease: false
62
+ type: :runtime
45
63
  description:
46
64
  email: matthew.todd@gmail.com
47
65
  executables:
@@ -51,21 +69,24 @@ extensions: []
51
69
  extra_rdoc_files:
52
70
  - README.rdoc
53
71
  files:
54
- - Rakefile
55
- - settlers.gemspec
56
72
  - README.rdoc
57
73
  - bin/settlers
74
+ - data/settlers/jsettlers-1.1.09/COPYING.txt
75
+ - data/settlers/jsettlers-1.1.09/JSettlers.jar
76
+ - data/settlers/jsettlers-1.1.09/README.txt
77
+ - data/settlers/jsettlers-1.1.09/VERSIONS.txt
78
+ - lib/settlers/address.rb
58
79
  - lib/settlers/application.rb
59
- - lib/settlers/jar.rb
60
- - lib/settlers/java_command.rb
80
+ - lib/settlers/bonjour.rb
81
+ - lib/settlers/client.rb
82
+ - lib/settlers/collector.rb
83
+ - lib/settlers/java.rb
84
+ - lib/settlers/observer.rb
85
+ - lib/settlers/server.rb
86
+ - lib/settlers/ui.rb
61
87
  - lib/settlers.rb
62
- - resources/jsettlers-1.0.6/COPYING.txt
63
- - resources/jsettlers-1.0.6/JSettlers.jar
64
- - resources/jsettlers-1.0.6/JSettlersServer.jar
65
- - resources/jsettlers-1.0.6/README.txt
66
- - resources/jsettlers-1.0.6/VERSIONS.txt
67
88
  has_rdoc: true
68
- homepage:
89
+ homepage: http://github.com/matthewtodd/settlers
69
90
  licenses: []
70
91
 
71
92
  post_install_message:
@@ -73,26 +94,34 @@ rdoc_options:
73
94
  - --main
74
95
  - README.rdoc
75
96
  - --title
76
- - settlers-0.2.1
97
+ - settlers-0.3.0
77
98
  - --inline-source
99
+ - --webcvs
100
+ - http://github.com/matthewtodd/settlers/blob/v0.3.0/
78
101
  require_paths:
79
102
  - lib
80
103
  required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
81
105
  requirements:
82
106
  - - ">="
83
107
  - !ruby/object:Gem::Version
108
+ hash: 3
109
+ segments:
110
+ - 0
84
111
  version: "0"
85
- version:
86
112
  required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
87
114
  requirements:
88
115
  - - ">="
89
116
  - !ruby/object:Gem::Version
117
+ hash: 3
118
+ segments:
119
+ - 0
90
120
  version: "0"
91
- version:
92
- requirements: []
93
-
121
+ requirements:
122
+ - java
94
123
  rubyforge_project:
95
- rubygems_version: 1.3.5
124
+ rubygems_version: 1.3.7
96
125
  signing_key:
97
126
  specification_version: 3
98
127
  summary: Provides a simple command-line executable for playing Robb Thomas' JSettlers game.