toy_robot 1.0.4
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.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +18 -0
- data/Rakefile +1 -0
- data/bin/toy_robot +4 -0
- data/lib/toy_robot.rb +19 -0
- data/lib/toy_robot/bin/io_utils.rb +38 -0
- data/lib/toy_robot/bin/runner.rb +108 -0
- data/lib/toy_robot/files/manifest.txt +9 -0
- data/lib/toy_robot/files/roboto.jpg +0 -0
- data/lib/toy_robot/manifest_reader.rb +16 -0
- data/lib/toy_robot/roboto.rb +90 -0
- data/lib/toy_robot/roboto/brain.rb +38 -0
- data/lib/toy_robot/roboto/motor_cortex.rb +67 -0
- data/lib/toy_robot/roboto/speech.rb +11 -0
- data/lib/toy_robot/simulator.rb +26 -0
- data/lib/toy_robot/table.rb +27 -0
- data/lib/toy_robot/version.rb +3 -0
- data/spec/bin/io_utils_spec.rb +70 -0
- data/spec/bin/runner_spec.rb +98 -0
- data/spec/fixtures/manifest_example.txt +5 -0
- data/spec/manifest_reader_spec.rb +32 -0
- data/spec/roboto/brain_spec.rb +36 -0
- data/spec/roboto/motor_cortex_spec.rb +322 -0
- data/spec/roboto/speech_spec.rb +13 -0
- data/spec/roboto_spec.rb +216 -0
- data/spec/simulator_spec.rb +37 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/table_spec.rb +54 -0
- data/toy_robot.gemspec +31 -0
- metadata +187 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 470b7ee06374f2b4e17346a8a842fb990766dd19
|
4
|
+
data.tar.gz: 5e6c4ba76194f3c01f500c54738518818b2317fb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c23a350702a5d232edfc69c501c5c7bcc3f5e12fa61b7f570089886761d1e5df1983b0b2e78321056f17ff0f88e7825bab67239a36c8a6f932e9a31cd81d1f98
|
7
|
+
data.tar.gz: 0ee1187e09d65ae884102910bafa0d73bdb9bc68778f509ced990b03454a19b6ead3433dcca85a941ee0e96d4fa908fde82449dd12767984f26638ed82a91cd7
|
data/.gitignore
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/lib/bundler/man/
|
26
|
+
|
27
|
+
# for a library or gem, you might want to ignore these files since the code is
|
28
|
+
# intended to run in multiple environments; otherwise, check them in:
|
29
|
+
Gemfile.lock
|
30
|
+
# .ruby-version
|
31
|
+
# .ruby-gemset
|
32
|
+
|
33
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
34
|
+
.rvmrc
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 louman
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/toy_robot
ADDED
data/lib/toy_robot.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'asciiart'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'rainbow'
|
4
|
+
require 'terminal-table'
|
5
|
+
|
6
|
+
require 'toy_robot/roboto'
|
7
|
+
require 'toy_robot/roboto/brain'
|
8
|
+
require 'toy_robot/roboto/motor_cortex'
|
9
|
+
require 'toy_robot/roboto/speech'
|
10
|
+
require 'toy_robot/table'
|
11
|
+
require 'toy_robot/manifest_reader'
|
12
|
+
require 'toy_robot/simulator'
|
13
|
+
|
14
|
+
require 'toy_robot/bin/io_utils'
|
15
|
+
require 'toy_robot/bin/runner'
|
16
|
+
|
17
|
+
module ToyRobot
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ToyRobot
|
2
|
+
module Bin
|
3
|
+
module IOUtils
|
4
|
+
|
5
|
+
# puts a message and sleep
|
6
|
+
def print_with_delay(msg, delay_time=2)
|
7
|
+
_print msg
|
8
|
+
delay delay_time
|
9
|
+
end
|
10
|
+
|
11
|
+
# just a wrapper for puts
|
12
|
+
def _print(msg)
|
13
|
+
puts msg
|
14
|
+
end
|
15
|
+
|
16
|
+
# just a wrapper for sleep
|
17
|
+
def delay(delay_time)
|
18
|
+
sleep delay_time
|
19
|
+
end
|
20
|
+
|
21
|
+
# returns a yellowish string
|
22
|
+
def yellow(text)
|
23
|
+
Rainbow(text).yellow
|
24
|
+
end
|
25
|
+
|
26
|
+
# returns a greenish string
|
27
|
+
def green(text)
|
28
|
+
Rainbow(text).green
|
29
|
+
end
|
30
|
+
|
31
|
+
# returns a reddish string
|
32
|
+
def red(text)
|
33
|
+
Rainbow(text).red
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module ToyRobot
|
2
|
+
module Bin
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
include IOUtils
|
6
|
+
|
7
|
+
attr_reader :simulator, :roboto, :selected_option
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@simulator = ToyRobot::Simulator.new
|
11
|
+
@roboto = @simulator.roboto
|
12
|
+
@roboto.name = robots.sample
|
13
|
+
end
|
14
|
+
|
15
|
+
def start
|
16
|
+
show_art
|
17
|
+
show_help
|
18
|
+
show_options
|
19
|
+
@selected_option = capture_input
|
20
|
+
handle_selected_option
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def capture_input
|
26
|
+
gets.chomp.strip
|
27
|
+
end
|
28
|
+
|
29
|
+
def handle_selected_option
|
30
|
+
case selected_option
|
31
|
+
when '1'
|
32
|
+
input_by_hand
|
33
|
+
when '2'
|
34
|
+
input_from_file
|
35
|
+
else
|
36
|
+
_print 'Invalid option, terminating simulator.'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def input_by_hand
|
41
|
+
boot_roboto('Ok, preparing roboto for manual orders')
|
42
|
+
_print green("-- #{@roboto.name} reporting for duty. What is you first order sir?")
|
43
|
+
command = capture_input
|
44
|
+
while command
|
45
|
+
begin
|
46
|
+
@simulator.run([command])
|
47
|
+
rescue StandardError
|
48
|
+
_print red('Order not recognized, sir!')
|
49
|
+
end
|
50
|
+
_print green("#{@roboto.name} waiting for new orders, sir!")
|
51
|
+
command = capture_input
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def input_from_file
|
56
|
+
boot_roboto('Ok, preparing roboto for orders manifest')
|
57
|
+
@simulator.run(read_manifest)
|
58
|
+
end
|
59
|
+
|
60
|
+
def read_manifest
|
61
|
+
ToyRobot::ManifestReader.new.read
|
62
|
+
end
|
63
|
+
|
64
|
+
def show_art
|
65
|
+
_print art
|
66
|
+
end
|
67
|
+
|
68
|
+
def show_help
|
69
|
+
_print help
|
70
|
+
end
|
71
|
+
|
72
|
+
def show_options
|
73
|
+
_print '1) Input orders by hand'
|
74
|
+
_print '2) Input orders from file'
|
75
|
+
end
|
76
|
+
|
77
|
+
def boot_roboto(acknowledge_message)
|
78
|
+
print_with_delay yellow(acknowledge_message)
|
79
|
+
print_with_delay yellow("Booting #{@roboto.name}")
|
80
|
+
print_with_delay yellow('Calibrating controls')
|
81
|
+
print_with_delay yellow('Activating AI')
|
82
|
+
print_with_delay yellow('1, 2, 3 testing')
|
83
|
+
end
|
84
|
+
|
85
|
+
def help
|
86
|
+
rows = []
|
87
|
+
rows << ['Type "exit" anytime to exit simulator']
|
88
|
+
rows << ['Valid commands are: PLACE, MOVE, LEFT, RIGHT, REPORT']
|
89
|
+
rows << ['PLACE: puts the toy robot on the table in position X,Y and facing NORTH, SOUTH, EAST or WEST.']
|
90
|
+
rows << ['MOVE: moves the toy robot one unit forward in the direction it is currently facing.']
|
91
|
+
rows << ['LEFT: rotates the robot 90 degrees counter-clockwise without changing the position of the robot.']
|
92
|
+
rows << ['RIGHT: rotates the robot 90 degrees clockwise without changing the position of the robot.']
|
93
|
+
rows << ['REPORT: announces the X,Y and F of the robot.']
|
94
|
+
table = Terminal::Table.new :rows => rows, title: 'Welcome to the Toy Robot Simulator!'
|
95
|
+
end
|
96
|
+
|
97
|
+
def robots
|
98
|
+
['T-800', 'Optimus Prime', 'R2-D2', 'C-3PO', 'Data', 'Sonny', 'Wall-E', 'Rosey', 'Robocop', 'T-1000']
|
99
|
+
end
|
100
|
+
|
101
|
+
def art
|
102
|
+
art = AsciiArt.new(File.join(File.dirname(__FILE__), '..', 'files', 'roboto.jpg'))
|
103
|
+
art.to_ascii_art(width: 70)
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
Binary file
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ToyRobot
|
2
|
+
class ManifestReader
|
3
|
+
|
4
|
+
attr_reader :file
|
5
|
+
|
6
|
+
def initialize(file_path:nil)
|
7
|
+
@file_path = file_path || File.join(File.dirname(__FILE__), 'files', 'manifest.txt')
|
8
|
+
@file = File.open(@file_path)
|
9
|
+
end
|
10
|
+
|
11
|
+
def read
|
12
|
+
@file.each_line.map(&:strip)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module ToyRobot
|
2
|
+
class Roboto
|
3
|
+
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def_delegator :@brain, :speak
|
7
|
+
def_delegator :@brain, :motor_cortex, :location
|
8
|
+
def_delegator :@brain, :understand_order
|
9
|
+
|
10
|
+
attr_accessor :table_edges, :brain, :name
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@brain = Brain.new
|
14
|
+
@name = 'Roboto'
|
15
|
+
end
|
16
|
+
|
17
|
+
# Sends order's method
|
18
|
+
def execute_order(order)
|
19
|
+
if order[:args].nil?
|
20
|
+
send(order[:command])
|
21
|
+
else
|
22
|
+
send(order[:command], order[:args])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Changes roboto location if is safe
|
27
|
+
# otherwise says advise message
|
28
|
+
def place(x:, y:, f:)
|
29
|
+
safe(x, y) do
|
30
|
+
location.reset(x: x.to_i, y: y.to_i, f: f.downcase.to_sym)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Moves roboto one unit forward to facing direction if roboto is placed
|
35
|
+
# and it's safe
|
36
|
+
# otherwise says advise message
|
37
|
+
def move
|
38
|
+
if_placed do
|
39
|
+
x, y = location.forward_prediction
|
40
|
+
safe(x, y) do
|
41
|
+
location.forward
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Says current location if roboto is placed
|
47
|
+
# otherwise says advise message
|
48
|
+
def report
|
49
|
+
if_placed do
|
50
|
+
puts speak(location.to_s)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Rotates roboto 90 degrees to the left if roboto is placed
|
55
|
+
# otherwise says advise message
|
56
|
+
def left
|
57
|
+
if_placed do
|
58
|
+
location.rotate_ccw
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Rotates roboto 90 degrees to the right if roboto is placed
|
63
|
+
# otherwise says advise message
|
64
|
+
def right
|
65
|
+
if_placed do
|
66
|
+
location.rotate_cw
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns true if location is nil
|
71
|
+
def placed?
|
72
|
+
!location.nil?
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def safe(x, y)
|
78
|
+
to_safe_location?(x, y) ? yield : speak("No can do, sir! If #{name} move #{name} fall.")
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_safe_location?(new_x, new_y)
|
82
|
+
new_x.between?(table_edges[:x][:min], table_edges[:x][:max]) && new_y.between?(table_edges[:y][:min], table_edges[:y][:max])
|
83
|
+
end
|
84
|
+
|
85
|
+
def if_placed
|
86
|
+
placed? ? yield : speak("#{name} not placed yet, sir!")
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ToyRobot
|
2
|
+
class Roboto
|
3
|
+
class Brain
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegator :@speech, :speak
|
8
|
+
|
9
|
+
attr_accessor :motor_cortex, :speech
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@motor_cortex = MotorCortex.new
|
13
|
+
@speech = Speech.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def understand_order(order)
|
17
|
+
order = sanitized_order(order).split(' ')
|
18
|
+
method = order.shift.downcase.to_sym
|
19
|
+
if order.any?
|
20
|
+
arguments = sanitized_args(order.shift)
|
21
|
+
end
|
22
|
+
{ command: method, args: arguments }
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def sanitized_order(order)
|
28
|
+
order.strip.squeeze(' ')
|
29
|
+
end
|
30
|
+
|
31
|
+
def sanitized_args(args)
|
32
|
+
args = args.gsub(/ /, '').split(',')
|
33
|
+
{ x: args[0].to_i, y: args[1].to_i, f: args[2].downcase.to_sym }
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module ToyRobot
|
2
|
+
class Roboto
|
3
|
+
class MotorCortex
|
4
|
+
|
5
|
+
attr_accessor :x, :y, :f
|
6
|
+
|
7
|
+
CARDINAL_DIRECTIONS = [:north, :east, :south, :west]
|
8
|
+
MOVE = { north: { x: 0, y: 1 },
|
9
|
+
east: { x: 1, y: 0 },
|
10
|
+
south: { x: 0, y: -1 },
|
11
|
+
west: { x: -1, y: 0 } }
|
12
|
+
|
13
|
+
def initialize(x: nil, y: nil, f: nil)
|
14
|
+
reset(x: x, y: y, f: f)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Sets X, Y and forwarding direction
|
18
|
+
def reset(x:, y:, f:)
|
19
|
+
raise "#{f} is not a recognized direction" unless f.nil? || CARDINAL_DIRECTIONS.include?(f)
|
20
|
+
@x = x
|
21
|
+
@y = y
|
22
|
+
@f = f
|
23
|
+
end
|
24
|
+
|
25
|
+
# Rotates -90 degrees
|
26
|
+
def rotate_ccw
|
27
|
+
@f = rotation(-1)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Rotates 90 degrees
|
31
|
+
def rotate_cw
|
32
|
+
@f = rotation(1) || CARDINAL_DIRECTIONS[0]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Increaseas one unit to forwarding direction
|
36
|
+
def forward
|
37
|
+
new_x, new_y = forward_prediction
|
38
|
+
reset(x: new_x, y: new_y, f: @f)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Calculates new position to move forward
|
42
|
+
def forward_prediction
|
43
|
+
return @x + MOVE[@f][:x], @y + MOVE[@f][:y]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns a formated string with axis and forwarding direction
|
47
|
+
def to_s
|
48
|
+
"X: #{value_or_not_available(@x)}, Y: #{value_or_not_available(@y)}, F: #{value_or_not_available(@f)}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns true X or Y or F is nil
|
52
|
+
def nil?
|
53
|
+
@x.nil? || @y.nil? || @f.nil?
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def value_or_not_available(value)
|
59
|
+
value.nil? ? 'N/A' : value
|
60
|
+
end
|
61
|
+
|
62
|
+
def rotation(step)
|
63
|
+
CARDINAL_DIRECTIONS[CARDINAL_DIRECTIONS.index(@f) + step]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|