davetron5000 / karel
- Source
- Commits
- Network (1)
- Issues (0)
- Downloads (0)
- Wiki (1)
- Graphs
-
Branch:
master
| name | age | message | |
|---|---|---|---|
| |
.vimrc | Mon Sep 07 09:04:29 -0700 2009 | |
| |
README.markdown | Tue Sep 08 10:59:43 -0700 2009 | |
| |
clean.sh | Mon Sep 07 09:04:29 -0700 2009 | |
| |
make.sh | Mon Sep 07 10:11:25 -0700 2009 | |
| |
run.sh | Tue Sep 08 07:23:45 -0700 2009 | |
| |
scaladoc.sh | Tue Sep 08 06:47:24 -0700 2009 | |
| |
src/ | Tue Sep 08 07:23:45 -0700 2009 | |
| |
test.sh | Mon Sep 07 13:54:46 -0700 2009 |
Karel The Robot in Scala
Finding myself with my laptop and without net access for a day I thought it would be fun to implement a DSL for Karel The Robot in Scala.
The idea was to get as close as possible to the Scala code looking like Karel code.
WTF is Karel the Robot?
Karel the Robot is a programming language designed for extreme beginners. The programs you write are control programs for a robot, named Karel, that has the ability to move around a grid picking up and putting down "beepers". The grid contains walls, through which Karel cannot pass.
Further, Karel has a very limited command set. Karel can turn left, move forward, pick up a beeper, and put down a beeper. This leads to some very simple subroutines that a beginning programmer can construct. For example, the "turn right" subroutine can be created by three "turn left" commands.
How to Use It
Karel's programming language doesn't define a way to create the world in which Karel lives, so you have to do that first. After that, you can define subroutines, and then enter your program.
> cat Program.scala
import karel._
object Program extends Application with KarelTheRobot {
BEGIN_PROGRAM(10,10, // a 10x10 World
Map(
(4,5) -> new Wall, // a wall will appear at 4x5
(4,6) -> new Wall,
(4,7) -> new Wall,
(4,8) -> new Wall,
(5,5) -> new Wall,
(6,5) -> new Wall,
(7,5) -> new Wall,
(7,4) -> new Wall,
(7,3) -> new Wall,
(3,8) -> new Beeper, // a beeper will appear at 3x8
(0,0) -> new Beeper
),
{
DEF('TURN_RIGHT,BLOCK(ITERATE(3,TURN_LEFT)))
DEF('TURN_AROUND,BLOCK(TURN_LEFT,TURN_LEFT))
DEF('RUN,BLOCK(
WHILE_DO(FRONT_IS_CLEAR,MOVE),
CALL('TURN_AROUND)
))
},
BEGIN_EXECUTION(
WHILE_DO(NOT_FACING_EAST,TURN_LEFT),
PICK_BEEPER,
CALL('RUN),
PUT_BEEPER
))
}
> ./make.sh
> ./run.sh Program
API
Karel's language is made up of control structures, conditions, and instructions.
Instructions
MOVE- move forward in the direction Karel is facingTURN_LEFT- turn to the left, in placePICK_BEEPER- pick up a beeper located at the current positionPUT_BEEPER- put down a beeper at the current position
Conditions
These are all fairly explanatory
FRONT_IS_CLEARFRONT_IS_BLOCKEDLEFT_IS_CLEARLEFT_IS_BLOCKEDRIGHT_IS_CLEARRIGHT_IS_BLOCKEDBACK_IS_CLEARBACK_IS_BLOCKEDNEXT_TO_BEEPERNOT_NEXT_TO_BEEPERANY_BEEPERS_IN_BAGNO_BEEPERS_IN_BAGFACING_NORTHNOT_FACING_NORTHFACING_SOUTHNOT_FACING_SOUTHFACING_EASTNOT_FACING_EASTFACING_WESTNOT_FACING_WEST
Control Structures
BLOCK- define a block of commandsIF_THEN_ELSE- test for a condition and execute one of two blocks, depending on its outcomeIF- test for a condition and execute a block true (doing nothing if false; Syntactic Sugar!)WHILE_DO- repeatedly perform a block of actions while a condition holdsITERATE- perform a block of actions a fixed number of times
Subroutines
DEF('SUB_NAME,BLOCK)- define a subroutine named'SUB_NAMEdescribed by the commands inBLOCK. This is not executable and must be called before the program is defined.CALL('SUB_NAME)- call the subroutine named'SUB_NAME
Similarities to "real" Karel code
The overall structure, as well as the reserved words, are very similar to Karel code, however Scala didn't make it easy to mimic the "BEGIN/END" block definition syntax (Karel took this from Pascal).
I omitted Karel's two superfluous commands TURN_ON and TURN_OFF, as they seemed kindof pointless.
The one area where Scala made this somewhat tedious is in the conditionals definition. Part of the "simplicity" of Karel is that there are no operators and no functions that take arguments. This makes conditions like BACK_IS_CLEAR and BACK_IS_BLOCKED very clear and easy to come up with (you just choose the condition from a list). While experienced programmers might prefer isClear(BACK) and !isClear(BACK), this would complicate Karel beyond it's purpose as a learning tool.
Given this, we can see that the definition of the conditions is very repetitive. Since I wanted the resulting DSL to be executable Scala code (and not a script that I parsed at runtime), there's really no simple way around it that I could see. Ruby, however, would've made this task much simpler and required much less repeated code. For example, the 8 conditions regarding the direction in which Karel is facing, could've been implemented in one method:
def method_missing(sym,args*)
karel = args[1]
parts = sym.split(/_/,3)
if parts[-2] == "FACING"
direction = parts[-1]
if parts[0] == "NOT"
return karel.direction == direction
else
return karel.direction != direction
end
end
(This could probably be tightened up a bit)
Also, the Scala means of calling defined subroutines that I came up with (CALL('SUB_NAME))) is kinda lame; the subs don't look like real calls (as they would in proper Karel). Ruby, via the ever-useful method_missing, would've made this dead simple.
I plan to work up a Ruby version of Karel as a comparison, but my suspicion is that Ruby would afford a much cleaner DSL in the end.
What's up with ./make.sh dude?
I was offline entirely, so Maven was out (plus I cannot stand Maven). I also didn't have SBT available, so shell scripts to the rescue.
