settlers 0.2.1 → 0.3.0

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