textflight-client 1.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.
@@ -0,0 +1,33 @@
1
+ require "textflight-client/version"
2
+ require "textflight-client/logging"
3
+ require "textflight-client/environment"
4
+ require "textflight-client/dot_dir"
5
+ require "textflight-client/string_utils"
6
+ require "textflight-client/command_parser"
7
+ require "textflight-client/response_parser"
8
+ require "textflight-client/models/model"
9
+ require "textflight-client/models/nav"
10
+ require "textflight-client/models/scan"
11
+ require "textflight-client/models/status_report"
12
+ require "textflight-client/models/status"
13
+ require "textflight-client/models/local"
14
+ require "textflight-client/tfprompt"
15
+
16
+ module TFClient
17
+
18
+ # Prints a deprecated message that includes the line number.
19
+ #
20
+ # @param [String] version Indicates when the feature was deprecated.
21
+ # @param [String] msg Deprecation message (possibly suggesting alternatives)
22
+ # @return [void]
23
+ def self.deprecated(version, msg)
24
+
25
+ stack = Kernel.caller(0, 6)[1..-1].join("\n")
26
+
27
+ msg = "deprecated '#{version}' - #{msg}\n#{stack}"
28
+
29
+ $stderr.puts "\033[34mWARN: #{msg}\033[0m"
30
+ $stderr.flush
31
+ end
32
+
33
+ end
@@ -0,0 +1,42 @@
1
+ module TFClient
2
+ class CommandParser
3
+
4
+ DIRECTION_MAP = {
5
+ n: "6",
6
+ ne: "7",
7
+ e: "4",
8
+ se: "2",
9
+ s: "1",
10
+ sw: "0",
11
+ w: "3",
12
+ nw: "5"
13
+ }
14
+
15
+ # 0: 315 degrees, X=-1, Y=-1 (northwest)
16
+ # 1: 0 degrees, X=0, Y=-1 (north)
17
+ # 2: 45 degrees, X=1, Y=-1 (northeast)
18
+ # 3: 270 degrees, X=-1, Y=0 (west)
19
+ # 4: 90 degrees, X=1, Y=0 (east)
20
+ # 5: 225 degrees, X=-1, Y=1 (southwest)
21
+ # 6: 180 degrees, X=0, Y=-1 (south)
22
+ # 7: 135 degrees, X=1, Y=-1 (southeast)
23
+
24
+ attr_reader :command
25
+ def initialize(command:)
26
+ @command = command
27
+ end
28
+
29
+ def parse
30
+ if @command == "quit" || @command == "exit"
31
+ return "exit"
32
+ end
33
+
34
+ direction = DIRECTION_MAP[@command.to_sym]
35
+ if direction
36
+ "jump #{direction}"
37
+ else
38
+ @command
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,21 @@
1
+
2
+ module TFClient::DotDir
3
+
4
+ def self.directory
5
+ home = TFClient::Environment.user_home_directory
6
+ dir = File.join(home, ".textflight", "client")
7
+ if !File.exist?(dir)
8
+ FileUtils.mkdir_p(dir)
9
+ end
10
+ dir
11
+ end
12
+
13
+ def self.local_database_file(dev:)
14
+ if dev
15
+ File.expand_path(File.join(self.directory,"development.db"))
16
+ else
17
+ File.expand_path(File.join(self.directory,"production.db"))
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,55 @@
1
+
2
+ module TFClient
3
+ class Environment
4
+
5
+ # Returns the user home directory
6
+ def self.user_home_directory
7
+ require 'etc'
8
+ # If uid is omitted, the value from Passwd[:uid] is returned instead.
9
+ #noinspection RubyArgCount
10
+ Etc.getpwuid.dir
11
+ end
12
+
13
+ # Returns true if Windows environment
14
+ def self.windows_env?
15
+ if @@windows_env.nil?
16
+ @@windows_env = Environment.host_os_is_win?
17
+ end
18
+
19
+ @@windows_env
20
+ end
21
+
22
+ # Returns true if debugging is enabled.
23
+ def self.debug?
24
+ ENV['DEBUG'] == '1'
25
+ end
26
+
27
+ private
28
+
29
+ # !@visibility private
30
+ def self.ci_var_defined?
31
+ value = ENV["CI"]
32
+ !!value && value != ''
33
+ end
34
+
35
+
36
+ # @visibility private
37
+ WIN_PATTERNS = [
38
+ /bccwin/i,
39
+ /cygwin/i,
40
+ /djgpp/i,
41
+ /mingw/i,
42
+ /mswin/i,
43
+ /wince/i,
44
+ ]
45
+
46
+ # @!visibility private
47
+ @@windows_env = nil
48
+
49
+ # @!visibility private
50
+ def self.host_os_is_win?
51
+ ruby_platform = RbConfig::CONFIG["host_os"]
52
+ !!WIN_PATTERNS.find { |r| ruby_platform =~ r }
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,58 @@
1
+
2
+ module TFClient
3
+
4
+ # cyan
5
+ def self.log_unix_cmd(msg)
6
+ if ENV["DEBUG"] == "1"
7
+ puts Color.cyan("SHELL: #{msg}") if msg
8
+ end
9
+ end
10
+
11
+ # blue
12
+ def self.warn(msg)
13
+ puts Color.blue(" WARN: #{msg}") if msg
14
+ end
15
+
16
+ # magenta
17
+ def self.debug(msg)
18
+ if ENV["DEBUG"] == "1"
19
+ puts Color.magenta("DEBUG: #{msg}") if msg
20
+ end
21
+ end
22
+
23
+ # green
24
+ def self.info(msg)
25
+ puts Color.green(" INFO: #{msg}") if msg
26
+ end
27
+
28
+ # red
29
+ def self.error(msg)
30
+ puts Color.red("ERROR: #{msg}") if msg
31
+ end
32
+
33
+ module Color
34
+ def self.colorize(string, color)
35
+ "\033[#{color}m#{string}\033[0m"
36
+ end
37
+
38
+ def self.red(string)
39
+ colorize(string, 31)
40
+ end
41
+
42
+ def self.blue(string)
43
+ colorize(string, 34)
44
+ end
45
+
46
+ def self.magenta(string)
47
+ colorize(string, 35)
48
+ end
49
+
50
+ def self.cyan(string)
51
+ colorize(string, 36)
52
+ end
53
+
54
+ def self.green(string)
55
+ colorize(string, 32)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,69 @@
1
+
2
+ module TFClient
3
+ module Models
4
+ module Local
5
+ require "sequel"
6
+ end
7
+ end
8
+ end
9
+
10
+ module TFClient::Models::Local
11
+
12
+ require_relative "../../textflight-client/logging.rb"
13
+ require "json"
14
+
15
+ class Database
16
+
17
+ attr_reader :connection, :path
18
+
19
+ def initialize(path:)
20
+ @connection = Sequel.connect("sqlite://#{path}")
21
+
22
+ if !@connection.table_exists?(:systems)
23
+ @connection.create_table(:systems) do
24
+ primary_key :id
25
+ column :x, Integer
26
+ column :y, Integer
27
+ column :claimed_by, String
28
+ column :brightness, Integer
29
+ column :asteroid_ore, String
30
+ column :asteroid_density, Integer
31
+ column :links, String
32
+ column :planets, String
33
+ end
34
+ end
35
+ end
36
+
37
+ def system_for_id(id:)
38
+ table = @connection[:systems]
39
+ table.where(id: id)
40
+ end
41
+
42
+ def create_system(id:, nav:)
43
+ TFClient.info("creating a new system with id: #{id}")
44
+
45
+ links = nav.links.items.map do |link|
46
+ [link[:index], link[:direction], link[:drag], link[:faction]]
47
+ end
48
+
49
+ planets = nav.planets.items.map do |planet|
50
+ [planet[:index], planet[:type], planet[:name], planet[:faction]]
51
+ end
52
+
53
+ table = @connection[:systems]
54
+ table.insert(
55
+ {
56
+ id: id,
57
+ x: nav.coordinates.x,
58
+ y: nav.coordinates.y,
59
+ claimed_by: nav.claimed_by ? nav.claimed_by.faction : "",
60
+ brightness: nav.brightness.value,
61
+ asteroid_ore: nav.asteroids.ore,
62
+ asteroid_density: nav.asteroids.density,
63
+ links: JSON.generate(links),
64
+ planets: JSON.generate(planets)
65
+ }
66
+ )
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,55 @@
1
+
2
+ module TFClient
3
+ module Models
4
+ require "cousin_roman"
5
+
6
+ TABLE_OPTIONS = { padding: [0,1,0,1], width: 80, resize: true }
7
+
8
+ class Response
9
+ attr_accessor :lines, :response
10
+
11
+ def initialize(lines:)
12
+ @lines = lines.dup
13
+ @response = []
14
+ end
15
+ end
16
+
17
+ class Model
18
+ attr_accessor :label, :translation, :values_hash
19
+
20
+ def initialize(line:)
21
+ tokens = ResponseParser.tokenize_line(line: line)
22
+ tokens_hash = ResponseParser.label_and_translation(tokens: tokens)
23
+ @label = tokens_hash[:label]
24
+ @translation = tokens_hash[:translation]
25
+ @values_hash = ResponseParser.hash_from_line_values(line: line)
26
+ end
27
+ end
28
+
29
+ class ModelWithItems < Model
30
+ require "tty-table"
31
+
32
+ attr_reader :items
33
+
34
+ def count
35
+ @items.count
36
+ end
37
+
38
+ def to_s
39
+ "#{@translation}: #{@items.map { |item| item[:string]}}"
40
+ end
41
+
42
+ def items_to_s
43
+ @items.map { |item| "\t#{item[:string]}" }
44
+ end
45
+
46
+ def response_str
47
+ "#{@translation}:\n#{items_to_s.join("\n")}"
48
+ end
49
+
50
+ def lines_offset
51
+ @items.length + 1
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,293 @@
1
+
2
+ module TFClient
3
+ module Models
4
+ class Nav < Response
5
+
6
+ GAME_LINK_TO_COMPASS_MAP = {
7
+ "0" => "sw",
8
+ "1" => "s",
9
+ "2" => "se",
10
+ "3" => "w",
11
+ "4" => "e",
12
+ "5" => "nw",
13
+ "6" => "n",
14
+ "7" => "ne"
15
+ }
16
+
17
+ COMPASS_TO_GAME_LINK_MAP = {
18
+ "n" => 6,
19
+ "ne" => 7,
20
+ "e" => 4,
21
+ "se" => 2,
22
+ "s" => 1,
23
+ "sw" => 0,
24
+ "w" => 3,
25
+ "nw" => 5
26
+ }
27
+
28
+ COMPASS_ORDER = {
29
+ "n" => 0,
30
+ "ne" => 1,
31
+ "e" => 2,
32
+ "se" => 3,
33
+ "s" => 4,
34
+ "sw" => 5,
35
+ "w" => 6,
36
+ "nw" => 7
37
+ }
38
+
39
+ LINE_IDENTIFIER = [
40
+ "Coordinates",
41
+ "Claimed by",
42
+ "Brightness",
43
+ "Asteroids",
44
+ "Links",
45
+ "Planets",
46
+ "Structures"
47
+ ].freeze
48
+
49
+ attr_reader :coordinates, :claimed_by, :brightness, :asteroids
50
+ attr_reader :links, :planets, :structures
51
+
52
+ def initialize(lines:)
53
+ super(lines: lines)
54
+
55
+ LINE_IDENTIFIER.each_with_index do |label, label_index|
56
+ var_name = ResponseParser.snake_case_sym_from_string(string: label)
57
+ class_name = ResponseParser.camel_case_from_string(string: label)
58
+
59
+ clazz = ResponseParser.model_class_from_string(string: class_name)
60
+ if clazz.nil?
61
+ raise "could not find class name: #{class_name}"
62
+ end
63
+
64
+ line, _ = ResponseParser.line_and_index_for_beginning_with(lines: @lines,
65
+ string: label)
66
+
67
+ next if line.nil?
68
+
69
+ if label_index < 4
70
+ var = clazz.new(line: line)
71
+ else
72
+ var = clazz.new(lines: @lines)
73
+ end
74
+
75
+ instance_variable_set("@#{var_name}", var)
76
+ end
77
+ end
78
+
79
+ def response
80
+ puts @planets
81
+
82
+ puts @links
83
+ table = TTY::Table.new(rows: [[
84
+ "#{@brightness.to_s}",
85
+ "#{@asteroids.to_s}",
86
+ "#{@coordinates}"]
87
+ ])
88
+
89
+ puts table.render(:ascii, Models::TABLE_OPTIONS) do |renderer|
90
+ renderer.alignments= [:left, :center, :right]
91
+ end
92
+
93
+ puts @structures
94
+ end
95
+
96
+ def to_s
97
+ "#<Nav: #{@coordinates.to_s}>"
98
+ end
99
+ end
100
+
101
+ class Coordinates < Model
102
+ attr_reader :x, :y
103
+
104
+ def initialize(line:)
105
+ super(line: line)
106
+ @x = @values_hash[:x].to_i
107
+ @y = @values_hash[:y].to_i
108
+ end
109
+
110
+ def to_s
111
+ %Q[#{@translation}: (#{@x},#{@y})]
112
+ end
113
+ end
114
+
115
+ class ClaimedBy < Model
116
+ attr_reader :faction
117
+
118
+ def initialize(line:)
119
+ super(line: line)
120
+ @faction = @values_hash[:faction]
121
+ end
122
+
123
+ def to_s
124
+ %Q[#{@translation} '#{@faction}']
125
+ end
126
+ end
127
+
128
+ class Brightness < Model
129
+ attr_reader :value, :percent
130
+
131
+ def initialize(line:)
132
+ super(line: line)
133
+ @value = @values_hash[:brightness].to_i
134
+ @percent = ((@value/255.0) * 100).round
135
+ end
136
+
137
+ def to_s
138
+ %Q[#{@translation}: #{@value} => #{@percent}%]
139
+ end
140
+ end
141
+
142
+ class Asteroids < Model
143
+ attr_reader :ore, :density, :percent
144
+
145
+ def initialize(line:)
146
+ super(line: line)
147
+ @ore = @values_hash[:asteroid_type]
148
+ @density = @values_hash[:asteroid_density].to_i
149
+ @percent = ((@density/7.0) * 100).round
150
+ end
151
+
152
+ def to_s
153
+ %Q[#{@translation}: #{@ore} (#{@density}) => #{@percent}%]
154
+ end
155
+ end
156
+
157
+ class Links < ModelWithItems
158
+ def initialize(lines:)
159
+ line, index = ResponseParser.line_and_index_for_beginning_with(lines: lines,
160
+ string: "Links")
161
+ super(line: line)
162
+ items = ResponseParser.collect_list_items(lines: lines, start_index: index + 1)
163
+ @items = items.map do |item|
164
+ line = item.strip
165
+ hash = ResponseParser.hash_from_line_values(line: line)
166
+
167
+ index = hash[:index].to_i
168
+ faction = hash[:faction]
169
+ drag = hash[:link_drag].to_i
170
+
171
+ # direction is WIP
172
+ direction = Nav::GAME_LINK_TO_COMPASS_MAP[index.to_s]
173
+ {
174
+ index: index, drag: drag, direction: direction, faction: faction
175
+ }
176
+ end.sort do |a, b|
177
+ Nav::COMPASS_ORDER[a[:direction]] <=> Nav::COMPASS_ORDER[b[:direction]]
178
+ end
179
+ end
180
+
181
+ def to_s
182
+ table = TTY::Table.new(header: [
183
+ {value: "#{@translation}", alignment: :right},
184
+ {value: "drag", alignment: :center},
185
+ {value: "faction", alignment: :center},
186
+ {value: "original", alignment: :center},
187
+ {value: "direction", alignment: :center}
188
+ ])
189
+
190
+ @items.each do |item|
191
+ table << [
192
+ "#{item[:direction]}",
193
+ item[:drag],
194
+ item[:faction],
195
+ "[#{item[:index]}]",
196
+ "#{item[:direction]}"
197
+ ]
198
+ end
199
+
200
+ table.render(:ascii, Models::TABLE_OPTIONS) do |renderer|
201
+ renderer.alignments= [:right, :right, :center, :center, :center, :center]
202
+ end
203
+ end
204
+ end
205
+
206
+ class Planets < ModelWithItems
207
+
208
+ def initialize(lines:)
209
+ line, index = ResponseParser.line_and_index_for_beginning_with(lines: lines,
210
+ string: "Planets")
211
+ super(line: line)
212
+
213
+ items = ResponseParser.collect_list_items(lines: lines, start_index: index + 1)
214
+ @items = items.map do |item|
215
+ line = item.strip
216
+
217
+ hash = ResponseParser.hash_from_line_values(line: line)
218
+
219
+ index = hash[:index].to_i
220
+ type = hash[:planet_type]
221
+ name = hash[:name]
222
+ faction = hash[:faction]
223
+
224
+ { index: index, type: type, name: name, faction: faction }
225
+ end
226
+ end
227
+
228
+ def to_s
229
+ return "" if @items.empty?
230
+ table = TTY::Table.new(header: [
231
+ {value: "#{@translation}", alignment: :right},
232
+ {value: "type", alignment: :center},
233
+ {value: "name", alignment: :center},
234
+ {value: "faction", alignment: :center},
235
+ {value: "index", alignment: :center}
236
+ ])
237
+
238
+ @items.each do |item|
239
+ table << [
240
+ "[#{item[:index]}]",
241
+ item[:type],
242
+ item[:name],
243
+ item[:faction],
244
+ "[#{item[:index]}]"
245
+ ]
246
+ end
247
+
248
+ table.render(:ascii, Models::TABLE_OPTIONS) do |renderer|
249
+ renderer.alignments= [:right, :right, :center, :center, :center]
250
+ end
251
+ end
252
+ end
253
+
254
+ class Structures < ModelWithItems
255
+
256
+ def initialize(lines:)
257
+ line, index = ResponseParser.line_and_index_for_beginning_with(lines: lines,
258
+ string: "Structures")
259
+ super(line: line)
260
+
261
+ items = ResponseParser.collect_list_items(lines: lines, start_index: index + 1)
262
+ @items = items.map do |item|
263
+ line = item.strip
264
+
265
+ hash = ResponseParser.hash_from_line_values(line: line)
266
+
267
+ id = hash[:id].to_i
268
+ name = hash[:name]
269
+ # TODO: distinguish shipyards from bases
270
+ type = hash[:sclass] || "base"
271
+ { id: id, name: name, type: type }
272
+ end
273
+ end
274
+
275
+ def to_s
276
+ table = TTY::Table.new(header: [
277
+ {value: "#{@translation}", alignment: :right},
278
+ {value: "name", alignment: :center},
279
+ {value: "ship class", alignment: :center},
280
+ {value: "id", alignment: :center }
281
+ ])
282
+
283
+ @items.each do |item|
284
+ table << ["[#{item[:id]}]", item[:name], item[:type], "[#{item[:id]}]"]
285
+ end
286
+
287
+ table.render(:ascii, Models::TABLE_OPTIONS) do |renderer|
288
+ renderer.alignments= [:right, :right, :center, :center]
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end