AdvSys2 Development - RIP

David BetzDavid Betz Posts: 12,724
I've decided to move the discussion of AdvSys2 for the WordFire platform to a new thread to avoid cluttering up Jim's thread with my implementation issues. I'm still having trouble getting text to display on all four screens. I've verified that I haven't changed anything in vga.driver.spin and I've only added functions to vga.spin to support my scrolling text based game. I would attach my code but I'm not sure I'm allowed to given the licensing of the code for WordFire but I'll include a small piece since it is pretty much the same code that Jim already posted in the other thread. Here is what I'm doing to try to display text on all four screens:
  'configure video pins
  lock := ((cnt >> 16 + 2) | 1) << 16   
  repeat s from 0 to 3 
    vga[s].init(lock|vconfig[s])                'initialize vid drv with vgrp/vpins  
    waitf(4) 'let voltage to screens stablize?
  
  ' write something on each screen
  repeat s from 0 to 3
    write(s, 10, 3, WB, string("Hello, world!"))
The text appears fine on screen 0 but it is shifted on screens 1-3. Also, I don't understand the purpose of the reference to cnt.
«13

Comments

  • 71 Comments sorted by Date Added Votes
  • JRetSapDoogJRetSapDoog Posts: 771
    edited August 30 Vote Up0Vote Down
    David Betz wrote: »
    The text appears fine on screen 0 but it is shifted on screens 1-3. Also, I don't understand the purpose of the reference to cnt.
    Okay, the fact that the text is shifted on the other screens seems to suggest that those screens aren't properly synchronized to share the hsync and vsync lines that screen 0 outputs. The VGA chips on the driver boards are doing the best that they can to try to make sense of the received signals and managed to put up a stable image, but it is shifted due to the color signals not being synchronized quite right with the hsync and/or vsync signals. So that may imply that the vga instances are not being created properly. But exactly why, we'll have to delve into. I've never had that happen before with any of my games using this driver, so I'm almost certain it is solvable. I just got online, though, and need to think about it.

    Okay, I took a look, and for that repeat loop for locking the screens together in sync, you don't want that waitf(4) line in that loop. It almost for sure is preventing kuroneko's driver from establishing sync lock of the instances properly. You only want the vga[ s ].init() line in that loop, nothing more. That is, it should be a tight/fast loop, so don't consolidate those two repeat loops from my example just because it appears that they could work as one loop (and be more compact). Make them back-to-back. Although I sometimes use separate repeat loops just to make the code clearer to read (even though it's less compact/efficient), in this case, the loops must be separate, as the first one has to be fast.

    Meanwhile, in the second repeat loop that I've suggested for the waitf() line, I'd probably put the following two lines before it. Again, the second repeat loop gets the waitf(4) line, if it's even necessary (I think I put it in just to be safe, and it only delays things by a second in total).
      repeat s from 0 to 3  
        vga[s].str(string($1B, "s"))              'page mode
        vga[s].setn(1, constant(NEGX|$8000))      'use the ROM font for chars 0..31
        waitf(4) 'let voltage to screens stablize? 
    
    I'm assuming that you don't want to redefine any font characters at this time.
  • I'll try taking the waitF out of the init loop when I get home later tonight. That's probably what's causing my problems. Thanks!
  • JRetSapDoogJRetSapDoog Posts: 771
    edited August 30 Vote Up0Vote Down
    No problem. Hope it works. I think that it should. By the way, I edited my post above a couple times shortly after posting it, so you might want to read it again to see the latest and hopefully greatest.
  • I read it. I don't think I need page mode since I actually want to scroll. In fact, I'm not really using any of your text functions. I've added a bunch of my own. I will post my added functions later but won't post my entire vga.spin module because of license issues.
  • You were right. I took out the waitF call and all of the screens are now working. Thanks for the advice!
  • Great! You're off to the races now. Handling keyboard input is straightforward.
  • David BetzDavid Betz Posts: 12,724
    edited August 31 Vote Up0Vote Down
    I got my trivial game working on all four screens at the same time. Here is the code. It isn't a very interesting game as the players don't interact with each other. That will be my next step. You'll notice that the screen() function switches between screens. The game engine just cycles through all four screens looking for player commands and obeying them.

    All of the code is checked into GitHub except the video and keyboard drivers which are not open source but you get the source if you purchase the WordFire console.

    Game code:
    var initialLocation = livingroom;
    
    include "game.adi";
    
    actor northActor {
    name:	"North";
    index:	0;
    loc:	livingroom;
    }
    
    actor westActor {
    name:	"West";
    index:	1;
    loc:	closet;
    }
    
    actor southActor {
    name:	"South";
    index:	2;
    loc:	pantry;
    }
    
    actor eastActor {
    name:	"East";
    index:	3;
    loc:	kitchen;
    }
    
    location storage_room {
    description:    "You are in the storage room.";
    west:           hallway;
    }
    
    location hallway {
    description:    "You are in the hallway.";
    east:           storage_room;
    north:          kitchen;
    south:          livingroom;
    }
    
    location kitchen {
    description:    "You are in the kitchen.";
    south:          hallway;
    west:           pantry;
    }
    
    location pantry {
    description:    "You are in the pantry.";
    east:           kitchen;
    }
    
    location livingroom {
    description:    "You are in the livingroom.";
    north:          hallway;
    west:           closet;
    south:          outside;
    }
    
    location closet {
    description:    "You are in the closet.";
    east:           livingroom;
    }
    
    location outside {
    description:    "You are outside.";
    north:          livingroom;
    }
    

    Game runtime code:
    object location {
    }
    
    object actor {
    }
    
    property north, south, east, west;
    property loc, description;
    
    def getp(obj, prop)
    {
        var value;
        try {
            value = obj.(prop);
        }
        catch (e) {
            value = 0;
        }
        return value;
    }
    
    def getc()
    {
        asm {
            TRAP 0
    	    RETURN
        }
    }
    
    def screen(n)
    {
        asm {
          LADDR 0
          LOAD
          TRAP 7
        }
    }
    
    var actors[] = { northActor, westActor, southActor, eastActor };
    
    def main()
    {
        var curloc, newloc, ch, s;
        
        for (s = 0; s < 4; ++s) {
            screen(s);
            print #actors[s].loc.description;
        }
        
        while (1) {
            for (s = 0; s < 4; ++s) {
                screen(s);
                curloc = actors[s].loc;
                if ((ch = getc()) != 0 && ch != '\n') {
                    if (ch == 'n')
                        newloc = getp(curloc, north);
                    else if (ch == 's')
                        newloc = getp(curloc, south);
                    else if (ch == 'e')
                        newloc = getp(curloc, east);
                    else if (ch == 'w')
                        newloc = getp(curloc, west);
                    else if (ch == 'l') {
                        print #curloc.description;
                        newloc = curloc;
                    }
                    else
                        newloc = -1;
                
                    if (newloc == -1)
                        print "Huh?";
                    else if (newloc == 0)
                        print "You can't go that way.";
                    else if (newloc != curloc) {
                        actors[s].loc = newloc;
                        print #newloc.description;
                    }
                }
            }
        }
    }
    
  • David BetzDavid Betz Posts: 12,724
    edited September 1 Vote Up0Vote Down
    Here is a binary of the "game". The game has a simple world map. Each screen is a different actor and each starts in a different location in the map. You can type 'n' to move "north", 's' to move "south", 'e' to move "east", 'w' to move "west", and 'l' to "look". Each keyboard controls a different actor.
  • JRetSapDoogJRetSapDoog Posts: 771
    edited September 1 Vote Up0Vote Down
    I loaded up the binary and explored the house. With the north keyboard, I checked out the living room, closet, hallway, storage room, kitchen and pantry, as well as the outdoors (the front yard apparently has a fence around it). Tried a couple other keyboards, too, and the navigation worked. I see that players start in different rooms. I slew the dragon in the closet and let the cat out of the pantry. Just kidding. You're off to a great start in very little time. But remember, it's a marathon, not the 100-meter dash. No, don't mind me: knock yourself out.
  • Thanks for trying the binary. As I've mentioned, the sources are in my GitHub repository for anyone interested. I'm trying to figure out if this could be used as a general purpose programming language for the Propeller. I'm not sure what advantages it might have over the other languages that are already available but it is a complete language with only minimal additions to support adventure games.
  • Here is an update to my simple game for the WordFire console. This version adds a few extras based on previous play testing and also makes each player aware of what the other players are doing. As usual, the sources are all in GitHub.

    This is about as far as I can go until I resolve a major omission in my new adventure writing system. The two main components of an adventure game are the game map and the command parser. I've implemented the game map but I only allow single character commands. To go any further I have to write a parser. The parser for my old system was written in C and I had to remove it when I implemented the interpreter in PASM on the Propeller. My plan is to write a new parser in the adventure language itself. That's my next project and it will likely take a while to complete.
  • FYI, I just pushed a small change to move to Kuroneko's open source video driver. The keyboard driver remains closed source though. I'll work on replacing that at some point.
  • AdvSys2 ain't just for adventure games or the WordFire anymore! AdvSys2 (which should probably have a better name) is a general purpose programming language that has some features that are useful for writing text adventure games. However, it can be used for other things as well. In fact, to prove it, here is a program that will run on the ActivityBoard. It's the standard "Hello, world!" of embedded systems. It blinks LEDs.
    def main()
    {
        while (1) {
            pinOn(26);
            pinOff(27);
            waitcnt(40000000);
            pinOff(26);
            pinOn(27);
            waitcnt(40000000);
        }
    }
    

    However, pinOn, pinOff, and waitcnt are not native to AdvSys2. They are defined as follows. The "asm" statement probably requires a bit of explanation. What appears in the "asm" statement are AdvSys2 byte code opcodes. The "LADDR 0" instruction loads the address of the first local variable onto the stack. The first local variable happens to be the first function parameter, in this case, "n". The "LOAD" instruction just loads a long from the address on the top of the stack. Then the "NATIVE" instructions execute native Propeller instructions. This is how AdvSys2 code gets at the Propeller hardware. The "RETURN" instruction simply returns from the function.
    def waitcnt(n)
    {
        asm {
            LADDR 0
            LOAD
            NATIVE mov      t1, cnt
            NATIVE add      t1, tos
            NATIVE waitcnt  t1, #0
            RETURN
        }
    }
    
    def pinOn(pin)
    {
        asm {
            LADDR 0
            LOAD
            NATIVE mov      t1, #1
            NATIVE shl      t1, tos
            NATIVE or       dira, t1
            NATIVE or       outa, t1
            RETURN
        }
    }
    
    def pinOff(pin)
    {
        asm {
            LADDR 0
            LOAD
            NATIVE mov      t1, #1
            NATIVE shl      t1, tos
            NATIVE or       dira, t1
            NATIVE andn     outa, t1
            RETURN
        }
    }
    

    I don't pretend that AdvSys2 will become a popular programming language for the Propeller but it is another entry in the long list of languages you have to choose from.
  • In case you want proof that this generates a working Propeller binary, here it is. I wanted to attach it to the previous message but the forum software doesn't seem to allow me to add an attachment when I edit a message.
  • David BetzDavid Betz Posts: 12,724
    edited September 14 Vote Up0Vote Down
    After a long journey down a path that I may have to revisit next, I finally realized that the solution to my nested array problem was easier than I though. Nested arrays are needed for my adventure system parser to handle lists of word phrases.

    Here is some sample code:
    var myArray[] = { 1, 2, 3, 4, 5 };
    
    var myArray2[] = { [1, 2], [3, 4, 5], [6] };
    
    def main()
    {
        var i, j, k;
        
        println myArray2[0][-1];
        
        for (i = 0; i < myArray[-1]; ++i)
            println myArray[i];
            
        for (j = 0; j < myArray2[-1]; ++j) {
            println "size=", myArray[j][-1];
            for (i = 0; i < myArray2[j][-1]; ++i)
                print myArray2[j][i], " ";
            println;
        }
    }
    

    And here is the output that it generates when you run it:
    2
    1
    2
    3
    4
    5
    size=2
    1 2 
    size=3
    3 4 5 
    size=1
    6 
    
    It's not very exciting but it proves that nested arrays work. The compiler puts the size of the array at offset -1. That is the reason for the references to myArray[-1].
  • Looking pretty cool, David! How do you compile the binary? Do you generate a .spin file and then run it through a Spin compiler?

  • That's an interesting question. At present it's kind of a kludge. My compiler writes out a binary image that is included in a Spin program using the FILE statement. I'd like to have the compiler directly generate the .binary file but I'm not quite sure how to do that yet.
  • How do you handle NATIVE instructions? Do you have a PASM assembler built in to your compiler?

    The binary file format is really simple (as I'm sure you know). I guess your problem is that you need to include your interpreter code (written in Spin/PASM) with the bytecode binary? For my Risc-V interpreter I solved this by pre-compiling the interpreter and padding it out to a fixed boundary (say 4K, I can't remember now what it was exactly). The interpreter always assumed the code started after the boundary. Then producing the binary was just a matter of concatenating the interpreter binary and bytecode binary, then fixing up the checksum.
  • You would need to ensure that the padding space contains enough room to handle the stack space you'll need plus any VAR space that you might have. I'm assuming your Spin program just does a coginit to start up your interpreter, so the stack space would just need to be a few longs.
  • ersmith wrote: »
    How do you handle NATIVE instructions? Do you have a PASM assembler built in to your compiler?

    The binary file format is really simple (as I'm sure you know). I guess your problem is that you need to include your interpreter code (written in Spin/PASM) with the bytecode binary? For my Risc-V interpreter I solved this by pre-compiling the interpreter and padding it out to a fixed boundary (say 4K, I can't remember now what it was exactly). The interpreter always assumed the code started after the boundary. Then producing the binary was just a matter of concatenating the interpreter binary and bytecode binary, then fixing up the checksum.
    I have a simple PASM assembler built into the compiler.

  • David BetzDavid Betz Posts: 12,724
    edited September 14 Vote Up0Vote Down
    Dave Hein wrote: »
    You would need to ensure that the padding space contains enough room to handle the stack space you'll need plus any VAR space that you might have. I'm assuming your Spin program just does a coginit to start up your interpreter, so the stack space would just need to be a few longs.
    The VM itself is just PASM but the runtime code that handles serial I/O and single stepping is Spin.
  • OK, then you'll need to ensure that you add enough padding to handle the stack and VAR space if you use Eric's method. Or you could concatenate the compiled AdvSys2 code after the Spin binary, and then adjust the DBASE, DCURR and VBASE values in the Spin header. Each of these three values would be increased by the size of the AdvSys2 code. You would also need to adjust the checksum in the header.
  • I have one further language design issue to resolve. I want to be able to address byte arrays since that will be needed for parsing text. Currently, the compiler assumes everything is a 32 bit integer. There are no type declarations. If you do something like this:
    var myArray[10];
    
    def main() {
       myArray[3] = 1;
    }
    
    The compiler assumes myArray is a data address and it adds 3*4 to it to get the address of the target 32 bit value. In order to do byte addressing instead of long addressing I guess I need some kind of type casting. I could use the Spin approach and do something like this:
    byte[myArray][3]
    
    However, that looks like double indexing which I already support for accessing nested arrays so there would be ambiguity. I'm thinking of something like this:
    myArray.byte[3]
    
    Does that seem like a good syntax for this? Any other suggestions?
  • that is actual the spin syntax to access bytes out of longs, works also with .word[1].

    but it would restrict youi byte-arrays to start at a long address and be 4 byte boundary long.

    wasting space for each string used.

    having every var being 32 bit might not be useful on a MC, so maybe add types for byte and word?

    Mike
    I am just another Code Monkey.
    A determined coder can write COBOL programs in any language. -- Author unknown.
    Press any key to continue, any other key to quit

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • msrobots wrote: »
    that is actual the spin syntax to access bytes out of longs, works also with .word[1].

    but it would restrict youi byte-arrays to start at a long address and be 4 byte boundary long.

    wasting space for each string used.

    having every var being 32 bit might not be useful on a MC, so maybe add types for byte and word?

    Mike
    Yes, I know that my first suggestion matches what Spin does. I'm just not sure I like it as well as my second suggestion. I also realize that it would be nice to offer different data sizes but I'm not sure I'm going to do that with this language as it would require types and I've been trying to avoid those. My strings are not necessarily 32 bit aligned. At least that is true of the literal strings. I haven't implemented a way to declare string arrays yet.
  • msrobotsmsrobots Posts: 2,089
    edited September 15 Vote Up0Vote Down
    well I think
    myArray.byte[321]
    
    or
    myArray.word[12]
    

    could work with any Array using the start address of the array, but getting to the index number needed one has to skip your size field on nested Arrays, seems complicated.
    myArray[2][5].byte[0..3]
    

    would allow access to each byte per index but just for one long. How about
    myArray[2].byte[0..(Size of nestedArray *4)-1]
    

    that would take the size of the one dimensional array stored at index-1 as bound check.

    But then byteArrays aka strings start at a long address and are long so wasting up to 3 bytes.

    a optimizing compiler could use those spare bytes for something :smile:

    just some thoughts

    Mike
    I am just another Code Monkey.
    A determined coder can write COBOL programs in any language. -- Author unknown.
    Press any key to continue, any other key to quit

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • Maybe I didn't describe very well what I meant by "nested arrays". In fact, they aren't really interwoven like a two-dimensional array would be. Each element of the parent array contains the address of the "nested array". The nested array subscripts start at zero just like the parent array and its size is at offset -1. There is no need to skip over the offsets.
  • msrobotsmsrobots Posts: 2,089
    edited September 15 Vote Up0Vote Down
    well then your array is not continuous in memory and accessing bytes over array boundaries is not possible. OK. Then
    var myArray[] = { [1, 2], [3, 4, 5], [6] };
      myArray[2].byte[0] ' 1. byte of 6
      myArray[2].byte[1] ' 2. byte of 6
      myArray[2].byte[2] ' 3. byte of 6
      myArray[2].byte[3] ' 4. byte of 6
    BUT
      myArray[1].byte[0] ' 1. byte of 3
      myArray[1].byte[1] ' 2. byte of 3
      myArray[1].byte[2] ' 3. byte of 3
      myArray[1].byte[3] ' 4. byte of 3
      myArray[1].byte[4] ' 1. byte of 4
      myArray[1].byte[5] ' 2. byte of 4
      myArray[1].byte[6] ' 3. byte of 4
      myArray[1].byte[7] ' 4. byte of 4
      myArray[1].byte[8] ' 1. byte of 5
      myArray[1].byte[9] ' 2. byte of 5
      myArray[1].byte[10] ' 3. byte of 5
      myArray[1].byte[11] ' 4. byte of 5
    

    is possible and useful


    Mike
    I am just another Code Monkey.
    A determined coder can write COBOL programs in any language. -- Author unknown.
    Press any key to continue, any other key to quit

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • msrobots wrote: »
    well then your array is not continuous in memory and accessing bytes over array boundaries is not possible. OK. Then
    var myArray[] = { [1, 2], [3, 4, 5], [6] };
      myArray[2].byte[0] ' 1. byte of 6
      myArray[2].byte[1] ' 2. byte of 6
      myArray[2].byte[2] ' 3. byte of 6
      myArray[2].byte[3] ' 4. byte of 6
    BUT
      myArray[1].byte[0] ' 1. byte of 3
      myArray[1].byte[1] ' 2. byte of 3
      myArray[1].byte[2] ' 3. byte of 3
      myArray[1].byte[3] ' 4. byte of 3
      myArray[1].byte[4] ' 1. byte of 4
      myArray[1].byte[5] ' 2. byte of 4
      myArray[1].byte[6] ' 3. byte of 4
      myArray[1].byte[7] ' 4. byte of 4
      myArray[1].byte[8] ' 1. byte of 5
      myArray[1].byte[9] ' 2. byte of 5
      myArray[1].byte[10] ' 3. byte of 5
      myArray[1].byte[11] ' 4. byte of 5
    

    is possible and useful


    Mike
    Yes, that is the way it works currently, myArray is an array of three pointers to other arrays.

  • David BetzDavid Betz Posts: 12,724
    edited September 15 Vote Up0Vote Down
    Anyway, I think I may have to do as you suggest and add some types:
    var
      long myLongArray[10];
      byte myByteArray[20];
    
    Maybe I could also accept this and default the type to long:
    var
      myLongArray[10];
      byte myByteArray[20];
    
Sign In or Register to comment.