karel 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,259 @@
1
+ = Karel the Robot in Ruby
2
+
3
+ Author:: Dave Copeland (mailto:davetron5000 at g mail dot com)
4
+ Copyright:: Copyright (c) 2010 by Dave Copeland
5
+ License:: Distributes under the Apache License, see LICENSE.txt in the source distro
6
+
7
+ == What is this?
8
+
9
+ This is a ruby implementation of Karel The Robot[http://www.cs.mtsu.edu/~untch/karel/], a
10
+ programming language designed for extreme beginners. It concerns controlling
11
+ a robot, named Karel, in a grid-based world comprised of walls and beepers. Karel can pick up and put down beepers, move
12
+ forward, and turn left. Karel can also detect things about his environment.
13
+
14
+ == Install
15
+
16
+ For the time being, there's no gem so just clone the repo.
17
+
18
+ git clone https://davetron5000@github.com/davetron5000/rkarel.git
19
+ cd rkarel
20
+ bin/karel init example.karel
21
+ bin/karel run example.karel
22
+ bin/karel run -g example.karel
23
+
24
+ == Usage
25
+
26
+ While the karel language doesn't have any concept of the world, you must still define one in which your program will run. This is done
27
+ at the top of the program via:
28
+
29
+ WORLD <<END
30
+ W B
31
+ W
32
+ WWW
33
+ B
34
+ K
35
+ END
36
+
37
+ This defines Karel's world to be 5x9 with walls represented by W's, a beeper represented by B's, and Karel's
38
+ initial location represented as a "K". Karel is assumed to be facing north with no beepers at the start of all programs.
39
+
40
+ You then can define subroutines as such:
41
+
42
+ DEFINE('TURNRIGHT') {
43
+ TURNLEFT()
44
+ TURNLEFT()
45
+ TURNLEFT()
46
+ }
47
+
48
+ You can then call this subroutine as you would any other
49
+
50
+ MOVE()
51
+ TURNRIGHT()
52
+ MOVE()
53
+ MOVE()
54
+
55
+ Once you've defined your subroutines, you can begin writing your program:
56
+
57
+ MOVE()
58
+ TURNLEFT()
59
+ ITERATE(3.TIMES) {
60
+ MOVE
61
+ }
62
+ IF(front_clear) {
63
+ MOVE()
64
+ }
65
+ ELSE {
66
+ PUTBEEPER()
67
+ }
68
+ TURNLEFT()
69
+ WHILE(not_on_beeper) {
70
+ MOVE
71
+ }
72
+ PICKBEEPER()
73
+
74
+ === Commands
75
+
76
+ [+MOVE+] Move Karel forward one square. If Karel can't, he explodes and the program aborts
77
+ [+TURNLEFT+] Rotate Karel, in place, to the left
78
+ [+PICKBEEPER+] Pick up the beeper at Karel's position. If there is no beeper, Karel explodes and the problem aborts
79
+ [+PUTBEEPER+] Put down a beeper at Karel's position. If there is a beeper or if Karel has no beepers, Karel explodes and the problem aborts
80
+
81
+ === Control Flow
82
+
83
+ [+IF+] takes a condition and a curly-braced block to perform if the condition holds
84
+ [+ELSE+] when after an IF, executes the curly-braced block if the condition didn't hold
85
+ [<code>ITERATE(<i>N</i>.TIMES)</code>] perform something a constant number of times
86
+ [+WHILE+] takes a condition and a curly-braced block and repeatedly performs the block's statements until the condition holds
87
+
88
+ ==== Conditions
89
+
90
+ [+on_beeper+] true if Karel is on the same square as a beeper
91
+ [+not_on_beeper+] true if Karel is not on the same square as a beeper
92
+ [+front_clear+] true if the square in front of Karel is clear
93
+ [+front_not_clear+] true if the square in front of Karel is not clear
94
+ [+left_clear+] true if the square to the left of Karel is clear
95
+ [+left_not_clear+] true if the square to the left of Karel is not clear
96
+ [+right_clear+] true if the square to the left of Karel is clear
97
+ [+right_not_clear+] true if the square to the left of Karel is not clear
98
+ [+facing_north+] true if Karel is facing north
99
+ [+not_facing_north+] true if Karel is not facing north
100
+ [+facing_south+] true if Karel is facing south
101
+ [+not_facing_south+] true if Karel is not facing south
102
+ [+facing_east+] true if Karel is facing east
103
+ [+not_facing_east+] true if Karel is not facing east
104
+ [+facing_west+] true if Karel is facing west
105
+ [+not_facing_west+] true if Karel is not facing west
106
+
107
+ === Subroutines
108
+
109
+ You can define your own subroutines via the +DEFINE+ directive:
110
+
111
+ DEFINE('RUN_TO_WALL') {
112
+ WHILE(front_clear) {
113
+ MOVE()
114
+ }
115
+ }
116
+ RUN_TO_WALL()
117
+
118
+
119
+ === Whitespace and comments
120
+
121
+ Whitespace is not significant and is ignored. Comments are lines starting with optional whitespace then followed by a +#+. The remainder
122
+ of the line is ignored.
123
+
124
+ == Example
125
+
126
+ Here's an example program that has Karel grabbing both beepers and stopping on the last one (the one in the upper-left corner):
127
+
128
+ WORLD <<END
129
+ B WW B
130
+ W
131
+ W
132
+
133
+ WWW K
134
+ END
135
+
136
+ WHILE(front_clear) {
137
+ MOVE()
138
+ }
139
+ TURNLEFT()
140
+ MOVE()
141
+ PICKBEEPER()
142
+ TURNLEFT()
143
+ # Karel is now facing south
144
+ WHILE(front_clear) {
145
+ MOVE()
146
+ }
147
+ TURNLEFT()
148
+ TURNLEFT()
149
+ MOVE()
150
+ TURNLEFT()
151
+ WHILE(front_clear) {
152
+ MOVE()
153
+ }
154
+ TURNLEFT()
155
+ TURNLEFT()
156
+ TURNLEFT()
157
+ WHILE(not_on_beeper) {
158
+ MOVE()
159
+ }
160
+ PICKBEEPER()
161
+
162
+ We can reduce the line count and make it more readable with some subroutines:
163
+
164
+ DEFINE('TURNRIGHT') {
165
+ ITERATE(3.TIMES) {
166
+ TURNLEFT()
167
+ }
168
+ }
169
+ DEFINE('TURNAROUND') {
170
+ TURNLEFT()
171
+ TURNLEFT()
172
+ }
173
+ DEFINE('RUN') {
174
+ WHILE(front_clear) {
175
+ MOVE()
176
+ }
177
+ }
178
+ DEFINE('BACKUP') {
179
+ TURNAROUND()
180
+ MOVE()
181
+ }
182
+
183
+ RUN()
184
+ TURNLEFT()
185
+ MOVE()
186
+ PICKBEEPER()
187
+ TURNLEFT()
188
+ RUN()
189
+ BACKUP()
190
+ TURNLEFT()
191
+ RUN()
192
+ TURNRIGHT()
193
+ WHILE(not_on_beeper) {
194
+ MOVE()
195
+ }
196
+ PICKBEEPER()
197
+
198
+ == Non-DSL Mode
199
+
200
+ If you wish to use this code as an engine to embed inside another application, the DSL mode is quite inconvienient. In this case, you can create an object and call the DSL methods on it.
201
+
202
+ engine = Engine.new
203
+ engine.WORLD <<END
204
+ B WW B
205
+ WW W
206
+ W WW
207
+ K W
208
+ END
209
+
210
+ Need to think this out more; what is possible and what isn't?
211
+
212
+ == Vim Syntax File
213
+
214
+ In <code>contrib</code> is a vim syntax file for karel source as described here.
215
+
216
+ == Command Line Interface
217
+
218
+ karel command_name [command-specific options] [--] arguments...
219
+
220
+ * Use the command +help+ to get a summary of commands
221
+ * Use the command <tt>help command_name</tt> to get a help for +command_name+
222
+ * Use <tt>--</tt> to stop command line argument processing; useful if your arguments have dashes in them
223
+
224
+ == Commands
225
+ [<tt>check</tt>] Performs a basic syntax check
226
+ [<tt>help</tt>] Shows list of commands or help for one command
227
+ [<tt>init</tt>] Creates a new example Karel program
228
+ [<tt>run</tt>] Executes a Karel program
229
+
230
+ === <tt>check source_file</tt>
231
+
232
+ Performs a basic syntax check
233
+
234
+ === <tt>help [command]</tt>
235
+
236
+ Shows list of commands or help for one command
237
+
238
+ === <tt>init source_file</tt>
239
+
240
+ Creates a new example Karel program
241
+
242
+
243
+
244
+ ==== Options
245
+ These options are specified *after* the command.
246
+
247
+ [<tt>-f, --force</tt>] Overwite files
248
+ === <tt>run source_file</tt>
249
+
250
+ Executes a Karel program
251
+
252
+
253
+
254
+ ==== Options
255
+ These options are specified *after* the command.
256
+
257
+ [<tt>-g, --debug</tt>] Show the world after each command is executed
258
+ [<tt>-s</tt>] Run silently; do not show the world before or after
259
+ [<tt>-x arg</tt>] Number of "ticks" to allow before assuming we are in an infinite loop <i>( default: <tt>1000</tt>)</i>
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/ruby
2
+ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+ $: << File.expand_path(File.dirname(__FILE__) + '/../ext')
4
+ require 'rubygems'
5
+ require 'gli'
6
+ require 'karel'
7
+ require 'karel/syntax_checker'
8
+
9
+ include GLI
10
+
11
+ desc 'Executes a Karel program'
12
+ arg_name 'source_file'
13
+ command :run do |c|
14
+
15
+ c.desc 'Run silently; do not show the world before or after'
16
+ c.long_desc 'This will execute the karel source file, exiting with 0 if the program ran without incident. An exit code of -1 means that Karel exploded, and -2 means that a potential infinite loop was encountered'
17
+ c.switch :s
18
+
19
+ c.desc 'Number of "ticks" to allow before assuming we are in an infinite loop'
20
+ c.long_desc 'To keep this implementation simple, we will count the number of times we execute a Karel command. If this number exceeds the number here, we will assume a non-terminating program and exit'
21
+ c.default_value '1000'
22
+ c.flag [:x]
23
+
24
+ c.desc 'Show the world after each command is executed'
25
+ c.switch [:g,:debug]
26
+
27
+ c.action do |global_options,options,args|
28
+ raise "You must provide a karel source file" if args.empty?
29
+ include Karel
30
+ code = File.open(args[0]).readlines.join("")
31
+ checker = Karel::SyntaxChecker.new
32
+ unless checker.valid?(code)
33
+ checker.errors.keys.sort.each do |line_number|
34
+ $stderr.puts "#{line_number}: #{checker.errors[line_number]}"
35
+ end
36
+ raise "There were syntax errors"
37
+ end
38
+ DEBUG() if options[:g]
39
+ SILENT() if options[:s]
40
+ eval(code)
41
+ unless options[:s]
42
+ puts "Final State"
43
+ puts THE_WORLD.to_s
44
+ end
45
+ end
46
+ end
47
+
48
+ desc 'Performs a basic syntax check'
49
+ arg_name 'source_file'
50
+ command :check do |c|
51
+ c.action do |global_options,options,args|
52
+ raise "You must provide a karel source file" if args.empty?
53
+ checker = Karel::SyntaxChecker.new
54
+ code = File.open(args[0]).readlines.join("")
55
+ unless checker.valid?(code)
56
+ checker.errors.keys.sort.each do |line_number|
57
+ $stderr.puts "#{line_number}: #{checker.errors[line_number]}"
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ desc 'Creates a new example Karel program'
64
+ arg_name 'source_file'
65
+ command :init do |c|
66
+ c.desc 'Overwite files'
67
+ c.switch [:f,:force]
68
+
69
+ c.action do |global_options,options,args|
70
+ raise "You must provide a karel source file" if args.empty?
71
+ not_overwritten = []
72
+ args.each do |filename|
73
+ if File.exists?(filename) && !options[:f]
74
+ not_overwritten << filename
75
+ else
76
+ File.open(filename,'w') do |file|
77
+ file.puts <<EOF
78
+ # A 10x10 world
79
+ WORLD <<END
80
+ B WW WB
81
+ W
82
+ W
83
+ W
84
+ WB
85
+ WWWW
86
+ WW
87
+ W
88
+ B
89
+ K
90
+ END
91
+
92
+ # Create a subroutine
93
+ DEFINE('MOVE_TWICE') {
94
+ MOVE()
95
+ MOVE()
96
+ }
97
+
98
+ MOVE()
99
+ TURNLEFT()
100
+ TURNLEFT()
101
+ TURNLEFT()
102
+ MOVE()
103
+ PICKBEEPER()
104
+ MOVE_TWICE()
105
+ PUTBEEPER()
106
+ WHILE(front_clear) {
107
+ MOVE()
108
+ }
109
+ EOF
110
+ end
111
+ end
112
+ end
113
+ not_overwritten.each do |filename|
114
+ puts "Not overwriting #{filename}; file exists"
115
+ end
116
+ end
117
+ end
118
+
119
+ pre do |global,command,options,args|
120
+ true
121
+ end
122
+
123
+ post do |global,command,options,args|
124
+ end
125
+
126
+ on_error do |exception|
127
+ case exception
128
+ when Karel::Explosion
129
+ $stderr.puts "Karel Exploded"
130
+ $stderr.puts THE_WORLD.to_s
131
+ false
132
+ else
133
+ # puts exception.backtrace
134
+ true
135
+ end
136
+ end
137
+
138
+ GLI.run(ARGV)
@@ -0,0 +1,5 @@
1
+ class Fixnum
2
+ def TIMES
3
+ self
4
+ end
5
+ end
@@ -0,0 +1,44 @@
1
+ = <tt>karel</tt>
2
+
3
+ karel command_name [command-specific options] [--] arguments...
4
+
5
+ * Use the command +help+ to get a summary of commands
6
+ * Use the command <tt>help command_name</tt> to get a help for +command_name+
7
+ * Use <tt>--</tt> to stop command line argument processing; useful if your arguments have dashes in them
8
+
9
+ == Commands
10
+ [<tt>check</tt>] Performs a basic syntax check
11
+ [<tt>help</tt>] Shows list of commands or help for one command
12
+ [<tt>init</tt>] Creates a new example Karel program
13
+ [<tt>run</tt>] Executes a Karel program
14
+
15
+ === <tt>check source_file</tt>
16
+
17
+ Performs a basic syntax check
18
+
19
+ === <tt>help [command]</tt>
20
+
21
+ Shows list of commands or help for one command
22
+
23
+ === <tt>init source_file</tt>
24
+
25
+ Creates a new example Karel program
26
+
27
+
28
+
29
+ ==== Options
30
+ These options are specified *after* the command.
31
+
32
+ [<tt>-f, --force</tt>] Overwite files
33
+ === <tt>run source_file</tt>
34
+
35
+ Executes a Karel program
36
+
37
+
38
+
39
+ ==== Options
40
+ These options are specified *after* the command.
41
+
42
+ [<tt>-g, --debug</tt>] Show the world after each command is executed
43
+ [<tt>-s</tt>] Run silently; do not show the world before or after
44
+ [<tt>-x arg</tt>] Number of "ticks" to allow before assuming we are in an infinite loop <i>( default: <tt>1000</tt>)</i>
@@ -0,0 +1,12 @@
1
+ require 'karel/commands'
2
+ require 'karel/exceptions'
3
+ require 'karel/karel'
4
+ require 'karel/square'
5
+ require 'karel/world'
6
+
7
+ module Karel
8
+ include Commands
9
+
10
+ THE_WORLD = World.new
11
+ KAREL = Karel.new
12
+ end
@@ -0,0 +1,152 @@
1
+ require 'fixnum_TIMES.rb'
2
+ module Karel
3
+ module Commands
4
+ INFINITE_LOOP_NUM_STEPS = 1000
5
+ # Create the world with the given initialization string
6
+ def WORLD(string)
7
+ @subroutines ||= {}
8
+ world_instance.create_from_string(string,karel_instance)
9
+ unless @silent
10
+ puts "Initial State"
11
+ puts THE_WORLD.to_s
12
+ end
13
+ end
14
+
15
+ def karel_instance; KAREL; end
16
+ def world_instance; THE_WORLD; end
17
+
18
+ def DEBUG
19
+ @debug = true
20
+ @silent = false
21
+ end
22
+
23
+ def SILENT
24
+ @silent = true
25
+ @debug = false
26
+ end
27
+
28
+ # Moves Karel forward one square
29
+ def MOVE
30
+ x,y = Karel.coordinates_after_move_from(karel_instance.direction,*world_instance.karel)
31
+ world_instance.karel=[x,y]
32
+ debug_command('MOVE')
33
+ end
34
+
35
+ # Turns karel to the left in place
36
+ def TURNLEFT
37
+ karel_instance.turnleft
38
+ debug_command('TURNLEFT')
39
+ end
40
+
41
+ def PICKBEEPER
42
+ karel = world_instance.karel
43
+ begin
44
+ world_instance.remove_beeper(*karel)
45
+ karel_instance.put_beeper_in_bag
46
+ rescue NoBeeper => x
47
+ raise Explosion
48
+ end
49
+ debug_command('PICKBEEPER')
50
+ end
51
+
52
+ def PUTBEEPER
53
+ karel = world_instance.karel
54
+ karel_instance.remove_beeper_from_bag
55
+ world_instance.add_beeper(*karel)
56
+ debug_command('PUTBEEPER')
57
+ end
58
+
59
+ def DEFINE(name,&block)
60
+ raise BadSubroutine unless subroutine_name_ok?(name)
61
+ @subroutines ||= {}
62
+ @subroutines[name.to_sym] = block;
63
+ end
64
+
65
+ # Handles calling subroutines defined
66
+ def method_missing(sym,*args)
67
+ if !args || args.size == 0
68
+ if @subroutines[sym]
69
+ puts "CALLING #{sym}" if @debug
70
+ @subroutines[sym].call
71
+ else
72
+ super.method_missing(sym,args)
73
+ end
74
+ else
75
+ super.method_missing(sym,args)
76
+ end
77
+ end
78
+
79
+ def ITERATE(num,&block)
80
+ num.times { block.call }
81
+ end
82
+
83
+ def WHILE(condition,&block)
84
+ steps = 0
85
+ while (condition_met? condition)
86
+ block.call
87
+ steps += 1
88
+ raise PossibleInfiniteLoop if steps > INFINITE_LOOP_NUM_STEPS
89
+ end
90
+ end
91
+
92
+ @last_condition = nil
93
+ def IF(condition,&block)
94
+ if condition_met? condition
95
+ block.call
96
+ end
97
+ @last_condition = condition
98
+ end
99
+
100
+ def ELSE(&block)
101
+ raise "No IF with this ELSE!" if @last_condition.nil?
102
+ condition = @last_condition
103
+ @last_condition = nil
104
+ unless (condition_met? condition)
105
+ block.call
106
+ end
107
+ end
108
+
109
+ def condition_met?(condition)
110
+ raise "No such condition #{condition}" unless CONDITIONS[condition]
111
+ CONDITIONS[condition].call(world_instance,karel_instance,*world_instance.karel)
112
+ end
113
+
114
+ def subroutine_name_ok?(name)
115
+ name =~ /^[A-Z][A-Z_0-9]*[A-Z]$/
116
+ end
117
+
118
+ def debug_command(command)
119
+ if @debug
120
+ puts "#{karel_instance.num_beepers} beepers> #{command}"
121
+ puts world_instance.to_s
122
+ end
123
+ end
124
+
125
+ CONDITIONS = {
126
+ :on_beeper => lambda{ |world,karel,row,column| world.clear?(row,column) && world[row,column].beeper? },
127
+ :front_clear => lambda{ |world,karel,row,column| world.clear?(*Karel.coordinates_after_move_from(karel.direction,row,column)) },
128
+ :left_clear => lambda{ |world,karel,row,column| world.clear?(*Karel.coordinates_after_move_from(Karel.left_of(karel.direction),row,column)) },
129
+ :right_clear => lambda{ |world,karel,row,column| world.clear?(*Karel.coordinates_after_move_from(Karel.right_of(karel.direction),row,column)) },
130
+ :facing_north => lambda{ |world,karel,row,column| karel.direction == :north },
131
+ :facing_south => lambda{ |world,karel,row,column| karel.direction == :south },
132
+ :facing_east => lambda{ |world,karel,row,column| karel.direction == :east },
133
+ :facing_west => lambda{ |world,karel,row,column| karel.direction == :west },
134
+
135
+ :not_on_beeper => lambda{ |world,karel,row,column| !CONDITIONS[:on_beeper].call(world,karel,row,column) },
136
+ :front_not_clear => lambda{ |world,karel,row,column| !CONDITIONS[:front_clear].call(world,karel,row,column) },
137
+ :left_not_clear => lambda{ |world,karel,row,column| !CONDITIONS[:left_clear].call(world,karel,row,column) },
138
+ :right_not_clear => lambda{ |world,karel,row,column| !CONDITIONS[:right_clear].call(world,karel,row,column) },
139
+ :not_facing_north => lambda{ |world,karel,row,column| !CONDITIONS[:facing_north].call(world,karel,row,column) },
140
+ :not_facing_south => lambda{ |world,karel,row,column| !CONDITIONS[:facing_south].call(world,karel,row,column) },
141
+ :not_facing_east => lambda{ |world,karel,row,column| !CONDITIONS[:facing_east].call(world,karel,row,column) },
142
+ :not_facing_west => lambda{ |world,karel,row,column| !CONDITIONS[:facing_west].call(world,karel,row,column) },
143
+ }
144
+
145
+ CONDITIONS.each_key do |condition|
146
+ define_method condition do
147
+ condition
148
+ end
149
+ end
150
+ end
151
+
152
+ end
@@ -0,0 +1,15 @@
1
+ module Karel
2
+ # Thrown when Karel does something that causes him to explode
3
+ class Explosion < Exception; end
4
+ # Thrown if the world was not created in a valid state
5
+ class InvalidWorld < Exception; end
6
+ # Thrown when a square is occupied when an operation requires it not to be
7
+ class SquareOccupied < Exception; end
8
+ # Thrown when there is no beeper on a square when an operation require there to be
9
+ class NoBeeper < Exception; end
10
+ # Thrown when a subroutine is defined that has a bad or not-allowed name
11
+ class BadSubroutine < Exception; end
12
+ # Thrown when a while loop has gone on too long and we think there might
13
+ # be an infinite loop
14
+ class PossibleInfiniteLoop < Exception; end
15
+ end
@@ -0,0 +1,67 @@
1
+ module Karel
2
+ # The robot itself. Knows only its internal state, not much about where it is
3
+ class Karel
4
+ attr_reader :direction
5
+ attr_reader :num_beepers
6
+
7
+ def initialize
8
+ reset
9
+ end
10
+
11
+ def reset
12
+ @direction = :north
13
+ @num_beepers = 0
14
+ end
15
+
16
+ def put_beeper_in_bag
17
+ @num_beepers += 1
18
+ end
19
+
20
+ def remove_beeper_from_bag
21
+ raise Explosion if @num_beepers <= 0
22
+ @num_beepers -= 1
23
+ end
24
+
25
+ def turnleft
26
+ @direction = Karel.left_of(@direction)
27
+ end
28
+
29
+ def to_s
30
+ STRINGS[direction]
31
+ end
32
+
33
+ def self.left_of(direction)
34
+ new_index = DIRECTIONS.index(direction) + 1
35
+ new_index = 0 if new_index >= DIRECTIONS.size
36
+ DIRECTIONS[new_index]
37
+ end
38
+
39
+ def self.right_of(direction)
40
+ new_index = DIRECTIONS.index(direction) - 1
41
+ new_index = DIRECTIONS.size - 1 if new_index < 0
42
+ DIRECTIONS[new_index]
43
+ end
44
+
45
+ def self.coordinates_after_move_from(direction,x,y)
46
+ if direction == :north
47
+ [x-1,y]
48
+ elsif direction == :south
49
+ [x+1,y]
50
+ elsif direction == :west
51
+ [x,y-1]
52
+ elsif direction == :east
53
+ [x,y+1]
54
+ end
55
+ end
56
+
57
+ DIRECTIONS = [:north,:west,:south,:east]
58
+
59
+ private
60
+ STRINGS = {
61
+ :north => '^',
62
+ :west => '<',
63
+ :south => 'v',
64
+ :east => '>',
65
+ }
66
+ end
67
+ end
@@ -0,0 +1,42 @@
1
+ module Karel
2
+ # A square on the board
3
+ class Square; end
4
+
5
+ # A square that can hold a beeper, or Karel.
6
+ class FreeSquare < Square
7
+ def initialize(beeper=false)
8
+ @beeper = beeper
9
+ end
10
+
11
+ def wall?; false; end
12
+
13
+ def beeper?; @beeper; end
14
+
15
+ def pick_beeper
16
+ raise NoBeeper unless beeper?
17
+ @beeper = false
18
+ end
19
+
20
+ def put_beeper
21
+ raise SquareOccupied if beeper?
22
+ @beeper = true
23
+ end
24
+
25
+ def to_s
26
+ if beeper?
27
+ "B"
28
+ else
29
+ " "
30
+ end
31
+ end
32
+ end
33
+
34
+ # A wall square
35
+ class Wall < Square
36
+ def wall?; true; end
37
+ def beeper?; false; end
38
+ def to_s; "W"; end
39
+ def put_beeper; raise SquareOccupied; end
40
+ def pick_beeper; raise NoBeeper; end
41
+ end
42
+ end
@@ -0,0 +1,86 @@
1
+ require 'karel/commands'
2
+
3
+ module Karel
4
+ # A very basic and very hacky syntax checker. This won't ensure the program runs
5
+ # and is pretty lame overall, but it more or less gets the job done
6
+ class SyntaxChecker
7
+ def initialize
8
+ @builtins = []
9
+ Commands.public_instance_methods.each do |method|
10
+ @builtins << method if method =~ /^[A-Z_]*$/
11
+ end
12
+ @control = [ 'WHILE', 'IF', 'ELSE', 'ITERATE' ]
13
+ end
14
+
15
+ def valid?(code)
16
+ line_number = 1
17
+ @errors = {}
18
+ in_world = false
19
+ defines = []
20
+ code.split(/\n/).each do |line|
21
+ line_number += 1
22
+ trimmed = line.strip.gsub(/\(\)/,'').strip
23
+ if in_world
24
+ if trimmed =~ /^END\s*$/
25
+ in_world = false
26
+ else
27
+ if trimmed =~ /^[ KWB]*$/
28
+ # ok
29
+ else
30
+ @errors[line_number] = "Bad world definition: #{trimmed}"
31
+ end
32
+ end
33
+ elsif (trimmed =~ /^WORLD\s+<<END$/)
34
+ in_world = true
35
+ else
36
+ next if trimmed =~/^\s*$/
37
+ next if trimmed =~/^\s*#.*$/
38
+ if trimmed =~ /^DEFINE\s*\(\s*["'](.[^"']*)["']\s*\)\s*\{\s*$/
39
+ define = $1
40
+ if define =~ /^[A-Z_]*$/
41
+ defines << define
42
+ else
43
+ @errors[line_number] = "Bad DEFINE symbol #{define}"
44
+ end
45
+ else
46
+ to_the_bone = trimmed.gsub(/[^A-Z_].*$/,'')
47
+ if @control.include? to_the_bone
48
+ if trimmed =~ /^#{to_the_bone}\s*\((.*)\)\s*\{\s*$/
49
+ condition = $1
50
+ if to_the_bone == 'ITERATE'
51
+ if condition =~ /^\s*[0-9]\.TIMES\s*$/
52
+ # ok
53
+ else
54
+ @errors[line_number] = "Bad #{to_the_bone} condition: #{condition}"
55
+ end
56
+ else
57
+ if (trimmed =~ /^ELSE/)
58
+ @errors[line_number] = "Bad #{to_the_bone} statement: #{trimmed}"
59
+ end
60
+ if condition =~ /^[a-z_]+$/
61
+ # ok
62
+ else
63
+ @errors[line_number] = "Bad #{to_the_bone} condition: #{condition}"
64
+ end
65
+ end
66
+ else
67
+ if trimmed =~ /^ELSE\s*\{\s*$/
68
+ # ok
69
+ else
70
+ @errors[line_number] = "Bad #{to_the_bone} statement: #{trimmed}"
71
+ end
72
+ end
73
+ elsif trimmed =~ /^\s*\}\s*$/
74
+ # ignore
75
+ else
76
+ @errors[line_number] = "Unknown symbol #{trimmed}" unless (@builtins.include? trimmed) || (defines.include? trimmed)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ @errors.empty?
82
+ end
83
+
84
+ def errors; @errors; end
85
+ end
86
+ end
@@ -0,0 +1,140 @@
1
+ module Karel
2
+ # The world in which Karel operates.
3
+ class World
4
+
5
+ attr_reader :karel
6
+ attr_reader :width
7
+ attr_reader :height
8
+
9
+ # Creates the world from a string that is a multi-line representation of the world.
10
+ # This isn't the constructor because our DSL requires uppercase and we don't want
11
+ # warnings by creating the world twice (once when declaring it as a constant in the Karel
12
+ # module and once inside the module method WORLD).
13
+ def create_from_string(definition,karel)
14
+ @karel = nil
15
+ @karel_instance = karel
16
+ @karel_instance.reset
17
+ @world = []
18
+ @width = 0
19
+ row = 0
20
+ column = 0
21
+ rows = definition.split(/\n/)
22
+ @height = rows.size
23
+ rows.each do |row_data|
24
+ @width = row_data.length if row_data.length > @width
25
+ row_data.split(//).each do |square|
26
+ @karel = [row,column] if square == 'K'
27
+ @world[row] ||= []
28
+ @world[row][column] = square_for(square)
29
+ column += 1
30
+ end
31
+ column = 0
32
+ row += 1
33
+ end
34
+ raise InvalidWorld,"There is no Karel in this World" unless @karel
35
+ fill_in_empty_spaces
36
+ end
37
+
38
+ # Removes a beeper from the world at the given row/column
39
+ #
40
+ # raises a NoBeeper exception if there is no beeper at those coordinates.
41
+ def remove_beeper(row,column)
42
+ self[row,column].pick_beeper
43
+ end
44
+
45
+ # Adds a beeper tot he world at the given row/column
46
+ #
47
+ # raises a SquareOccupied exception if there is a beeper or wall at those coordinates.
48
+ def add_beeper(row,column)
49
+ self[row,column].put_beeper
50
+ end
51
+
52
+ # Set the location of Karel
53
+ #
54
+ # location - an array of size 2, with the row/column where karel should be
55
+ #
56
+ # Raises an Explosion if you try to put Karel where there is a wall
57
+ def karel=(location)
58
+ raise Explosion,"#{location.inspect} is out of bounds" unless in_bounds?(*location)
59
+ raise Explosion if self[*location].wall?
60
+ @karel = location
61
+ end
62
+
63
+ # True if the square could be moved into by Karel
64
+ def clear?(row,column)
65
+ if in_bounds?(row,column)
66
+ !self[row,column].wall?
67
+ else
68
+ false
69
+ end
70
+ end
71
+
72
+ # Provides access to the square at the given row/column
73
+ #
74
+ # Returns a Square instance, on which you can call handy methods like beeper? and wall?
75
+ def [](row,column)
76
+ @world[row][column]
77
+ end
78
+
79
+ # Returns the string representation, which looks like the
80
+ # the string used to construct this
81
+ def to_s
82
+ string = "+"
83
+ @width.times { string += '-' }
84
+ string += "+\n"
85
+ @height.times do |row|
86
+ string += "|"
87
+ @width.times do |column|
88
+ kr,kc = karel
89
+ if (kr == row) && (kc == column)
90
+ string += @karel_instance.to_s
91
+ else
92
+ string += self[row,column].to_s
93
+ end
94
+ end
95
+ string += "|\n"
96
+ end
97
+ string += "+"
98
+ @width.times { string += '-' }
99
+ string += "+\n"
100
+ string
101
+ end
102
+
103
+ private
104
+
105
+ SQUARE_FACTORIES = {
106
+ 'B' => lambda { FreeSquare.new(true) },
107
+ 'W' => lambda { Wall.new },
108
+ ' ' => lambda { FreeSquare.new },
109
+ 'K' => lambda { FreeSquare.new },
110
+ }
111
+
112
+ # Given a character, returns the square type
113
+ # that should go there
114
+ def square_for(square)
115
+ raise InvalidWorld,"Square type #{square} is not valid" if SQUARE_FACTORIES[square].nil?
116
+ SQUARE_FACTORIES[square].call
117
+ end
118
+
119
+ def in_bounds?(row,column)
120
+ if row < 0 || column < 0
121
+ false
122
+ elsif row >= @height
123
+ false
124
+ elsif column >= @width
125
+ false
126
+ else
127
+ true
128
+ end
129
+ end
130
+
131
+ def fill_in_empty_spaces
132
+ @height.times do |row|
133
+ @world[row] = [] if @world[row].nil?
134
+ @width.times do |column|
135
+ @world[row][column] = FreeSquare.new if @world[row][column].nil?
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: karel
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Dave Copeland
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-26 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: gli
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 17
30
+ segments:
31
+ - 1
32
+ - 1
33
+ - 1
34
+ version: 1.1.1
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ description: |
38
+ This is a ruby implementation of Karel The Robot, a
39
+ programming language designed for extreme beginners. It concerns controlling
40
+ a robot, named Karel, in a grid-based world comprised of walls and beepers. Karel can pick up and put down beepers, move
41
+ forward, and turn left. Karel can also detect things about his environment.
42
+
43
+ email: davetron5000@gmail.com
44
+ executables:
45
+ - karel
46
+ extensions: []
47
+
48
+ extra_rdoc_files:
49
+ - README.rdoc
50
+ - karel.rdoc
51
+ files:
52
+ - bin/karel
53
+ - ext/fixnum_TIMES.rb
54
+ - lib/karel/commands.rb
55
+ - lib/karel/exceptions.rb
56
+ - lib/karel/karel.rb
57
+ - lib/karel/square.rb
58
+ - lib/karel/syntax_checker.rb
59
+ - lib/karel/world.rb
60
+ - lib/karel.rb
61
+ - README.rdoc
62
+ - karel.rdoc
63
+ has_rdoc: true
64
+ homepage: http://www.naildrivin5.com
65
+ licenses: []
66
+
67
+ post_install_message:
68
+ rdoc_options:
69
+ - --title
70
+ - Karel The Robot
71
+ - --main
72
+ - README.rdoc
73
+ - -ri
74
+ require_paths:
75
+ - lib
76
+ - lib
77
+ - ext
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ requirements: []
97
+
98
+ rubyforge_project:
99
+ rubygems_version: 1.3.7
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: An implementation of Karel the Robot as a Ruby DSL
103
+ test_files: []
104
+