Question

I'm playing around in CL, making a One-Dimensional version of Battleship before I try to tackle a full Two-Dimensional version, and I've hit a hangup. To check if the boat is there, I've represented it with zeroes, and when a spot is hit, I replace it with an asterisk, so I can check the list with numberp. However, when I run (new-game), it immediately finishes, which tells me that I'm not entering the zeroes correctly so that they are recognized as numbers. What am I doing wrong? I know it must be a rookie mistake.

;;;; Suez-Canal.lisp
;;;;
;;;; A simple, 1-Dimensional version of Battleship
;;;; The computer places a boat randomly, and you must sink it.

(setf *random-state* (make-random-state t))
(defparameter *boat-length* 3)
(defparameter *canal-length* 10)
(defparameter *shots-fired* 0)

(defun new-game ()
  (init-canal *canal-length*)
  (place-boat)
  (game-loop)
  (format t "It took you ~a shots to sink the boat." *shots-fired*))

(defun init-canal (len)
  (defparameter *canal* (make-list len)))

(defun place-boat ()
  (let ((pos (random-spot)))
    (setf (nth pos *canal*) 'O)
    (setf (nth (+ pos 1) *canal*) 'O)
    (setf (nth (+ pos 2) *canal*) 'O)))

(defun random-spot ()
  (let ((x (random 7)))
    x))

(defun game-loop ()
  (loop until (notany #'numberp *canal*)
       do (progn
        (prompt-for-guess)
        (check-guess (read-guess))
        (incf *shots-fired*))))

(defun prompt-for-guess ()
  (format t "~&Enter in a number between 1 and 10 to fire a shot.~&"))

(defun read-guess ()
  (parse-integer (read-line *query-io*) :junk-allowed t))

(defun check-guess (guess)
  (if (and (<= guess 9)
      (>= guess 0))
      (fire-shot guess)
      (progn
        (format t "~&Invalid selection~&")
        (check-guess (read-guess)))))

(defun fire-shot (pos)
  (if (= (nth (- pos 1) *canal*) 0)
      (progn
        (setf (nth (- pos 1) *canal*) #\*)
        (print "Hit!"))
      (print "Miss!")))
Was it helpful?

Solution

You are not entering zeroes at all, but rather the letter 'O'.

Other notes:

Do not use DEFPARAMETER inside DEFUN. Define the variable at top level, and inside the initialization function just SETF it.

Do not use lists for random access. Use arrays.

Numerical comparison operators will signal an error when given a non-numeric value. Use EQL for general comparisons.

OTHER TIPS

Here is a corrected version:

(setf *random-state* (make-random-state t))
(defparameter *boat-length* 3)
(defparameter *canal-length* 10)
(defparameter *shots-fired* 0)

;;; you need to declare *canal* at toplevel.
(defparameter *canal* nil)

(defun new-game ()
  (init-canal *canal-length*)
  (place-boat)
  (game-loop)
  (format t "It took you ~a shots to sink the boat." *shots-fired*))

;;; just set the the variable.
(defun init-canal (length)
  (setq *canal* (make-list length)))

;;; you need to set those positions to 0 and not to O
(defun place-boat ()
  (let ((pos (random-spot)))
    (setf (nth pos       *canal*) 0)
    (setf (nth (+ pos 1) *canal*) 0)
    (setf (nth (+ pos 2) *canal*) 0)))

;;; no need for a LET
(defun random-spot ()
  (random 7))

;;; no need for progn
;;; you could also replace UNTIL NOTANY with WHILE SOME
(defun game-loop ()
  (loop until (notany #'numberp *canal*)
       do
       (prompt-for-guess)
       (check-guess (read-guess))
       (incf *shots-fired*)))

(defun prompt-for-guess ()
  (format t "~&Enter in a number between 1 and 10 to fire a shot.~&"))

(defun read-guess ()
  (parse-integer (read-line *query-io*) :junk-allowed t))

;;; <= can take more than two arguments
;;; typically this recursive version might be replaced with a LOOP
(defun check-guess (guess)
  (if (<= 0 guess 9)
      (fire-shot guess)
    (progn
      (format t "~&Invalid selection~&")
      (check-guess (read-guess)))))

;;; use EQL, = only compares numbers
(defun fire-shot (pos)
  (if (eql (nth (- pos 1) *canal*) 0)
      (progn
        (setf (nth (- pos 1) *canal*) #\*)
        (print "Hit!"))
      (print "Miss!")))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top