Author Topic: B+ Pathfinder  (Read 2461 times)

B+

  • Guest
B+ Pathfinder
« on: August 14, 2018, 02:29:10 AM »
Saturday, I learned about the A star Pathfinder, today I present to the world the B+ Pathfinder that uses no knowledge of where B is (when finding the shortest path from A to B) such that it is possible to find paths to all the B's you want on a map.

Caveat, it zigs and zags when /where you wouldn't expect it to, but as far as I can tell it does not use more squares should the path be straightened, each square represents a step like a tick of the clock.

Code: [Select]
_TITLE "PathFinder 1a, press spacebar to continue whenever it stops, press esc to quit"
'QB64 X 64 version 1.2 20180228/86  from git b301f92
'started 2018-08-11 when Colbalt asked about A* pathfinder
' He has now 2018-08-12 posted a QB64 version that is nice!

' 2018-08-11 started PathFinder 1
' 2018-08-12 almost working but buggy backtract to point A after point B is found.
' 2018-08-13 PathFinder 1a.bas I think I have a fix for buggy path BackTrack but major surgery so this new version
' 8:38 PM 28 times in a row it worked, now diddle the size of array and loose the board text view
' 9:09 PM I have run 2 dozen tests more at least and zero bugs! on smaller and more squares.
' 9:25 PM Still no failures! Ready to show the world.


DEFINT A-Z
CONST ww = 800
CONST wh = 600
CONST sq = 10
CONST maxx = 80
CONST maxy = 60

SCREEN _NEWIMAGE(ww, wh, 32)
_SCREENMOVE (1280 - ww) / 2 + 30, (760 - wh) / 2
RANDOMIZE TIMER

TYPE xytype
    x AS INTEGER
    y AS INTEGER
END TYPE
DIM SHARED ax, ay, bx, by
DIM SHARED board(1 TO maxx, 1 TO maxy) AS STRING * 6
DO
    FOR y = 1 TO maxy
        FOR x = 1 TO maxx
            board(x, y) = " "
        NEXT
    NEXT
    'with these obstacles there is no guarantee a path will exist
    FOR i = 1 TO maxx * maxy * .8
        ox = rand(1, maxx): oy = rand(1, maxy)
        WHILE (ox = ax AND oy = ay) OR (ox = bx AND ox = by)
            ox = rand(1, maxx): oy = rand(1, maxy)
        WEND
        board(ox, oy) = "O"
    NEXT
    ax = rand(1, maxx): ay = rand(1, maxy)
    bx = rand(1, maxx): by = rand(1, maxy)
    WHILE ax = bx AND ay = by
        bx = rand(1, maxx): by = rand(1, maxy)
    WEND
    board(ax, ay) = "A"
    board(bx, by) = "B"
    displayB
    WHILE NOT _KEYDOWN(32): _LIMIT 100: WEND
    WHILE _KEYDOWN(32): _LIMIT 100: WEND
    parentF = 1: tick = 0: parentx = 0
    WHILE parentF = 1 AND parentx = 0
        parentF = 0: tick = tick + 1: changes$ = ""
        'IF tick > maxx * maxy THEN EXIT WHILE   'this was crude infinite loop stopper
        ystart = max(ay - tick, 1): ystop = min(ay + tick, maxy)
        FOR y = ystart TO ystop
            xstart = max(ax - tick, 1): xstop = min(ax + tick, maxx)
            'PRINT ystart, ystop, xstart, xstop
            'END
            FOR x = xstart TO xstop
                'check out the neighbors
                IF x - 1 >= 1 THEN xxstart = x - 1 ELSE xxstart = x
                IF x + 1 <= maxx THEN xxstop = x + 1 ELSE xxstop = x
                IF y - 1 >= 1 THEN yystart = y - 1 ELSE yystart = y
                IF y + 1 <= maxy THEN yystop = y + 1 ELSE yystop = y
                IF RTRIM$(board(x, y)) = "" THEN
                    cf = 0
                    FOR yy = yystart TO yystop
                        FOR xx = xxstart TO xxstop
                            IF xx <> x OR yy <> y THEN
                                IF RTRIM$(board(xx, yy)) = "A" OR INSTR(RTRIM$(board(xx, yy)), ",") > 0 THEN 'found a parent to assign to cell

                                    '          This had ne stuck for awhile!
                                    'LOGIC BUG   board(x, y) = LTRIM$(STR$(xx)) + "," + LTRIM$(STR$(yy))
                                    'can't change board until all are checked!!!!   so save up changes
                                    changes$ = changes$ + LTRIM$(STR$(x)) + "," + LTRIM$(STR$(y)) + "{" + LTRIM$(STR$(xx)) + "," + LTRIM$(STR$(yy)) + "}"
                                    rec (x - 1) * sq + 3, (y - 1) * sq + 3, x * sq - 3, y * sq - 3, 555
                                    parentF = 1 'so will continue looping
                                    cf = 1: EXIT FOR
                                END IF
                            END IF
                        NEXT
                        IF cf THEN EXIT FOR
                    NEXT
                ELSEIF RTRIM$(board(x, y)) = "A" OR INSTR(board(x, y), ",") > 0 THEN 'see if B is a neighbor
                    FOR yy = yystart TO yystop
                        FOR xx = xxstart TO xxstop
                            IF xx <> x OR yy <> y THEN
                                IF RTRIM$(board(xx, yy)) = "B" THEN 'B conects to x, y
                                    parentx = x: parenty = y 'from this we should be able to backtrack to A
                                    GOTO jump1
                                END IF
                            END IF
                        NEXT
                    NEXT
                END IF
            NEXT
        NEXT
        jump1:
        'update board with cells assigned parents
        WHILE changes$ <> ""
            new$ = leftOf$(changes$, "}")
            changes$ = rightOf$(changes$, "}")
            newxy$ = leftOf$(new$, "{")
            newParent$ = rightOf$(new$, "{")
            u = VAL(leftOf$(newxy$, ",")): v = VAL(rightOf$(newxy$, ","))
            board(u, v) = leftOf$(newParent$, ",") + "," + rightOf$(newParent$, ",")
        WEND

        '_DISPLAY
        _LIMIT 10
    WEND
    'displayB
    'either parentF = 0, no parents found for all the cells in tick or parentbx was found and we have a path to backtrack to A
    BackTrack$ = ""
    IF parentx THEN 'backtrack to A   note: B could be right next to A!!!
        'IF parentx <> ax AND parenty <> ay THEN
        frec (parentx - 1) * sq + 3, (parenty - 1) * sq + 3, sq - 6, sq - 6, 990
        'END IF

        'this had me stuck for the longest time! parentx was the fix! (along with removal of blunders)
        WHILE parentx 'trace the path back
            ps$ = board(parentx, parenty)
            parentx = VAL(leftOf$(ps$, ","))
            parenty = VAL(rightOf$(ps$, ","))

            'IF parentx <> ax AND parenty <> ay THEN
            frec (parentx - 1) * sq + 3, (parenty - 1) * sq + 3, sq - 6, sq - 6, 990
            'END IF
            'IF parentx <> ax AND parenty <> ay THEN EXIT WHILE
            '_DISPLAY
            _LIMIT 10
        WEND
        WHILE NOT _KEYDOWN(32)
            IF _KEYDOWN(27) THEN END
            _LIMIT 100
        WEND
        WHILE _KEYDOWN(32): _LIMIT 100: WEND
    ELSE
        rgb 999
        LOCATE 15, 10: PRINT "Did not connect to B"
        _DELAY 3
    END IF

    'for this to be of any use, I need to format the print to exact same size, well thats easy
    '
    '                      this is why board is string * 6 type
    '                          maxx = 16 maxy = 12  or less
    'rgb 999
    'CLS
    'FOR y = 1 TO maxy
    '    FOR x = 1 TO maxx
    '        PRINT board(x, y);
    '    NEXT
    '    PRINT: PRINT
    'NEXT
    'WHILE NOT _KEYDOWN(32)
    '    IF _KEYDOWN(27) THEN END
    '    _LIMIT 100
    'WEND
    'WHILE _KEYDOWN(32): _LIMIT 100: WEND
LOOP UNTIL _KEYDOWN(27)
SUB displayB
    FOR y = 1 TO maxy
        FOR x = 1 TO maxx
            SELECT CASE RTRIM$(board(x, y))
                CASE IS = "": k = 0
                CASE IS = "A": k = 9
                CASE IS = "B": k = 999
                CASE IS = "O": k = 50
                    'CASE ELSE: k = 30
            END SELECT
            frec (x - 1) * sq, (y - 1) * sq, sq, sq, k
        NEXT
    NEXT
END SUB
SUB rec (x1, y1, x2, y2, rgbN)
    rgb rgbN
    LINE (x1, y1)-(x2, y2), , B
END SUB
SUB frec (x1, y1, w, h, rgbN)
    rgb rgbN
    LINE (x1, y1)-STEP(w, h), , BF
END SUB
SUB rgb (n) ' New (even less typing!) New Color System 1000 colors with up to 3 digits
    s3$ = RIGHT$("000" + LTRIM$(STR$(n)), 3)
    r = VAL(MID$(s3$, 1, 1)): IF r THEN r = 28 * r + 3
    g = VAL(MID$(s3$, 2, 1)): IF g THEN g = 28 * g + 3
    b = VAL(MID$(s3$, 3, 1)): IF b THEN b = 28 * b + 3
    COLOR _RGB32(r, g, b)
END SUB
FUNCTION rand% (lo%, hi%)
    rand% = INT(RND * (hi% - lo% + 1)) + lo%
END FUNCTION
FUNCTION min (n1, n2)
    IF n1 > n2 THEN min = n2 ELSE min = n1
END FUNCTION
FUNCTION max (n1, n2)
    IF n1 < n2 THEN max = n2 ELSE max = n1
END FUNCTION
FUNCTION leftOf$ (source$, of$)
    posOf = INSTR(source$, of$)
    IF posOf > 0 THEN leftOf$ = MID$(source$, 1, posOf - 1)
END FUNCTION
FUNCTION rightOf$ (source$, of$)
    posOf = INSTR(source$, of$)
    IF posOf > 0 THEN rightOf$ = MID$(source$, posOf + LEN(of$))
END FUNCTION


PS this is hot off the workbench and there is allot of stuff yet to cleanup: eg make this into a reusable subroutine.
« Last Edit: August 14, 2018, 02:32:37 AM by B+ »

johnno56

  • Guest
Re: B+ Pathfinder
« Reply #1 on: August 14, 2018, 08:02:41 AM »
Very cool indeed!

J

B+

  • Guest
Re: B+ Pathfinder
« Reply #2 on: August 15, 2018, 02:40:40 AM »
B+ Pathfinder 2, actually once the map is setup, you can find a path from any point on it (if there is one to be found).

To me, this is a far more powerful algorithm that A star Pathfinder.

Here is a demo:
Code: [Select]
_TITLE "PathFinder 2, prepping maze as you read this."
'QB64 X 64 version 1.2 20180228/86  from git b301f92
'started 2018-08-11 when Colbalt asked about A* pathfinder
' He has now 2018-08-12 posted a QB64 version that is nice!

' 2018-08-11 started PathFinder 1
' 2018-08-12 almost working but buggy backtract to point A after point B is found.
' 2018-08-13 PathFinder 1a.bas I think I have a fix for buggy path BackTrack but major surgery so this new version
' 2018-08-14 Pathfinder 2:  2 parts
' Part 1: creates a map, a random Home point and backtracts all the points available to move over.
' Part 2: allows clicking variaous points of map to see if path is found.

DEFINT A-Z
CONST ww = 800
CONST wh = 600
CONST sq = 10
CONST mapw = 80
CONST maph = 60

SCREEN _NEWIMAGE(ww, wh, 32)
_SCREENMOVE (1280 - ww) / 2 + 30, (760 - wh) / 2
RANDOMIZE TIMER

DIM SHARED ax, ay, bx, by
DIM SHARED board(1 TO mapw, 1 TO maph) AS STRING * 6
DO

    'this part sets up a sample map and get's the Backtracking build into map

    FOR y = 1 TO maph
        FOR x = 1 TO mapw
            board(x, y) = " "
        NEXT
    NEXT
    'with these obstacles there is no guarantee a path will exist
    FOR i = 1 TO mapw * maph * .7
        ox = rand(1, mapw): oy = rand(1, maph)
        board(ox, oy) = "O"
    NEXT
    ax = rand(1, mapw): ay = rand(1, maph)
    board(ax, ay) = "A"
    parentF = 1: tick = 0: parentx = 0
    WHILE parentF = 1 AND parentx = 0
        parentF = 0: tick = tick + 1: changes$ = ""
        ystart = max(ay - tick, 1): ystop = min(ay + tick, maph)
        FOR y = ystart TO ystop
            xstart = max(ax - tick, 1): xstop = min(ax + tick, mapw)
            FOR x = xstart TO xstop
                'check out the neighbors
                IF x - 1 >= 1 THEN xxstart = x - 1 ELSE xxstart = x
                IF x + 1 <= mapw THEN xxstop = x + 1 ELSE xxstop = x
                IF y - 1 >= 1 THEN yystart = y - 1 ELSE yystart = y
                IF y + 1 <= maph THEN yystop = y + 1 ELSE yystop = y
                IF RTRIM$(board(x, y)) = "" THEN
                    cf = 0
                    FOR yy = yystart TO yystop
                        FOR xx = xxstart TO xxstop
                            IF xx <> x OR yy <> y THEN
                                IF RTRIM$(board(xx, yy)) = "A" OR INSTR(RTRIM$(board(xx, yy)), ",") > 0 THEN 'found a parent to assign to cell
                                    changes$ = changes$ + LTRIM$(STR$(x)) + "," + LTRIM$(STR$(y)) + "{" + LTRIM$(STR$(xx)) + "," + LTRIM$(STR$(yy)) + "}"
                                    parentF = 1 'so will continue looping
                                    cf = 1: EXIT FOR
                                END IF
                            END IF
                        NEXT
                        IF cf THEN EXIT FOR
                    NEXT
                END IF
            NEXT
        NEXT
        'update board with cells assigned parents
        WHILE changes$ <> ""
            new$ = leftOf$(changes$, "}")
            changes$ = rightOf$(changes$, "}")
            newxy$ = leftOf$(new$, "{")
            newParent$ = rightOf$(new$, "{")
            u = VAL(leftOf$(newxy$, ",")): v = VAL(rightOf$(newxy$, ","))
            board(u, v) = leftOf$(newParent$, ",") + "," + rightOf$(newParent$, ",")
        WEND
        _LIMIT 300
    WEND


    'this parts displays the ability to find a path to blue square anywhere in the maze

    _TITLE "Click maze to find a path to blue square (if any), c = clear, n = new map, esc = quit"
    displayB
    DO
        'CLS
        'displayB
        WHILE _MOUSEINPUT: WEND
        IF _MOUSEBUTTON(1) THEN
            mx = _MOUSEX - .5 * sq: my = _MOUSEY - .5 * sq
            bx = mx / sq + 1: by = my / sq + 1
            IF bx >= 1 AND bx <= mapw AND by >= 1 AND by <= maph THEN
                frec (bx - 1) * sq + 2, (by - 1) * sq + 2, sq - 4, sq - 4, 990
                ps$ = board(bx, by)
                parentx = VAL(leftOf$(ps$, ","))
                parenty = VAL(rightOf$(ps$, ","))
                IF parentx THEN 'backtrack to A   note: B could be right next to A!!!
                    frec (parentx - 1) * sq + 3, (parenty - 1) * sq + 3, sq - 6, sq - 6, 999
                    WHILE parentx 'trace the path back
                        ps$ = board(parentx, parenty)
                        parentx = VAL(leftOf$(ps$, ","))
                        parenty = VAL(rightOf$(ps$, ","))
                        frec (parentx - 1) * sq + 3, (parenty - 1) * sq + 3, sq - 6, sq - 6, 999
                        _LIMIT 10
                        _DISPLAY
                    WEND
                ELSE
                    rgb 999
                    LOCATE 15, 10: PRINT "Did not connect to B"
                    _DISPLAY
                    _DELAY 3
                    displayB
                END IF
            END IF
        END IF
        IF _KEYDOWN(27) THEN END
        IF _KEYDOWN(ASC("n")) THEN EXIT DO
        IF _KEYDOWN(ASC("c")) THEN displayB
        _DISPLAY
        _LIMIT 100
    LOOP
LOOP UNTIL _KEYDOWN(27)

SUB displayB
    FOR y = 1 TO maph
        FOR x = 1 TO mapw
            SELECT CASE RTRIM$(board(x, y))
                CASE "A": k = 9
                CASE "B": k = 905
                CASE "O": k = 50
                CASE ELSE: k = 0
            END SELECT
            frec (x - 1) * sq, (y - 1) * sq, sq, sq, k
        NEXT
    NEXT
END SUB
SUB rec (x1, y1, x2, y2, rgbN)
    rgb rgbN
    LINE (x1, y1)-(x2, y2), , B
END SUB
SUB frec (x1, y1, w, h, rgbN)
    rgb rgbN
    LINE (x1, y1)-STEP(w, h), , BF
END SUB
SUB rgb (n) ' New (even less typing!) New Color System 1000 colors with up to 3 digits
    s3$ = RIGHT$("000" + LTRIM$(STR$(n)), 3)
    r = VAL(MID$(s3$, 1, 1)): IF r THEN r = 28 * r + 3
    g = VAL(MID$(s3$, 2, 1)): IF g THEN g = 28 * g + 3
    b = VAL(MID$(s3$, 3, 1)): IF b THEN b = 28 * b + 3
    COLOR _RGB32(r, g, b)
END SUB
FUNCTION rand% (lo%, hi%)
    rand% = INT(RND * (hi% - lo% + 1)) + lo%
END FUNCTION
FUNCTION min (n1, n2)
    IF n1 > n2 THEN min = n2 ELSE min = n1
END FUNCTION
FUNCTION max (n1, n2)
    IF n1 < n2 THEN max = n2 ELSE max = n1
END FUNCTION
FUNCTION leftOf$ (source$, of$)
    posOf = INSTR(source$, of$)
    IF posOf > 0 THEN leftOf$ = MID$(source$, 1, posOf - 1)
END FUNCTION
FUNCTION rightOf$ (source$, of$)
    posOf = INSTR(source$, of$)
    IF posOf > 0 THEN rightOf$ = MID$(source$, posOf + LEN(of$))
END FUNCTION


johnno56

  • Guest
Re: B+ Pathfinder
« Reply #3 on: August 15, 2018, 03:38:40 AM »
Slight problem. I click on the maze and all I get is, "Did not connect to B". I tried many locations... I have attached one of my attempts...

J

B+

  • Guest
Re: B+ Pathfinder
« Reply #4 on: August 15, 2018, 10:55:06 AM »
There you have clicked the hedge, the black is the free space on which paths are stepped through.

Also I was not satisfied when I clicked a spot that it was clicking the right square, so I adjusted the mouseX, mouseY by .5*sq = half the square size. For me it was better, for your pointer maybe worse,

Here is the line to diddle:
mx = _MOUSEX - .5 * sq: my = _MOUSEY - .5 * sq

maybe just leave it to mouse x, y no adjustments? Maybe adjust a whole square?

But you should be definitely attempting to click a black square. And some places will not connect to A the blue rectangle because the hedge completely surrounds the spot clicked.

johnno56

  • Guest
Re: B+ Pathfinder
« Reply #5 on: August 15, 2018, 01:50:38 PM »
Ok. Let's try that dance again... Many thanks.

J

chrisws

  • Guest
Re: B+ Pathfinder
« Reply #6 on: August 16, 2018, 09:14:31 PM »
Hi Johnn and B+

I've finally joined this forum!

The A* algorithm is pretty cool, but Q-learning is way more interesting!

Two exclamations in my first post. how's that?

Chris
« Last Edit: August 16, 2018, 09:16:16 PM by chrisws »

B+

  • Guest
Re: B+ Pathfinder
« Reply #7 on: August 16, 2018, 09:21:53 PM »
Hi Johnn and B+

I've finally joined this forum!

The A* algorithm is pretty cool, but Q-learning is way more interesting!

Two exclamations in my first post. how's that?

Chris

I am happy to see you here too!!

johnno56

  • Guest
Re: B+ Pathfinder
« Reply #8 on: August 16, 2018, 09:50:07 PM »
Greetings, Chris (may I call you 'Chris'?)

I fail to understand the reference to using a specific number of exclamations in a posting, as you have stated. 'I've finally joined this forum'. I have to conclude that this is a reference to which both you and Mark have experienced elsewhere.

Regardless, of my lack of understanding, I am gratified that a 'coder' of your calibre has decided to, what is the correct idiom, 'Hang with us'. I am confident that I will learn a great deal.

Welcome.

J

B+

  • Guest
Re: B+ Pathfinder
« Reply #9 on: August 24, 2018, 03:57:13 PM »
Latest update of Pathfinder 4 has some interesting changes:
#1 the diagonal stepping has been eliminated greatly simplifying code, plus more realistic paths through mazes.
#2 you can make paths to destination points (look like square targets) by moving obstacles around.
#3 the blue door takes you to new map and the maps all start with blue door as destination which can be changed by clicking other places on map.

No fancy tiling or anything just working out mechanics of a game setup.

Moving obstacles around is possible because multiple keys can be read:
_KEYHIT can report when keys are down and released (with negative numbers return) this is handy for taking one step at a time with arrow keys to move hero (what we call the main player).
_KEYDOWN(key_const) can report multiple keys being pressed True/False
So that the combined use of key report functions allows me to program the moving of items around including the blue door exit to another map.

Code: [Select]
_TITLE "PathFinder 4, No diagonal movements"
'QB64 X 64 version 1.2 20180228/86  from git b301f92

' version 4: Pathfinder or Path Maker!

' ================================ Instructions ==============================================
'  ctrl + Arrow will haul a green obstacle
' shift + Arrow will push a green obstacle as long as there is a space on the other side of it.
' With a new map the destination for paths is set the same as the blue door, see target.
' The destination can be reset by clicking the map.
' The blue square is the exit or door to another map, so is pressing n for new.
' A yellow path will appear between the white hero player and the destination if the way is not
' blocked. If it is, you can usually clear a path to the door by moving obstacles around.
' When you clear a path the yellow path will light up.   esc to quit.
' Oh yeah, you can haul the door around too.
'
' It is strangely fun to move the green obstacles around, like sculpting with a bull dozer.
' I am thinking maybe some sort of landscaping game.
'=============================================================================================
' History of development:
' started 2018-08-11 when Colbalt asked about A* pathfinder
' He has now 2018-08-12 posted a QB64 version that is nice!

' 2018-08-11 started PathFinder 1
' 2018-08-12 almost working but buggy backtract to point A after point B is found.
' 2018-08-13 PathFinder 1a.bas I think I have a fix for buggy path BackTrack but major surgery so this new version
' 2018-08-14 Pathfinder 2:  2 parts
' Part 1: creates a map, a random Home point and backtracts all the points available to move over.
' Part 2: allows clicking variaous points of map to see if path is found.

' 2018-08-15 PathFinder 3: Make a function that returns a path from A to B given A, B
' The map is shared as well as it's dimensions mapw by maph (constants).
' The function will create a copy of map and work with that so main map remains intact.
' Well I suppose we need aa random map maker for starters.
' And then of course a sub to display the map.
' OK I am now working under the influence of Steve McNeil's programs. ;)
' Thanks Steve some great ideas for simplfying the PathFinder for actual game use.

' 2018-08-17 With Pathfinder 3 I want to start working code towards game using MAP(s)
' I am also under the influence of PMACKAY's game series Mr Bash or Dash or whatever he decides to call the Diamond Miner.
' Thanks PMACKAY, I love the little game you started and have learned from your questions and setup.

'2018-08-18 Pathfinder 3a, I have 3 working with ticks but it changes all after running through the whole tick session.
' 3a tests whether I can do this with Steve's version which changes immediately and not after whole tick session.
' It is allot simpler and will run faster than saving all changes in a container and reprocessing into step array.
' I tried this before but I think I know what I did wrong then, I did not check if value was = current tick.
' Before I think I just checked if the map had a value or not written in, no only current tick values should parent a new cell.
' Finally found the frick'in bug!!! had cy instead of cx, OK let's see if can draw the path from Steps array.
' OK now prepStepMap in called in the Path routine and working.

'2018-08-19 PathFinder 4 add the needed Push Mode to compliment the Haul mode
'2018-08-23 PathFinder 4 DO PATHS USING ONLY VERTCAL AND HORIZONTAL MOVEMENT
' Also use _keydown for push / haul modes, forget having to toggle modes On and Off.
' Use left hand Ctrl+ Arrow to Haul and Shift + Arrow to Push, when both down, neither work.
' Extracted Hero from being emedded in the map.
' Draw the Hero tile on a second layer over the floor = spacer tile.
'

DEFINT A-Z
' Decided to use integer values with Descriptive CONST names, thanks Tempodibasic
' Using ALL CAP's for CONSTants, user defined TYPE, and SHARED variables,
' but short mnuemonic abrev because I hate typing long descriptions,
' so if you forget what the cap'd variable name means, check back to this main section.

CONST TRUE = -1
CONST FALSE = 0
CONST WW = 1060 ' Window Width  in pixels
CONST WH = 720 '  Window Height in pixels
CONST SQ = 30 '   Square Size in pixels
CONST MAPW = 30 ' MAP number of squares Wide or Width
CONST MAPH = 20 ' MAP number of squares High or Height

' MAP ITEMS
' CONST HERO = -1   no longer embedded in map
CONST BORDER = -2
CONST OBSTACLE = -3
CONST SPACER = 0
CONST DOOR = -4

'following Steve's example
TYPE COORDINATE
    X AS INTEGER
    Y AS INTEGER
END TYPE

SCREEN _NEWIMAGE(WW, WH, 32)
_SCREENMOVE _MIDDLE ' ah this is working better now! is it the 64X64 version?
RANDOMIZE TIMER

'The MAP:
'There will be a 1 SQ Border around the Map, the actual squares for game action run 1 to MAPW, 1 to MAPH
DIM SHARED MAP(MAPW + 2, MAPH + 2) AS INTEGER
DIM SHARED HC AS COORDINATE 'Hero Coordinate
DIM SHARED HDC AS COORDINATE 'Hero Destination Coordinate
DIM SHARED MX AS COORDINATE 'Map Exit, AKA Door

'This will be a map used for getting paths from HC = Hero Coordinate  to HDC = Hero Destination Coordinate
DIM SHARED STEPMAP(MAPW + 2, MAPH + 2) AS INTEGER
DIM SHARED STEPS(MAPH * MAPW) AS COORDINATE
DIM SHARED STEPSI AS INTEGER 'counts steps
DIM SHARED XOFF, YOFF, HAULMODE AS _BYTE, PUSHMODE AS _BYTE
DIM t AS COORDINATE
XOFF = (WW - SQ * (MAPW + 2)) / 2
YOFF = (WH - SQ * (MAPH + 2)) / 2
HAULMODE = 0: PUSHMODE = 0
DO

    'this part sets up a sample map, and here the door is created
    RandomMap .7
    'set door as first destination for pathing
    HDC.X = MX.X: HDC.Y = MX.Y

    'find free space to start hero
    DO
        testx = Rand(1, MAPW): testy = Rand(1, MAPH)
    LOOP UNTIL MAP(testx, testy) = SPACER
    HC.X = testx: HC.Y = testy

    'this part displays the ability to find a path to blue square anywhere in the maze

    _TITLE "ctrl+Arrow <haul, shift+Arrow >push, Click to set destination, n or blue door for new map, esc=quit"
    DO

        WHILE _MOUSEINPUT: WEND
        IF _MOUSEBUTTON(1) THEN
            xm = _MOUSEX - XOFF - .5 * SQ
            ym = _MOUSEY - YOFF - .5 * SQ
            t.X = xm / SQ: t.Y = ym / SQ
            IF t.X >= 1 AND t.X <= MAPW AND t.Y >= 1 AND t.Y <= MAPH THEN
                HDC.X = t.X: HDC.Y = t.Y
            END IF
        END IF
        KH& = _KEYHIT
        IF _KEYDOWN(100306) THEN HAULMODE = TRUE ELSE HAULMODE = fales 'ctrl
        IF _KEYDOWN(100304) THEN PUSHMODE = TRUE ELSE PUSHMODE = FALSE 'shft
        IF PUSHMODE AND HAULMODE THEN PUSHMODE = FALSE: HAULMODE = FALSE
        SELECT CASE KH&
            CASE 18432 'up
                IF MAP(HC.X, HC.Y - 1) = DOOR THEN EXIT DO
                IF MAP(HC.X, HC.Y - 1) = SPACER THEN
                    IF HAULMODE AND MAP(HC.X, HC.Y + 1) < -2 THEN
                        MAP(HC.X, HC.Y) = MAP(HC.X, HC.Y + 1)
                        MAP(HC.X, HC.Y + 1) = SPACER
                        HC.Y = HC.Y - 1
                    ELSEIF HAULMODE = FALSE AND PUSHMODE = FALSE THEN
                        HC.Y = HC.Y - 1
                    END IF
                ELSEIF MAP(HC.X, HC.Y - 2) = SPACER AND MAP(HC.X, HC.Y - 1) < -2 AND PUSHMODE THEN
                    MAP(HC.X, HC.Y - 2) = MAP(HC.X, HC.Y - 1)
                    MAP(HC.X, HC.Y - 1) = MAP(HC.X, HC.Y)
                    HC.Y = HC.Y - 1
                END IF

            CASE 20480 'down
                IF MAP(HC.X, HC.Y + 1) = DOOR THEN EXIT DO
                IF MAP(HC.X, HC.Y + 1) = SPACER THEN
                    IF HAULMODE = TRUE AND MAP(HC.X, HC.Y - 1) < -2 THEN
                        MAP(HC.X, HC.Y) = MAP(HC.X, HC.Y - 1)
                        MAP(HC.X, HC.Y - 1) = SPACER
                        HC.Y = HC.Y + 1
                    ELSEIF HAULMODE = FALSE AND PUSHMODE = FALSE THEN
                        HC.Y = HC.Y + 1
                    END IF
                ELSEIF MAP(HC.X, HC.Y + 2) = SPACER AND MAP(HC.X, HC.Y + 1) < -2 AND PUSHMODE THEN
                    MAP(HC.X, HC.Y + 2) = MAP(HC.X, HC.Y + 1)
                    MAP(HC.X, HC.Y + 1) = MAP(HC.X, HC.Y)
                    HC.Y = HC.Y + 1
                END IF

            CASE 19200 'left
                IF MAP(HC.X - 1, HC.Y) = DOOR THEN EXIT DO
                IF MAP(HC.X - 1, HC.Y) = SPACER THEN
                    IF HAULMODE AND MAP(HC.X + 1, HC.Y) < -2 THEN
                        MAP(HC.X, HC.Y) = MAP(HC.X + 1, HC.Y)
                        MAP(HC.X + 1, HC.Y) = SPACER
                        HC.X = HC.X - 1
                    ELSEIF HAULMODE = FALSE AND PUSHMODE = FALSE THEN
                        HC.X = HC.X - 1
                    END IF
                ELSEIF MAP(HC.X - 2, HC.Y) = SPACER AND MAP(HC.X - 1, HC.Y) < -2 AND PUSHMODE THEN
                    MAP(HC.X - 2, HC.Y) = MAP(HC.X - 1, HC.Y)
                    MAP(HC.X - 1, HC.Y) = SPACER
                    HC.X = HC.X - 1
                END IF

            CASE 19712 'right
                IF MAP(HC.X + 1, HC.Y) = DOOR THEN EXIT DO
                IF MAP(HC.X + 1, HC.Y) = SPACER THEN
                    IF HAULMODE = TRUE AND MAP(HC.X - 1, HC.Y) < -2 THEN
                        MAP(HC.X, HC.Y) = MAP(HC.X - 1, HC.Y)
                        MAP(HC.X - 1, HC.Y) = SPACER
                        HC.X = HC.X + 1
                    ELSEIF HAULMODE = FALSE AND PUSHMODE = FALSE THEN
                        HC.X = HC.X + 1
                    END IF
                ELSEIF MAP(HC.X + 2, HC.Y) = SPACER AND MAP(HC.X + 1, HC.Y) < -2 AND PUSHMODE THEN
                    MAP(HC.X + 2, HC.Y) = MAP(HC.X + 1, HC.Y)
                    MAP(HC.X + 1, HC.Y) = MAP(HC.X, HC.Y)
                    HC.X = HC.X + 1
                END IF

        END SELECT

        IF _KEYDOWN(27) THEN END
        IF _KEYDOWN(ASC("n")) THEN EXIT DO
        displayMap
        _LIMIT 100
    LOOP
LOOP UNTIL _KEYDOWN(27)

SUB path (ptB AS COORDINATE)
    'path is from hero (DIM SHARED HC as COORDINATE) to ptB (DIM SHARED HDC
    prepStepMap ptB
    dist = STEPMAP(HC.X, HC.Y) 'STEPMAP is DIM SHARED as INTEGER
    STEPSI = 0 'DIM SHARED, no path is signaled if still 0 after this sub
    IF dist = 0 THEN EXIT SUB
    cx = HC.X: cy = HC.Y
    'count dist down to destination
    WHILE dist >= 2
        cf = 0
        IF STEPMAP(cx, cy - 1) = dist - 1 THEN
            cf = 1: cy = cy - 1
        ELSEIF STEPMAP(cx - 1, cy) = dist - 1 THEN
            cf = 1: cx = cx - 1
        ELSEIF STEPMAP(cx + 1, cy) = dist - 1 THEN
            cf = 1: cx = cx + 1
        ELSEIF STEPMAP(cx, cy + 1) = dist - 1 THEN
            cf = 1: cy = cy + 1
        END IF
        IF cf = 0 THEN 'lost path, this should not happen until it is done
            EXIT SUB
        ELSE
            'add next step to steps array, set next search target
            STEPSI = STEPSI + 1: STEPS(STEPSI).X = cx: STEPS(STEPSI).Y = cy
            dist = dist - 1
        END IF
    WEND
END SUB

'this is Steve McNeil's method optimised to skip over needless or redundant checks
SUB prepStepMap (target AS COORDINATE)
    FOR y = 1 TO MAPH
        FOR x = 1 TO MAPW
            STEPMAP(x, y) = 0
        NEXT
    NEXT
    STEPMAP(target.X, target.Y) = 1: tick = 1: changes = 1
    'from an ever broadening area around destination find neighbor to step from
    WHILE changes
        t = tick: tick = tick + 1: changes = 0
        ystart = max(target.Y - tick, 1): ystop = min(target.Y + tick, MAPH)
        FOR y = ystart TO ystop
            xstart = max(target.X - tick, 1): xstop = min(target.X + tick, MAPW)
            FOR x = xstart TO xstop
                'check out the neighbors
                IF MAP(x, y) = SPACER THEN
                    IF STEPMAP(x, y) = 0 THEN
                        IF STEPMAP(x, y - 1) = t OR STEPMAP(x - 1, y) = t OR STEPMAP(x + 1, y) = t OR STEPMAP(x, y + 1) = t THEN
                            'there is a step close by to step from
                            STEPMAP(x, y) = tick: changes = 1 'next step
                        END IF
                    END IF 'stepmap = 0
                END IF 'map = spacer
            NEXT
        NEXT
    WEND
END SUB

SUB displayMap 'all drawing instructions conatined here
    'MAP is shared, 1 based with width =  mapw, height = maph that are constants
    DIM k AS LONG 'kolor tiles
    CLS
    FOR y = 0 TO MAPH + 1
        FOR x = 0 TO MAPW + 1
            SELECT CASE MAP(x, y)
                'CASE HERO: k = _RGB32(255, 255, 255)   'not embedded in map anymore
                CASE BORDER: k = _RGB32(255, 128, 64)
                CASE OBSTACLE: k = _RGB32(0, 128, 0)
                CASE SPACER: k = _RGB32(0, 0, 0)
                CASE DOOR: k = _RGB32(0, 0, 255)
            END SELECT
            LINE (x * SQ + XOFF, y * SQ + YOFF)-STEP(SQ - 2, SQ - 2), k, BF
        NEXT
    NEXT

    'loads steps array with steps from hero to destination from prepared stepmap array,
    'stepsI is number of steps to destination unless = 0
    path HDC
    IF STEPSI <> 0 THEN
        FOR s = 1 TO STEPSI
            LINE (STEPS(s).X * SQ + XOFF + 10, STEPS(s).Y * SQ + YOFF + 10)-STEP(SQ - 20, SQ - 20), _RGB32(255, 255, 0), BF
        NEXT
    END IF

    'draw target or destination
    FOR i = 6 TO 10 STEP 2
        LINE (HDC.X * SQ + XOFF + i - 1, HDC.Y * SQ + YOFF + i - 1)-STEP(SQ - 2 * i, SQ - 2 * i), _RGB32(255, 255, 255), BF
        LINE (HDC.X * SQ + XOFF + i, HDC.Y * SQ + YOFF + i)-STEP(SQ - 2 * i - 2, SQ - 2 * i - 2), _RGB32(255, 0, 0), BF
    NEXT

    'draw hero
    LINE (HC.X * SQ + XOFF + 1, HC.Y * SQ + YOFF + 1)-STEP(SQ - 4, SQ - 4), _RGB32(255, 255, 255), BF

    'special keys = powers
    LOCATE 1, 90: PRINT SPACE$(30)
    IF PUSHMODE THEN LOCATE 1, 90: PRINT "Push Mode is ON."
    IF HAULMODE THEN LOCATE 1, 90: PRINT "Haul Mode is ON."
    _DISPLAY
END SUB

'someone might start with this a build a map or levels editor!
'load a shared MAP(1 to mapw, 1 to maph)
SUB RandomMap (obstacleDensity!) ' obstacleDensity! = fraction of map squares to make obstacles
    'MAP is shared, 1 based with width =  mapw, height = maph that are constants
    'clear last map
    FOR y = 1 TO MAPH
        FOR x = 1 TO MAPW
            MAP(x, y) = SPACER
        NEXT
    NEXT

    'borders
    FOR x = 0 TO MAPW + 1
        MAP(x, 0) = BORDER
        MAP(x, MAPH + 1) = BORDER
    NEXT
    FOR y = 0 TO MAPH + 1
        MAP(0, y) = BORDER
        MAP(MAPW + 1, y) = BORDER
    NEXT

    'convert this part into walls, buildings, jewels, potions...
    'with these obstacles there is no guarantee a path will exist
    FOR I = 1 TO MAPW * MAPH * obstacleDensity!
        ox = Rand(1, MAPW): oy = Rand(1, MAPH)
        MAP(ox, oy) = OBSTACLE
    NEXT

    'door, exit to next map near a border?
    wall = Rand(1, 4)
    SELECT CASE wall
        CASE 1: MX.X = 1: MX.Y = Rand(2, MAPH - 2)
        CASE 2: MX.X = MAPW: MX.Y = Rand(2, MAPH - 2)
        CASE 3: MX.X = Rand(2, MAPW - 2): MX.Y = 1
        CASE 4: MX.X = Rand(2, MAPW - 2): MX.Y = MAPH
    END SELECT
    MAP(MX.X, MX.Y) = DOOR
END SUB

'handy functions
FUNCTION Rand% (lo%, hi%)
    Rand% = INT(RND * (hi% - lo% + 1)) + lo%
END FUNCTION
FUNCTION min (n1, n2)
    IF n1 > n2 THEN min = n2 ELSE min = n1
END FUNCTION
FUNCTION max (n1, n2)
    IF n1 < n2 THEN max = n2 ELSE max = n1
END FUNCTION

« Last Edit: August 24, 2018, 04:04:26 PM by B+ »