Command line - Mars Rover Tech Challenge in common lisp
In this post I’ll discuss the steps to finalise the Mars Rover Tech Challenge.
In particular how to read the configuration settings from a file and then run the code.
As with the last post, please forgive all the spurious (princ ...)
, it’s the
only way I know to get Org mode to export the results for inclusion in this blog
post.
This series of posts
This is the second of three posts on this topic. I’ll update these links as I publish the other posts. I’ve used lisp (the second oldest programming language still in use!) for this, and in the third post I’ll explore why:
- Part 1 : Initial code - running from a REPL
- Part 2 : Read the configuration file and running from the command line
- Part 3 : Benchmarking (AKA why this funny looking language?)
(Updated:
)Get the code
You can download the code from for this and the previous post from my GitHub account:
https://github.com/stewart123579/mars-rover-challenge-lisp
Load the previous code
(load "mars-rover.lisp")
Read in the input file
Here we use the UIOP read-file-lines
function for cross lisp-implementation
and cross machine support.
(defun read-input-file (filename)
;; Throw away blank lines
(let ((setup (loop for line in (uiop:read-file-lines filename)
if (not (equal line ""))
collect (read-from-string (concatenate 'string "(" line ")")))))
(set-plateau-size (car setup))
(loop-over-starting-positions (cdr setup))))
I’ve also used a loop
to
- convert the input into a list whilst
- throwing away blank lines; and
- converting the text of each line into a list
I’ve made an assumption that the input file is of the form:
X_MAX Y_MAX
START_X_1 START_Y_1 ORIENTATION_1
COMMANDS_1
START_X_2 START_Y_2 ORIENTATION_2
COMMANDS_2
...
…but what does this variable setup
actually look like?
(princ (loop for line in (uiop:read-file-lines "mars-rover-test-input.txt")
if (not (equal line ""))
collect (read-from-string (concatenate 'string "(" line ")"))))
((5 5) (1 2 N) (LMLMLMLMM) (3 3 E) (MMRMMRMRRM))
Set the plateau size
This is only really needed as I’m not writing this in the functional style.
The plateau shape is the first element in the setup
list.
(defun set-plateau-size (plateau-shape)
(setf *x_max* (car plateau-shape))
(setf *y_max* (cadr plateau-shape)))
Loop over each of the starting positions
This is probably the least transparent piece of code in this discussion.
Let’s work through the logic: What do we need for running update-position
?
Firstly we need the pos
and orientation
. Looking at the results above, the
list (1 2 N)
has all the information. So the pos
is simply a cons
of the
first (car
) and second (cadr
) elements. The orientation
is the third
(caddr
) element.
Now what about the actual command
? They need to be a list of the individual
letters in the third (cadr
) list. So we need the car
of that list.
Initially I thought something like this would work:
(let ((cmds_list '(LMLMLMLMM)))
(coerce (car cmds_list) 'list))
but it results in the error: The value LMLMLMLMM is not of type SEQUENCE
.
Let’s think about that for a moment. Here LMLMLMLMM
isn’t a string of letters,
it’s simply an atom (think an object/variable in other languages). Lisp doesn’t
see any difference between it and any other atom. To get it treated like a
string of letters we need to convert it to a string (and that’s the SEQUENCE
give-away in the error message). So let’s try:
(let ((cmds_list '(LMLMLMLMM)))
(princ (coerce (string (car cmds_list)) 'list)))
(L M L M L M L M M)
Perfect! So let’s go…
The code to loop over the elements of the configuration file
Here we take the first two elements of the input
list, convert them to pos
,
orientation
and commands
, call update-position
and then call this function
again with the remainder of the input list. Recursion again!
(defun loop-over-starting-positions (input)
"Loop over the starting poition/orientation and commands
INPUT is a list of pairs of lists (x y orientation) (commands)"
(when input
(let* ((start (car input))
(commands (coerce (string (car (cadr input))) 'list))
(pos (cons (car start) (cadr start)))
(orientation (caddr start)))
(update-position pos orientation commands))
(loop-over-starting-positions (cddr input))))
Note - I’ve left this code reading (car (cadr input))
rather than simplifying
to (caadr input)
to make it more consistent with the explanation above. (If
that’s all Greek to you, do some more learning about lisp!)
Run the code over the input file
(read-input-file "mars-rover-test-input.txt")
1 3 N
5 1 E
This is the expected output. Success!
Now let’s make a command line tool
This is a remarkably easy process using Roswell.
I’ll make the assumption that you’ve installed Roswell and have it working.
Scripting with Roswell
The script to run the code we’ve just developed is the following:
#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$@"
|#
(progn ;;init forms
(ros:ensure-asdf)
#+quicklisp(ql:quickload '() :silent t)
)
(defpackage :ros.script.rover.3790038153
(:use :cl))
(in-package :ros.script.rover.3790038153)
(load "mars-rover-v2.lisp")
(defun main (filename &rest argv)
(declare (ignorable argv))
(read-input-file filename))
;;; vim: set ft=lisp lisp:
There are only two lines of interest, and what they do is pretty obvious.
(load "mars-rover-v2.lisp")
;; ...and
(read-input-file filename)
Now we can simply run the code like:
./mars-rover.ros mars-rover-test-input.txt
What’s next?
In the next post I’ll talk about benchmarking this implementation - and why use Lisp.