2

I am trying to represent a 3D Connect 4 board game:

BoardGameConnect4-3D

For example, I have the following list of lists structure:

(
(
(NIL NIL NIL NIL)
(NIL NIL NIL NIL)
(NIL NIL NIL NIL)
(NIL NIL NIL NIL)
)
(1 1 1 1 1 1 1 1 1 1 1 1 10 10 10 10 10 10 10 10 10 10 10 10)
)

Each NIL represents a position in it. For example, if I put two pieces (one black and another white) in the first position it would look like this:

(
(
((B W) NIL NIL NIL)
(NIL NIL NIL NIL)
(NIL NIL NIL NIL)
(NIL NIL NIL NIL)
)
(1 1 1 1 1 1 1 1 1 1 1 1 10 10 10 10 10 10 10 10 10 10 10 10)
)

Which means W would be the one on the bottom. I would also need to compare them to each other in order for the program to say when a winner has been found.

How can add pieces in each position? And how would I compare them since they are lists with the NIL value?

coredump
  • 37,664
  • 5
  • 43
  • 77
Joao Costa
  • 21
  • 2
  • 3
    How did you decide for this particular data-structure? what are 1s and 10s at the bottom? did you consider using a multi-dimensional array instead? – coredump Jul 28 '16 at 16:33
  • 1
    Use the usual list functions of Lisp. What's wrong with that? – Rainer Joswig Jul 28 '16 at 17:05
  • 1
    Are you going to have one state and mutate it or are you doing an algorithm with backtracking? The choice of structure usually have benefits and trade offs so while there are a lot of possible ways to do this there are just a few that are optimal in a certain setting. I imagine getting the slices in a map perhaps and then perhaps the first element should be the bottom or perhaps the elements are lists with placeholders so that the first element is the top even when the top isn't put yet. – Sylwester Jul 28 '16 at 22:12

2 Answers2

1

In principle, if you have a list of lists, of lists, you can use nested nth invocations to get to where you want to check. In this specific case, the linear-in-time nature of nth probably isn't horrible, but I would probably use a 4x4 array of lists, or a 4x4x4 array, although in that case you'd end up needing to track "the next position" yourself, even if that would simplify the "check for win condition" logic.

And an example of how to mutate a list-of-lists-of-lists using push and nth (I've edited the way s is displayed, to make the "arrayness" of it easier to see):

* *s*

((NIL NIL NIL NIL) 
 (NIL NIL NIL NIL) 
 (NIL NIL NIL NIL) 
 (NIL NIL NIL NIL))
* (push 'w (nth 0 (nth 0 *s*)))

(W)
* *s*

(((W) NIL NIL NIL) 
 (NIL NIL NIL NIL) 
 (NIL NIL NIL NIL) 
 (NIL NIL NIL NIL))
Vatine
  • 20,782
  • 4
  • 54
  • 70
1

Whatever the implementation you choose to use, it is best to define an interface to manipulate your objects. Here below, I define make-board, push-token and pop-token functions. There are other accessor functions you could define, such taking the value at coordinate (x y z).

Then, you only manipulate your data through this interface so that your code is readable and easy to maintain. I am using a 2D matrix of vectors, where the inner vectors are used as stacks thanks to their fill-pointers (see MAKE-ARRAY for details).

Board class and token type

(defclass board ()
  ((matrix :reader board-matrix :initarg :matrix)
   (size :reader board-size :initarg :size)))

(deftype token-type () '(member white black))

Constructor

(defun make-board (size)
  (let ((board
         (make-array (list size size))))
    (dotimes (i (array-total-size board))
      (setf (row-major-aref board i)
            (make-array size
                        :element-type 'symbol
                        :fill-pointer 0)))
    (make-instance 'board :matrix board :size size)))

Custom printer

(defmethod print-object ((b board) stream)
  (print-unreadable-object (b stream :type t)
    (let ((matrix (board-matrix b))
          (size (board-size b)))
      (dotimes (row size)
        (fresh-line)
        (dotimes (col size)
          (let* ((stack (aref matrix row col)))
            (dotimes (z size)
              (princ (case (aref stack z)
                       (white #\w)
                       (black #\b)
                       (t #\.))
                     stream)))
          (princ #\space stream))))))

Push at (x,y)

(defun push-token (board x y token)
  (check-type token token-type)
  (vector-push token (aref (board-matrix board) y x)))

Pop from (x,y)

(defun pop-token (board x y)
  (ignore-errors
    (let ((stack (aref (board-matrix board) y x)))
      (prog1 (vector-pop stack)
        ;; we reset the previous top-most place to NIL because we
        ;; want to allow the access of any cell in the 3D
        ;; board. The fill-pointer is just here to track the
        ;; position of the highest token.
        (setf (aref stack (fill-pointer stack)) nil)))))

Test

(let ((board (make-board 4)))
  (flet ((@ (&rest args) (print board)))
    (print board)
    (@ (push-token board 1 2 'white))
    (@ (push-token board 1 2 'black))
    (@ (push-token board 1 2 'white))
    (@ (push-token board 1 2 'black))
    (@ (push-token board 1 2 'black))
    (@ (push-token board 0 3 'white))
    (@ (pop-token board 1 2))
    (@ (pop-token board 1 2))))

Output

#<BOARD 
.... .... .... .... 
.... .... .... .... 
.... .... .... .... 
.... .... .... .... > 
#<BOARD 
.... .... .... .... 
.... .... .... .... 
.... w... .... .... 
.... .... .... .... > 
#<BOARD 
.... .... .... .... 
.... .... .... .... 
.... wb.. .... .... 
.... .... .... .... > 
#<BOARD 
.... .... .... .... 
.... .... .... .... 
.... wbw. .... .... 
.... .... .... .... > 
#<BOARD 
.... .... .... .... 
.... .... .... .... 
.... wbwb .... .... 
.... .... .... .... > 
#<BOARD 
.... .... .... .... 
.... .... .... .... 
.... wbwb .... .... 
.... .... .... .... > 
#<BOARD 
.... .... .... .... 
.... .... .... .... 
.... wbwb .... .... 
w... .... .... .... > 
#<BOARD 
.... .... .... .... 
.... .... .... .... 
.... wbw. .... .... 
w... .... .... .... > 
#<BOARD 
.... .... .... .... 
.... .... .... .... 
.... wb.. .... .... 
w... .... .... .... >
coredump
  • 37,664
  • 5
  • 43
  • 77