Visual LISP, AutoLISP and General Customization
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Tetris for AutoCAD - help with grread

14 REPLIES 14
Reply
Message 1 of 15
doaiena
1972 Views, 14 Replies

Tetris for AutoCAD - help with grread

I thought it might be interesting to play a quick game inside AutoCAD, so yesterday i wrote a quick draft for a tetris game.

The idea was to store the game info in a 2D matrix or list of lists, to perform the backend logic, and present the user with graphics made of polyline shapes. I wrote a quick "skeleton" for the game, but when i got to the main loop and the user input, i got stuck. I can't find a way to keep the main loop running in the background, while waiting for user input with grread. After a set ammount of time, the active shape should move down, independent of any user input. I might be totally wrong with my approach, so suggestions are very welcome.

"Q" - exit

"W,S,A,D" - controls /Up,Down,Left,Right/

Everything is global, i didn't bother localizing variables at this stage.

(defun c:tetris ( / )

;;----------------------=={ Remove Nth }==--------------------;;
;;                                                            ;;
;;  Removes the item at the nth index in a supplied list      ;;
;;------------------------------------------------------------;;
;;  Author: Lee Mac, Copyright © 2011 - www.lee-mac.com       ;;
;;------------------------------------------------------------;;
;;  Arguments:                                                ;;
;;  n - index of item to remove (zero based)                  ;;
;;  l - list from which item is to be removed                 ;;
;;------------------------------------------------------------;;
;;  Returns:  List with item at index n removed               ;;
;;------------------------------------------------------------;;

(defun LM:RemoveNth ( n l / i )
    (setq i -1)
    (vl-remove-if '(lambda ( x ) (= (setq i (1+ i)) n)) l)
);defun

(defun CreateCanvas ()
(setq canvasEnt
(LWPoly (list origin
	      (mapcar '+ origin (list 0 (* rows pixelSize) 0))
	      (mapcar '+ origin (list (* cols pixelSize) (* rows pixelSize) 0))
	      (mapcar '+ origin (list (* cols pixelSize) 0 0))))
)
(ClearCanvas rows cols)
);defun

(defun ClearCanvas (rows cols)
(setq canvas nil)
(repeat rows (setq canvas (cons (repeat cols (EmptyRow)) canvas)))
);defun

(defun EmptyRow ( / row)
(repeat cols (setq row (cons 0 row)))
);defun

(defun RemoveRow (rowPos / row)
(setq canvas (append (LM:RemoveNth rowPos canvas) (list (EmptyRow))))
);defun

(defun LWPoly (lst)
(entmakex (append (list (cons 0 "LWPOLYLINE")
(cons 100 "AcDbEntity")
(cons 100 "AcDbPolyline")
(cons 90 (length lst))
(cons 70 1))
(mapcar (function (lambda (p) (cons 10 p))) lst)))
);defun

(defun NewPixel (pt)
(setq activeShapePos (cons (mapcar '/ pt (list 10 10)) activeShapePos))
(LWPoly (list pt
	      (mapcar '+ pt (list 0 pixelSize 0))
	      (mapcar '+ pt (list pixelSize pixelSize 0))
	      (mapcar '+ pt (list pixelSize 0 0))))
);defun

(defun Shape1 ()
(setq activeShapePos nil)
(mapcar '(lambda (pixel)
(ssadd pixel activeShape)
)
(list
(NewPixel spawnPoint)
(NewPixel (mapcar '+ spawnPoint (list 0 pixelSize 0)))
(NewPixel (mapcar '+ spawnPoint (list pixelSize pixelSize 0)))
(NewPixel (mapcar '+ spawnPoint (list pixelSize 0 0)))
)
)
);defun

(defun SpawnShape (shape)

(setq activeShape (ssadd))
(cond
((= shape 1) (Shape1))
);cond

);defun

;;;(defun ShapeStop (shape)
;;;
;;;);defun
;;;
;;;(defun ShapeRotate (shape)
;;;
;;;);defun

(defun MoveUp ()
(command "move" activeShape "" (list 0 0 0) (list 0 pixelSize 0))
);defun

(defun MoveDown ()
(command "move" activeShape "" (list 0 0 0) (list 0 (* pixelSize -1) 0))
);defun

(defun MoveLeft ()
(command "move" activeShape "" (list 0 0 0) (list (* pixelSize -1) 0 0))
);defun

(defun MoveRight ()
(command "move" activeShape "" (list 0 0 0) (list pixelSize 0 0))
);defun

(defun sleep (secs / time)
(setq time (getvar "Millisecs"))
(while (< (/ (- (getvar "Millisecs") time) 1000.0) secs) nil)
);defun




;;;					
;;;	FUNCTION STARTS HERE		
;;;					


(setq origin (list 0 0 0))
(setq pixelSize 10)
(setq rows 20)
(setq cols 10)
(setq spawnPoint (mapcar '+ origin (list (* (- (/ cols 2) 1) pixelSize) (* rows pixelSize) 0)))
;;;(setq canvasX (* pixelSize cols))
;;;(setq canvasY (* pixelSize rows))
;;;(setq color 7)
;;;(setq activeShapeColor 3)
(setq gameOver nil)
(setq timer nil)
(setq tick 0.5)

(command "_-view" "_t")
(command "_ucs" "_w")
(CreateCanvas)
(command "_zoom" "o" canvasEnt "")
(SpawnShape 1)

(while (not gameOver)

(if (not timer) (setq timer (getvar "Millisecs")))
(if (< (/ (- (getvar "Millisecs") timer) 1000.0) tick)
(progn
(setq gRead (grread T 15 1) grCode (car gRead) grVal (cadr gRead))

(cond
;Quit game
((vl-position grVal '(113 81)) (setq gameOver T) (alert "Game Over!"));q Q
((vl-position grVal '(115 83)) (MoveDown));s S
((vl-position grVal '(97 65)) (MoveLeft));a A
((vl-position grVal '(100 68)) (MoveRight));d D
((vl-position grVal '(119 87)) (MoveUp));w W for testing
);cond

)

(progn
(MoveDown)
(setq timer (getvar "Millisecs"))
)
)

);main loop

(princ)
);defun

 

14 REPLIES 14
Message 2 of 15
dlanorh
in reply to: doaiena

Not tested, but browsing 10 year old code

 

    (setq flg T)
    (while flg
      (redraw)
      (setq gr (grread T 8))
      (if (< (/ (- (getvar "Millisecs") timer) 1000.0) tick)
        (cond
          ((or (equal gr '(2 81)) (equal gr '(2 113))) (setq flg nil))
          ((or (equal gr '(2 83)) (equal gr '(2 115))) (MoveDown))
          ((or (equal gr '(2 65)) (equal gr '(2 97))) (MoveLeft))
          ((or (equal gr '(2 68)) (equal gr '(2 100))) (MoveRight))
          ((or (equal gr '(2 87)) (equal gr '(2 119))) (MoveUp))
        )
        (progn (MoveDown)  (setq timer (getvar "Millisecs")))
      )
    )
    (alert "Game Over!")

I am not one of the robots you're looking for

Message 3 of 15
doaiena
in reply to: doaiena

Maybe i didn't state my problem clearly enough in the first post. In the main loop, the function should constantly "sense" for user input /button press/. Independent of the user input, after a given ammount of time, the function should execute the "MoveDown" code. The problem comes here. When i set up "grread" to read key presses /track = nil, bit0 = 0/, the loop halts at the grread and waits for user input indefinitely. I can't find a way to "escape" out of the grread function if no key is pressed after a while.

 

If track = T or bit0 = 1, any mouse movement gets captured by grread and the code can continue for another loop. That means that the user should be constantly moving the mouse, in order to keep the loop running.

If i execute a "zoom" command, just before the "grread", that is interpreted as a mouse movement and the loop keeps moving on. The problem with that "hack" is that "grread" gets flooded with data so fast, that it can't read the user's keyboard inputs anymore.

 

Basically that's my problem. I need to find a way to keep the main loop running and at the same time "sense" for user input. I even thought about running the loop out of the main acad process /external app in .NET/ and injecting commands into AutoCAD as needed, but that's a whole nother can of worms and defeats the purpose of writing it in lisp in the first place.

 

I wonder if what i am trying to achieve is even possible in lisp...

Message 4 of 15
Anonymous
in reply to: doaiena

I liked the idea!!! 😛😱🤗

Message 5 of 15
CADaSchtroumpf
in reply to: Anonymous

With this in mind, I made a lisp clone of the 2048 game.

Merry Christmas.

Message 6 of 15
Anonymous
in reply to: CADaSchtroumpf

@CADaSchtroumpf  The snake game is missing 🤣:snake:

 

HeartfeltAcceptableAmericanrobin-small.gif

 

Message 7 of 15
doaiena
in reply to: doaiena

I got the loop working, but the timing is very inconsintent. I just can't get it to run in a smooth predictable pace, so i'm giving up on the idea to make such a game in AutoCAD, but i found a few useful things, while tinkering with this.

 

I searched the internet for a way to move a lwpolyline with entmod, but i wasn't able to find anything on the topic, so i wrote my own function.


Move a lwpolyline using entmod:
Usage:
(entmod (MassMod (entget ename) (list 0 X Y)))

 

(defun MassMod (lst mod / pair return)
(while (setq pair (car lst) lst (cdr lst))
(setq return (cons (if (= (car pair) 10) (mapcar '+ pair mod) pair) return))
)
(reverse return)
);defun



Another useful thing i found is a way to capture user input inside a loop, without halting the loop, with the help of a relatively simple .NET dll. I made a LispFunction that listens for user input, within a given timeout and if there is input, it is stored in the "keyPress" variable. If the timeout is reached, the function exits, without assigning any value to the "keyPress" variable.

This function was inspired by Kean Walmsley, so all credit goes here: https://www.keanw.com/2007/02/allowing_users_.html 


Intercept user input inside a loop, withing a given timeout:

Argument: "timeout" - timeout in milliseconds /INTEGER/

The function does not return anything, but if a key is pressed, it sets the key /as a string/ in a variable named "keyPress". You can exit the function with the "ESC" key, if you set a long timeout and want to abort.

Usage:

(setq keyPress nil)

(GetInput timeout)
(cond
((= keypress "Q") (DoSomething));q Q
((= keypress "S") (DoSomething));S
((= keypress "A") (DoSomething));A
((= keypress "D") (DoSomething));D
((= keypress "W") (DoSomething));W
(T (DoSomethingElse))
);cond

using System;
using System.Diagnostics;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using WinForms = System.Windows.Forms;

namespace ACAD_Test
{
    public class MyCommands
    {
        [LispFunction("GetInput")]
        static public void GetInput(ResultBuffer resbuf)
        {
            MyMessageFilter filter = new MyMessageFilter();
            WinForms.Application.AddMessageFilter(filter);
            try
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Editor ed = doc.Editor;

                TypedValue[] args = resbuf.AsArray();
                int timeout = (int)args[0].Value;

                while (sw.ElapsedMilliseconds < timeout)
                {
                    System.Windows.Forms.Application.DoEvents();

                    if (filter.bCanceled == true)
                    {
                        ed.WriteMessage("\nGetInput function cancelled:");
                        break;
                    }

                    switch (filter.key)
                    {
                        case "A":
                            doc.SetLispSymbol("keyPress", new TypedValue((int)LispDataType.Text, "A"));
                            break;
                        case "D":
                            doc.SetLispSymbol("keyPress", new TypedValue((int)LispDataType.Text, "D"));
                            break;
                        case "S":
                            doc.SetLispSymbol("keyPress", new TypedValue((int)LispDataType.Text, "S"));
                            break;
                        case "W":
                            doc.SetLispSymbol("keyPress", new TypedValue((int)LispDataType.Text, "W"));
                            break;
                        case "Q":
                            doc.SetLispSymbol("keyPress", new TypedValue((int)LispDataType.Text, "Q"));
                            break;
                    }
                }
                System.Windows.Forms.Application.RemoveMessageFilter(filter);
            }
            catch (System.Exception Ex)
            {
                System.Windows.Forms.Application.RemoveMessageFilter(filter);
                Application.ShowAlertDialog("System exception:\n" + Ex.Message + "\n" + Ex.Source + "\n" + Ex.StackTrace);
            }
        }
    }//Class

    public class MyMessageFilter : WinForms.IMessageFilter
    {
        public const int WM_KEYDOWN = 0x0100;
        public bool bCanceled = false;
        public string key = "";

        public bool PreFilterMessage(ref WinForms.Message m)
        {
            if (m.Msg == WM_KEYDOWN)
            {
                // Check for the Escape keypress
                WinForms.Keys kc = (WinForms.Keys)(int)m.WParam & WinForms.Keys.KeyCode;

                if (kc == WinForms.Keys.Escape)
                {
                    bCanceled = true;
                }
                key = kc.ToString();
                // Return true to filter all keypresses
                return true;
            }
            key = "";
            // Return false to let other messages through
            return false;
        }//Method
    }//Class
}//Namespace

 

 

 

I Hope you find these functions useful, so that at least i won't have wasted my time in vain.

Message 8 of 15
doaiena
in reply to: doaiena

I found a lot of things i've been doing wrong. I'm sure my approach is wrong, but i stuck to it. Anyway, as a proof of concept at least...

 

Rotation is not implemented.
https://streamable.com/22947 

Message 9 of 15
vladimir_michl
in reply to: doaiena

Looks good! This is already with the keypress DLL? I would propose to use blocks (drawn with a byblock color) for the pieces - rotation and fancy designs will be easier.

 

Vladimir Michl, www.cadforum.cz - www.cadstudio.cz 

Message 10 of 15
Anonymous
in reply to: doaiena

Very good!!! 👏👏

Message 11 of 15
vladimir_michl
in reply to: doaiena

Not a Tetris, but also based on the KeyboardInput - Tennis game for AutoCAD:

 

 

via https://www.cadstudio.cz/en/the-17th-christmas-cad-freeware-giveaway-by-cad-studio-art3562

 

Vladimir Michl, www.cadforum.cz

 

Message 12 of 15
doaiena
in reply to: vladimir_michl

Bravo! An interesting addition to the "AutoCAD games library". Yesterday i posted another entry here.

Message 13 of 15
john.uhden
in reply to: doaiena

The program can not wait for a flag to be set by a grread.  It's got to work something something like this pseudo code:

(set done nil) ;; or localize it
(while (not done)
(setq code nil) (while (> timeleft 0) (if (> timeleft 0))
(setq code (grread etc))
 (decrement timeleft)
) (cond
((= timeleft 0)(move_down)) ((= code this)(do_this)) ((= code that)(do_that)) ((equal code <quit>)(setq done 1)) ) ) (move_down) )
;; I guess the timeleft should be reset within move_down.
;; I dunno. I should be eating rather than drinking.

But I think even that requires at least moving your pointer, even if only an angstrom.

John F. Uhden

Message 14 of 15
doaiena
in reply to: john.uhden

@john.uhden 
No matter how you structure the code, the problem remains the same. Once the program reaches the "grread" function, it will stop execution until there is some type of user input. To get around that, you either need a background thread, constantly monitoring for user input and executing functions accordingly, or set a timeout on your user input function,  to break out of it once that timeout window has passed. I wanted to control the main loop from lisp, so i opted to go for a grread-like function with a built in timeout, written in .NET.

Message 15 of 15
john.uhden
in reply to: doaiena

I believe you are correct. Sadly, the only NET I know is the one I use to
scoop leaves out of my pool.

John F. Uhden

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Technology Administrators


Autodesk Design & Make Report