Jump to content
IGNORED

50 Years of BASIC - TIME


Mr SQL

Recommended Posts

In the sample programming exercise in Chapter 3 the author explains the line numbers are for the programmers convenience, and that Edtasm+ can edit, delete and renumber lines just like you do in BASIC.

 

OK, that's a special interactive editing mode of the Edtasm+ to allow you to edit lines directly. Those are not addresses, they are the same as BASIC line numbers. However, in BASIC, those are part of the program, in Edtasm+ they appear to be just an artefact of the input editor. As soon as you assemble, they are gone, and you deal directly with the assembled addresses.

 

There is still a one to one relation between line numbers (or text symbols) in BASIC and memory addresses even though the statements can compile into multiple opcodes that each have an address; those statements memory addresses are not visible to the BASIC programmer, only the linenumber lookups are and those always have a 1:1 correlation to the address.

 

I guess we have different definitions of what 1:1 means. If one BASIC statement represented by a single line number assembles to many op-codes, each on its own individual consecutive memory location, then there would be many addresses corresponding to one BASIC line number. *shrug*

 

While Edtasm organizes with line numbers like BASIC, it lacks another set of lookups that would have enabled it to use the line numbers as symbols - you can only jump to labels.

 

Because it is only an artefact for editing during initial input. It does not keep them as part of the program like in BASIC. In that way, it is like any other assembler. The line numbers are just for editing during the original input.

 

 

From your description of IntyBASIC I think we're down to semantics again because Flashback BASIC and Visual bB work similarly, they just wrap everything so that you click the play button and the IDE builds a binary.

 

If the IntyBASIC Assembly output that's ready for the AS1600 to turn into a binary contains both the BASIC program and the IntyBASIC runtime I would consider the runtime bound to the program, how else would you describe it? From my perspective you described a linker too.

 

What you call the IntyBASIC "runtime," is just included like any other library module. IntyBASIC does absolutely nothing to "bind" it except add something like

   INCLUDE "inty_prologue.asm"

Or inject code inline. Is this "binding"?

 

 

That's some weird way to call it. If my program is composed of multiple library modules, and I include them together to compile them into a single program, are you saying that each module is "bound" to the program? Because I would just say that they are "included" or compiled together.

 

 

 

OK, then. *shrug*

 

In any case, this "binding" is performed by the Assembler, not by IntyBASIC.

 

With interpreted BASIC, the runtime is also bound alongside the interpreter. Semantically the programmer might have the runtime linked in another file when assembling the language instead of inline, but it's necessarily bound together with the interpreter when the binary is created.

 

I don't really get this distinction of "the runtime is also bound alongside the interpreter" for programs which are not pre-compiled... Interpreted BASIC programs exist merely as tokenized text strings in memory and are never compiled, linked, nor bound in any special way. As the run-time (the real "run-time" environment, which is actually running in the background) executes a statement, it interprets the tokenize string, parsing it and identifying what command and parameters are in it, then it calls specialized routines to perform the work accordingly.

 

It seems you are talking about the way some languages like Visual Basic contain a runtime library which must then be linked with your object code after compilation. This "runtime" was always pointed out as a special thing because it came already pre-compiled into object code. Nobody talks about "binding" the Standard C Library to their code when compiling a C program which includes it because it's just a source code module, just like any other, except written by someone else.

 

Those are two different processes, one is just normal library inclusion during compilation (i.e., just like program source code normally works), and the other is the linkage of an existing library in object code. If it is not pre-compiled into object code then there is no need to treat it as a special case, it's just your normal "compile and link" cycle.

 

IntyBASIC (and most 8-bit compilers) doesn't even have a linker, it doesn't even compile into object code and includes no pre-compiled libraries, just assembly language routines which are then included in your final assembly language program.

 

If you want to call that "binding" then go ahead, but it's not really a term of art in the industry.

 

-dZ.

Link to comment
Share on other sites

@Mr SQL

I don't want to argue with you over this. It's silly. Why don't you address my original points (which I re-iterated in my last post) if you disagree with them or if you have something else to add, rather than attacking the minutiae. I don't think many people in this thread care about an ancient, idiosyncratic assembler with BASIC-like qualities or object code linking. (At least it's not on topic.)

 

I just asked you about what you meant by "binding" because you mentioned some work you did and I was interested to find out more about your compiler project -- not because I wanted to argue over the semantics of source code transformations. If you want to talk about your project, I'd be interested in reading it. Otherwise, if you just want to break apart another one of my response sentences, I'm out of this topic again.

 

-dZ.

Link to comment
Share on other sites

Now, here's something that you and the others will find interesting: it's the original source code of Micro-Soft's Altair BASIC for 8080, annotated heavily to explain how it works.

More than a historical curiosity, it provides a deep insight into how to an entire programming environment can be constructed on a resource-constrained system. It also explains some very curious quirks of the language and the interpreter (such as why removing all whitespace reduces the program size, or why "<=" was the same as "=<," etc.).

 

I would recommend reading through it to anybody who's interested in BASIC and ever wondered how it actually worked. It's really quite simple.

 

-dZ.

  • Like 1
Link to comment
Share on other sites

Now, here's something that you and the others will find interesting: it's the original source code of Micro-Soft's Altair BASIC for 8080, annotated heavily to explain how it works.

More than a historical curiosity, it provides a deep insight into how to an entire programming environment can be constructed on a resource-constrained system. It also explains some very curious quirks of the language and the interpreter (such as why removing all whitespace reduces the program size, or why "<=" was the same as "=<," etc.).

 

I would recommend reading through it to anybody who's interested in BASIC and ever wondered how it actually worked. It's really quite simple.

 

-dZ.

 

Awesome BASIC analysis! :) I've been reading through the comments, it's really well documented.

 

 

@Mr SQL

I don't want to argue with you over this. It's silly. Why don't you address my original points (which I re-iterated in my last post) if you disagree with them or if you have something else to add, rather than attacking the minutiae. I don't think many people in this thread care about an ancient, idiosyncratic assembler with BASIC-like qualities or object code linking. (At least it's not on topic.)

 

I just asked you about what you meant by "binding" because you mentioned some work you did and I was interested to find out more about your compiler project -- not because I wanted to argue over the semantics of source code transformations. If you want to talk about your project, I'd be interested in reading it. Otherwise, if you just want to break apart another one of my response sentences, I'm out of this topic again.

 

-dZ.

 

 

I had fun programming with that ancient Assembler, the line numbers made it feel more like BASIC but also made me wish I could use them as symbols.

 

I was influenced by those features when designing Flashback BASIC.

 

Tiny BASIC fit the interpreter and the runtime into 2K which was also inspiring, I eventually fit a soft blitter chip into a 2K runtime.

 

Atari Flashback BASIC is nearly 100% cross compatible with SuperCharger BASIC but has 5K free to BASIC instead of 4K, SuperCharger SpaceInvaders ran on both, but stopped running on the SuperCharger at BETA4 because it needed 5K.

 

I enjoy talking about any aspects of BASIC or my project but I tend to use contemporary terminology and expansive definitions; I don't distinguish between Assembler and Machine Language (unless macro's are used, those are high level), they seem essentially the same to me though the Assembler is a much better tool than a lookup table of opcode mnemonics.

 

If a compiler outputs Assembly, that's still machine language just not in binary format. Ditto for a Monitor outputting the hex values for 8 bytes per row; I consider that still machine language too, just not in binary format.

 

What's your perspective?

Link to comment
Share on other sites

I had fun programming with that ancient Assembler, the line numbers made it feel more like BASIC but also made me wish I could use them as symbols.

 

Yeah, it does look like BASIC, and I can see how that could be useful. However, I also see it as a "mixed metaphor," which is (I think) why the developers of Edtasm+ didn't follow through fully with the abstraction. Once the source is assembled, you get a "listing" file, which is an actual conventional artefact of assemblers. There's no place for line numbers at that point because you get actual absolute addresses.

 

At that point, what do you do with the line numbers? Keeping them around in a linked-list to be visible to the debugger would have taken additional resources that may not have been available.

 

Treating them as labels would have been "weird" also. Think about it: the environment used "100" as a label, now that is a label you cannot use. However, you can use "1000" because the program isn't that big and didn't have a line number that high. So everything works well until the day that your reach line #1000 in your source code, and now you have a duplicate definition of a label.

 

Alternatively, the manual could say "you can make use any symbolic label you want -- except numbers!" but that wouldn't be much fun at all. ;)

 

It wouldn't be impossible, and it probably wouldn't be too bad to keep the line numbers and follow through as full system-wide symbols (visible to the editor, debugger, etc.) However, I imagine it may have been too cumbersome or resource intensive to do at the time.

 

 

I was influenced by those features when designing Flashback BASIC.

 

Tiny BASIC fit the interpreter and the runtime into 2K which was also inspiring, I eventually fit a soft blitter chip into a 2K runtime.

 

Atari Flashback BASIC is nearly 100% cross compatible with SuperCharger BASIC but has 5K free to BASIC instead of 4K, SuperCharger SpaceInvaders ran on both, but stopped running on the SuperCharger at BETA4 because it needed 5K.

 

I'd like to hear more about Atari Flashback BASIC and its design.

 

 

I enjoy talking about any aspects of BASIC or my project but I tend to use contemporary terminology and expansive definitions...

 

 

As has been apparent by my previous posts, I moved on from BASIC back in the mid-to-late 1980s. Later on, in between system-level programming in hard-core languages such as C and Assembly, I also got stuck doing a stint on Visual Basic for a few years; then as a web developer on ASP (VBScript) for a couple more; and later on maintaining other's people's VB.Net code.

 

I learned to despise BASIC, especially VBScript, with the burning passion of a thousand suns. Being exposed (and proficient) in at least 1/2 a dozen other languages, it strikes me as such a broken language for anything more than just toy programming...

 

... Then again, I'm quite good at it. I excel at it, which is why I kept getting pulled into VB/VB.Net/VBScript projects by my employers, and among all the other things I had to do, I had to be the "VB Guy." I firmly believe that the strong command of the BASIC and its variations I have is due to my exposure to other languages, and to proper software engineering techniques -- and that they came in spite of doing BASIC itself.

 

Even today, having changed carriers to move out completely from software engineering and becoming a Data Architect for a major bank, someone discovered that our data modelling tool, SAP PowerDesigner, has a very expansive and accessible meta-model that can be extended at all levels for deep customizations and automation -- all running on (guess what???) VBScript! And once again, amidst the many other responsibilities I have is the programming of custom automation extensions for PowerDesigner in VBScript -- my nemesis. :mad:

 

So yeah, I'm a bit jaded because over the years I've seen soooooooo much crap code made by VB "programmers" that looks close to the stuff I made when I was a little kid. Having a deep understanding of the language semantics and its underlying implementation lets me see why this is so, and over and over I come to the same conclusion: the language just doesn't promote good practices. It's not that it's impossible to be a good VB programmer, it's that it is sooooo easy to be crap at it and get a good job -- and back in the day before Java (don't start me on Java either!) took over the corporate world, EVERY. SINGLE. BUSINESS. APPLICATION. was written in VB by an idiot! (I sometimes thought it was the same idiot just following me around from employer to employer writing the same crappy spaghetti VB code.)

 

I know the above sounds like an angry rant, but trust me, I'm writing this with my tongue firmly sticking to my cheek. It's sort of like a love-hate relationship what I have with BASIC: I hate it, but our fates appear to be intertwined and I can't seem to escape its grasp. I have a deep appreciation for work done to facilitate access to programming to newbies, such as IntyBASIC, bAtari BASIC, or your own Flashback BASIC.

 

For my personal projects, however, I stay away from it like the plague. ;)

 

I don't distinguish between Assembler and Machine Language (unless macro's are used, those are high level), they seem essentially the same to me though the Assembler is a much better tool than a lookup table of opcode mnemonics.

 

If a compiler outputs Assembly, that's still machine language just not in binary format. Ditto for a Monitor outputting the hex values for 8 bytes per row; I consider that still machine language too, just not in binary format.

 

What's your perspective?

 

I think that's a common misconception amongst many, although your comment above is more of a rational personal view. I think you should consider the differences between Assembly and Machine Language -- only in doing so can you appreciate what a big step Assembly Language was in the computer field.

 

In Machine Language, you must enter the machine code of each individual operation and its operands in the proper encoding order expected by the machine. For instance, something as simple as "add one to register R0" is not as simple as entering the "add" op-code and the register argument, but setting the proper bits of the op-code to select R0. In other words, there is a distinct op-code for "add one to register R0" and another for "add one to register R1," etc.

 

You also need to be aware of the size of each operation. Some operations encode into one byte or 16-bit word, others into two, or perhaps even three or four. As you enter these operations, you need to keep track of how many words have been used and increment the absolute address accordingly -- always keeping in mind that the CPU will do so one at a time, so if you by chance leave a gap, it'll probably crash it.

 

Moreover, depending on how the micro-code was encoded, some op-codes reserve different bits for different types of arguments, so it is not as straightforward as it may seem. Of course, this is all tightly-coupled to the CPU architecture, but each one has its own idiosyncrasies.

 

In contrast, Assembly Language abstracts all this by providing automatic encoding of a more simpler mnemonic syntax into its corresponding machine language. It is much more than the mnemonics -- it's a higher level cognitive model of the program. Assembly Language groups related operations into "families," such as unconditional and conditional branches, arithmetic and logic, and perhaps even register operations vs. memory operations. For each of these groups, it provides a consistent and intuitive (albeit low-level) interface so that you can think at a higher level of abstraction.

 

In Assembly Language adding one to a register may be as simple as just selecting the "add" instruction and the appropriate register as its argument. The assembler will then translate and encode the entire operation into the appropriate machine codes. It will take care of computing the size of the full operation and incrementing its internal address counter, mapping and translating symbolic labels, etc.

 

This is where the name "assembly" comes from: The programmer provides a list of higher-level mnemonic building blocks that on the surface stand self-contained on their own (i.e., not tied to any specific memory location or CPU circuit), and the tool assembles them into tightly packed codes understood by the machine, which exist on specific memory spaces.

 

To my mind, I cannot fathom how this can be missed as such an important and significant difference. It is as significant an achievement as going from manually editing a paper document typed in a typewriter, to doing so in a word-processor -- all of a sudden you do not have to worry about the position of words, and if you missed a sentence or double-typed a word, you don't have to scrap the whole thing and start from scratch or jump through hoops to patch it. The tool does the work of producing the actual output for you, and you can just concentrate on the task of writing.

 

As a final illustration, consider the following excerpt from the listing file of my assembled Intellivision programming framework:

0x513B                          TIMER.START     PROC
0x30D                           _t.next_exp     QSET    .TIMER.STATUS.NextExpiry
0x30C                           _t.sysclock     QSET    .TIMER.STATUS.SysClock

                                                ; Arguments:
                                                ;       R3:   Argument
                                                ;       DECLE Timer number
                                                ;       DECLE Delay
                                                ;       DECLE Period
                                                ;       DECLE Address
                                                ; --------------------------------------
513B   02A8                                     MVI@    R5,     R0                      ; Get timer number

513C   004C                     @@1:            SLL     R0,     2                       ; \
513D   02BC 0330                                MVII    #_t.tbl_addrs,          R4      ;  > Compute entry on timer table
513F   00C4                                     ADDR    R0,     R4                      ; /     t[n] = (table.start + (n * 4))

5140   0003                     @@2:            DIS
5141   02A8                                     MVI@    R5,     R0                      ; \
5142   02C0 030C                                ADD     _t.sysclock,            R0      ;  > Compute timer expiration time and store it
5144   0260                                     MVO@    R0,     R4                      ; /     timers[timer][EXPIRY] = (sysclock + expiry)

5145   0340 030D                                CMP     _t.next_exp,            R0      ; Check if this timer is the shortest and
5147   0201 0002                                BC      @@__skip                        ;   update the Next Expiry time if so.
5149   0240 030D                                MVO     R0,     _t.next_exp

514B   02A8                     @@__skip:       MVI@    R5,     R0                      ; \_ Store repeat period
514C   0260                                     MVO@    R0,     R4                      ; /

514D   0263                                     MVO@    R3,     R4                      ;    Store task argument

514E   02A8                                     MVI@    R5,     R0                      ; \_ Store pointer to task
514F   0260                                     MVO@    R0,     R4                      ; /

                                ;               JRE     R5
                                                ;
5150   0002                                     EIS
5151   00AF                                     JR      R5
                                                ENDP

The actual code itself is not relevant. I want to direct your attention to the left margin. See those hex numbers? That's the machine code after assemblage:

0x513B             
0x30D              
0x30C              

                   
                   
                   
                   
                   
                   
; Adr Op-Code                   
; --- -----------
513B   02A8        

513C   004C        
513D   02BC 0330   
513F   00C4        

5140   0003        
5141   02A8        
5142   02C0 030C   
5144   0260        

5145   0340 030D   
5147   0201 0002   
5149   0240 030D   

514B   02A8        
514C   0260        

514D   0263        

514E   02A8        
514F   0260        

                   
                   
5150   0002        
5151   00AF        

Notice how some are one word and others two. Notice also how the addresses increment accordingly. Also notice how some operations differ only by a single bit, whereas in the code I can use a more intuitive symbol like "@" to define addressing modes. For instance:

; Move out, indirect mode from R0 to R4:
5144   0260                                     MVO@    R0,     R4

; Move out, direct mode from R0 to _t.next_exp
5149   0240 030D                                MVO     R0,     _t.next_exp

               |
0x0260 : 001001100000
0x0240 : 001001000000
               ^

Lastly, and more important of all, notice how I didn't have to worry about any of that at all in my source code.

 

Personally, that to me is a very big deal -- enough to warrant calling things by their proper name and acknowledging their differences. :)

 

-dZ.

  • Like 1
Link to comment
Share on other sites

If a compiler outputs Assembly, that's still machine language just not in binary format.

 

Just as a last point on this for you to consider, I'd like to offer a thought experiment: Imagine how you would design and implement an assembler. It may be a lot simpler than a compiler, but I submit that it is still not a trivial project -- even without fancy stuff like complex expression evaluation and macros. You'll probably find out that it takes a bit more than a look-up table. ;)

 

-dZ.

Link to comment
Share on other sites

 

Yeah, it does look like BASIC, and I can see how that could be useful. However, I also see it as a "mixed metaphor," which is (I think) why the developers of Edtasm+ didn't follow through fully with the abstraction. Once the source is assembled, you get a "listing" file, which is an actual conventional artefact of assemblers. There's no place for line numbers at that point because you get actual absolute addresses.

 

At that point, what do you do with the line numbers? Keeping them around in a linked-list to be visible to the debugger would have taken additional resources that may not have been available.

 

Treating them as labels would have been "weird" also. Think about it: the environment used "100" as a label, now that is a label you cannot use. However, you can use "1000" because the program isn't that big and didn't have a line number that high. So everything works well until the day that your reach line #1000 in your source code, and now you have a duplicate definition of a label.

 

Alternatively, the manual could say "you can make use any symbolic label you want -- except numbers!" but that wouldn't be much fun at all. ;)

 

It wouldn't be impossible, and it probably wouldn't be too bad to keep the line numbers and follow through as full system-wide symbols (visible to the editor, debugger, etc.) However, I imagine it may have been too cumbersome or resource intensive to do at the time.

 

 

 

I'd like to hear more about Atari Flashback BASIC and its design.

 

 

 

 

As has been apparent by my previous posts, I moved on from BASIC back in the mid-to-late 1980s. Later on, in between system-level programming in hard-core languages such as C and Assembly, I also got stuck doing a stint on Visual Basic for a few years; then as a web developer on ASP (VBScript) for a couple more; and later on maintaining other's people's VB.Net code.

 

I learned to despise BASIC, especially VBScript, with the burning passion of a thousand suns. Being exposed (and proficient) in at least 1/2 a dozen other languages, it strikes me as such a broken language for anything more than just toy programming...

 

... Then again, I'm quite good at it. I excel at it, which is why I kept getting pulled into VB/VB.Net/VBScript projects by my employers, and among all the other things I had to do, I had to be the "VB Guy." I firmly believe that the strong command of the BASIC and its variations I have is due to my exposure to other languages, and to proper software engineering techniques -- and that they came in spite of doing BASIC itself.

 

Even today, having changed carriers to move out completely from software engineering and becoming a Data Architect for a major bank, someone discovered that our data modelling tool, SAP PowerDesigner, has a very expansive and accessible meta-model that can be extended at all levels for deep customizations and automation -- all running on (guess what???) VBScript! And once again, amidst the many other responsibilities I have is the programming of custom automation extensions for PowerDesigner in VBScript -- my nemesis. :mad:

 

So yeah, I'm a bit jaded because over the years I've seen soooooooo much crap code made by VB "programmers" that looks close to the stuff I made when I was a little kid. Having a deep understanding of the language semantics and its underlying implementation lets me see why this is so, and over and over I come to the same conclusion: the language just doesn't promote good practices. It's not that it's impossible to be a good VB programmer, it's that it is sooooo easy to be crap at it and get a good job -- and back in the day before Java (don't start me on Java either!) took over the corporate world, EVERY. SINGLE. BUSINESS. APPLICATION. was written in VB by an idiot! (I sometimes thought it was the same idiot just following me around from employer to employer writing the same crappy spaghetti VB code.)

 

I know the above sounds like an angry rant, but trust me, I'm writing this with my tongue firmly sticking to my cheek. It's sort of like a love-hate relationship what I have with BASIC: I hate it, but our fates appear to be intertwined and I can't seem to escape its grasp. I have a deep appreciation for work done to facilitate access to programming to newbies, such as IntyBASIC, bAtari BASIC, or your own Flashback BASIC.

 

For my personal projects, however, I stay away from it like the plague. ;)

 

 

I think that's a common misconception amongst many, although your comment above is more of a rational personal view. I think you should consider the differences between Assembly and Machine Language -- only in doing so can you appreciate what a big step Assembly Language was in the computer field.

 

In Machine Language, you must enter the machine code of each individual operation and its operands in the proper encoding order expected by the machine. For instance, something as simple as "add one to register R0" is not as simple as entering the "add" op-code and the register argument, but setting the proper bits of the op-code to select R0. In other words, there is a distinct op-code for "add one to register R0" and another for "add one to register R1," etc.

 

You also need to be aware of the size of each operation. Some operations encode into one byte or 16-bit word, others into two, or perhaps even three or four. As you enter these operations, you need to keep track of how many words have been used and increment the absolute address accordingly -- always keeping in mind that the CPU will do so one at a time, so if you by chance leave a gap, it'll probably crash it.

 

Moreover, depending on how the micro-code was encoded, some op-codes reserve different bits for different types of arguments, so it is not as straightforward as it may seem. Of course, this is all tightly-coupled to the CPU architecture, but each one has its own idiosyncrasies.

 

In contrast, Assembly Language abstracts all this by providing automatic encoding of a more simpler mnemonic syntax into its corresponding machine language. It is much more than the mnemonics -- it's a higher level cognitive model of the program. Assembly Language groups related operations into "families," such as unconditional and conditional branches, arithmetic and logic, and perhaps even register operations vs. memory operations. For each of these groups, it provides a consistent and intuitive (albeit low-level) interface so that you can think at a higher level of abstraction.

 

In Assembly Language adding one to a register may be as simple as just selecting the "add" instruction and the appropriate register as its argument. The assembler will then translate and encode the entire operation into the appropriate machine codes. It will take care of computing the size of the full operation and incrementing its internal address counter, mapping and translating symbolic labels, etc.

 

This is where the name "assembly" comes from: The programmer provides a list of higher-level mnemonic building blocks that on the surface stand self-contained on their own (i.e., not tied to any specific memory location or CPU circuit), and the tool assembles them into tightly packed codes understood by the machine, which exist on specific memory spaces.

 

To my mind, I cannot fathom how this can be missed as such an important and significant difference. It is as significant an achievement as going from manually editing a paper document typed in a typewriter, to doing so in a word-processor -- all of a sudden you do not have to worry about the position of words, and if you missed a sentence or double-typed a word, you don't have to scrap the whole thing and start from scratch or jump through hoops to patch it. The tool does the work of producing the actual output for you, and you can just concentrate on the task of writing.

 

As a final illustration, consider the following excerpt from the listing file of my assembled Intellivision programming framework:

0x513B                          TIMER.START     PROC
0x30D                           _t.next_exp     QSET    .TIMER.STATUS.NextExpiry
0x30C                           _t.sysclock     QSET    .TIMER.STATUS.SysClock

                                                ; Arguments:
                                                ;       R3:   Argument
                                                ;       DECLE Timer number
                                                ;       DECLE Delay
                                                ;       DECLE Period
                                                ;       DECLE Address
                                                ; --------------------------------------
513B   02A8                                     MVI@    R5,     R0                      ; Get timer number

513C   004C                     @@1:            SLL     R0,     2                       ; \
513D   02BC 0330                                MVII    #_t.tbl_addrs,          R4      ;  > Compute entry on timer table
513F   00C4                                     ADDR    R0,     R4                      ; /     t[n] = (table.start + (n * 4))

5140   0003                     @@2:            DIS
5141   02A8                                     MVI@    R5,     R0                      ; \
5142   02C0 030C                                ADD     _t.sysclock,            R0      ;  > Compute timer expiration time and store it
5144   0260                                     MVO@    R0,     R4                      ; /     timers[timer][EXPIRY] = (sysclock + expiry)

5145   0340 030D                                CMP     _t.next_exp,            R0      ; Check if this timer is the shortest and
5147   0201 0002                                BC      @@__skip                        ;   update the Next Expiry time if so.
5149   0240 030D                                MVO     R0,     _t.next_exp

514B   02A8                     @@__skip:       MVI@    R5,     R0                      ; \_ Store repeat period
514C   0260                                     MVO@    R0,     R4                      ; /

514D   0263                                     MVO@    R3,     R4                      ;    Store task argument

514E   02A8                                     MVI@    R5,     R0                      ; \_ Store pointer to task
514F   0260                                     MVO@    R0,     R4                      ; /

                                ;               JRE     R5
                                                ;
5150   0002                                     EIS
5151   00AF                                     JR      R5
                                                ENDP

The actual code itself is not relevant. I want to direct your attention to the left margin. See those hex numbers? That's the machine code after assemblage:

0x513B             
0x30D              
0x30C              

                   
                   
                   
                   
                   
                   
; Adr Op-Code                   
; --- -----------
513B   02A8        

513C   004C        
513D   02BC 0330   
513F   00C4        

5140   0003        
5141   02A8        
5142   02C0 030C   
5144   0260        

5145   0340 030D   
5147   0201 0002   
5149   0240 030D   

514B   02A8        
514C   0260        

514D   0263        

514E   02A8        
514F   0260        

                   
                   
5150   0002        
5151   00AF        

Notice how some are one word and others two. Notice also how the addresses increment accordingly. Also notice how some operations differ only by a single bit, whereas in the code I can use a more intuitive symbol like "@" to define addressing modes. For instance:

; Move out, indirect mode from R0 to R4:
5144   0260                                     MVO@    R0,     R4

; Move out, direct mode from R0 to _t.next_exp
5149   0240 030D                                MVO     R0,     _t.next_exp

               |
0x0260 : 001001100000
0x0240 : 001001000000
               ^

Lastly, and more important of all, notice how I didn't have to worry about any of that at all in my source code.

 

Personally, that to me is a very big deal -- enough to warrant calling things by their proper name and acknowledging their differences. :)

 

-dZ.

 

dZ,

Your Framework for the Inty looks interesting, I imagine it's designed to facilitate faster Assembly development on the Inty, what's in it?

 

Do you have an about section or a comment section that shows the Assembly programmer what the routines are?

 

I think of a Framework as a runtime library, half of a language that also enables abstract Assembly development.

 

The runtime library for Atari Flashback BASIC is the ASDK, a framework that I used directly to write PIXELS, here's a review with a clip of the game playing on the Atari Flashback:

https://www.youtube.com/watch?v=cl8ldGhacWI

 

Here are the comments at the top of the ASDK showing the objects and routines:

 

;;---------------------------------------------------------

;;---------------------------------------------------------

;;ASDK Framework Objects:

;;---------------------------------------------------------

;; The Virtual World! 10x the size of the viewable screen; x,y addressable

;;---------------------------

;; The CAM object (the visible screen/playfield) x,y positioning:

;; BITIndex is the x index, BYTErowoffset is the y index in 12 step increments

;;---------------------------

;; The Sprite objects (player0,player1, missile0,missile1)

;; x,y addressable, collapse when 0,0 (also can be mapped to the virtual world)

;;---------------------------

;; Music Engine (plays multiple scores, shapes notes, interrupts one voice for sfx, automatically returns to main theme (loops it) when secondary themes interrupt)

;;---------------------------------------------------------

;;---------------------------------------------------------

;; Static RAM variables (20 4-bit static RAM variables)

;; It's static RAM! Initialise them inline without code!

;;---------------------------------------------------------

;; Optimal 256 aditional static RAM variables (8-bit)

;;---------------------------------------------------------

;; Dynamic RAM variables 27 permanent and 4 temp variables (8-bit)

;;---------------------------------------------------------

;;

;;

;;---------------------------------------------------------

;;---------------------------------------------------------

;;ASDK Framework Functions:

;;---------------------------------------------------------

;;---------------------------------------------------------

;; Set pixel function: set, flip or poll a virtual world pixel

;;----------------------------------------------------------------------------------------

;; getbitstatus (argument: Accumulator (0 - flip pixel, 1 - set pixel, 2 - poll))

;; (return : Accumulator (0 - off, not zero - on).

;; Note that setting or flipping a pixel returns it's previous state (usefull!)

;; (uses all temp vars a,b,c,d)

;;----------------------------------------------------------------------------------------

;;----------------------------------------------------------------------------------------

;; Mapping sprites to the virtual world and detecting collisions in the virtual world:

;;----------------------------------------------------------------------------------------

;; findspritexyfromvirtualworldpixel - returns the corresponding x,y sprite coordinates in temp vars c & d

;; for any target pixel in the virtual world (if it's outside the viewable area 0's are returned to collapse the sprite).

;; (does not touch temp vars a & b)

;;----------------------------------------------------------------------------------------

;;----------------------------------------------------------------------------------------

;; Scrolling or panning the camera to another area:

;;----------------------------------------------------------------------------------------

;; scrollvirtualworldtoggle should be set to 1 to engage both kernels any time you change the CAM coordinates (set it back to 0 after scrolling to eliminate flicker).

;;----------------------------------------------------------------------------------------

;;----------------------------------------------------------------------------------------;;

;; Loading sprite definitions:

;;----------------------------------------------------------------------------------------

;; loadplayer (arguments: accumulator (0/1; player0/player1),y (0,8,16,24... sprite image index in inline sprite library)

;; loadplayerupsidedown (same arguments, vertical flip)

;; (does not touch temp vars a & b)

;;----------------------------------------------------------------------------------------;;

;; set4bitvar: Typecasting function to cast and store 8-bit variables (with values 0-15) as 4-bit variables

;;----------------------------------------------------------------------------------------;;

;; Where to put your Abstract Assembly game:

;; Put it in Gameloop and Gameloop2 (top and bottom blanks) - your game code goes there!

;;----------------------------------------------------------------------------------------;;

 

 

 

Regarding the two listings, I think the hex listing on the left with the addresses is machine language but still symbolic in the sense that those are hex numbers and still not binary, might as well be decimal.

 

The symbolic machine language on the right is more abstract, I definitely agree it's friendlier and more manageable, but at the same time it's still machine language - we know that the opcode mnemonics opcodes and operands tally up and increment the (invisible) addresses for the proceeding opcode mnemonics and opcodes.

 

If I was going to write an Assembler it would show the memory addresses next to every line and recalculate them all in realtiime every time you insert or delete a command; writing timing sensitive page aligned code that would be nice to see and modern Assemblers can be too abstract imo. I don't like to use Macro's for that reason; they don't fit with my conceptualization of Assembly as Machine Language, nor do super modern Assemblers that do too much stuff for me because they hide too much.

 

The programmer flipping switches to bootstrap an Altair could arguably make the distinction that only they and other switch flippers are really coding directly in machine language while the rest us are using symbolic representations to make life easier ;)

 

Cool BASIC stories from the field! I've gotten pulled into a lot of those projects too, always have a lot of fun with them - the T-SQL language extensions in SQL feel like BASIC with set operations.

Edited by Mr SQL
  • Like 1
Link to comment
Share on other sites

 

If I was going to write an Assembler it would show the memory addresses next to every line and recalculate them all in realtiime every time you insert or delete a command; writing timing sensitive page aligned code that would be nice to see and modern Assemblers can be too abstract imo. I don't like to use Macro's for that reason; they don't fit with my conceptualization of Assembly as Machine Language, nor do super modern Assemblers that do too much stuff for me because they hide too much.

 

 

 

I'm afraid you won't like my P-Machinery framework for Intellivision. It is a high-level framework built on a huge number of macros to abstract large parts of the underlying machinery needed to make games. It's fully object-oriented (in the old-school sense, where you operate on objects, not in the modern OOP paradigm) and event-driven.

 

The idea is that you can concentrate on the game's logic as handlers for the various game-events (controller input triggers, timers, collisions, etc.). You still need to use Assembly Language to check and update the system state, but the majority of the program's objects are manipulated using macros that resemble high-level language functions. It also offers a rudimentary but effective static memory allocation manager.

 

The input decoder sub-system is designed to be "plug-and-play," in that you can select any of a few built-in generalized decoders, or create your own specialized one and "plug" it in by just registering it to the framework.

 

For example, you first include the framework at the very top of your program:

                ; Import and initialize the P-Machiner framework
                ;   NOTE:   This macro must be the very first thing
                ;           of your program.
                INCLUDE "mac/mac_prog.mac"

Then you wrap your program in PROG.Start() and PROG.End() directives to define the scope of your program:

                ; Define a new P-Machinery program.
                ;   PROG.Start title, author, year
                PROG.Start "P-MACH TEST", "DZ-Jay", 2018

                    ; You implement your game program here.  The recommended
                    ; way is to treat each game phase (Main State) as its
                    ; own self-contained sub-program, and import each module
                    ; individually below.

                    ; Demo:  Test MOB movement
                    LIB.Include  "tests/test-mob.asm"

                    ; Once all states are defined, declare which is
                    ; the starting State and Sub-State.
                    STATE.DefineInitial(TestMob, Init)

                ; Close the P-Machinery program.
                PROG.End

You use the LIB.Include() directive to include modules and sub-programs into your game, that way you can organize your code in manageable chunks. The LIB.Include() directive is a wrapper to the assembler's "include" directive, but keeps track of imported modules so that they are only imported once. That way, you don't have to worry about the order of dependencies or duplicated imports, you can just "LIB.Include" any module that is needed on any other module and the framework will make sure to only import it once.

 

In this example, the actual program code is encompassed in the module "test-mob.asm" included above.

 

P-Machinery is at its core a rather simple Finite State Machine, where each state represents a self-contained game program "phase," such as the "title screen" or "credits sequence," "game play," "game over," etc. Each one of these states is enclosed in "STATE.Start()" and "STATE.End()" directives, and all variables declared within them exist (statically) in their own space. Thus, the available RAM is re-used in each state. They also can use a different controller input decoder, so that you can have, say, a keypad entry to select from a menu in a "Menu" state, and a disc-and-action button decoder for regular game-play.

 

These main "Machine States" are themselves sub-divided into smaller sub-states, such as "initialization," "play," "finalization," etc. These sub-states are the ones that handle normal game events by defining individual event handlers via dispatch tables.

 

In the above example, the "test-mob.asm" module first declares the hand-controller it will use and starts a new main machine state called "TestMob":

        ; Declare which hand-controller decoders we'll need
        IO.UseDecoder(GeneralSingle)

        ; Define a new state: TEST
        STATE.Start TestMob

The "GeneralSingle" decoder is a general-purpose decoder for single player games that will read input from any of the hand-controllers and treat them as a single input device. It will dispatch discrete events for any key, disc, or button pressed.

 

If you intend your game to use multiple decoders, you must declare them all for use. That will take care of importing the necessary code modules and data structures.

 

We then proceed to allocate some RAM by defining a Graphics RAM block for use by the test sprite and a couple of variables:

                ; Must define resources first
                ; --------------------------------------
                GRAM.DefineSpriteBlock(ROCKET, Double)  ; GRAM block for the sprite
                RAM.WordVar            ROCKET.SpeedX    ; \_ Variables to hold sprite speed
                RAM.WordVar            ROCKET.SpeedY    ; /

It then uses the framework's PICT block directive to define the sprites graphics data. This directive will actually generate the necessary binary data to describe the picture.

                ; Define sprite picture
                ; --------------------------------------
ROCKET_PIC      PICT    Double, PM.AUTO                 ; Double vertical resolution, with
                DROW    "........"                      ;   automatic pixel character detection:
                DROW    "........"                      ;       "." = Off
                DROW    "...#...."                      ;       "#" = On
                DROW    "...#...."
                DROW    "...#...."
                DROW    "...#...."
                DROW    "..###..."
                DROW    "..#.#..."
                DROW    ".#...#.."
                DROW    "##.#.##."
                DROW    "###.###."
                DROW    "#######."
                DROW    "..#.#..."
                DROW    ".#.#.#.."
                DROW    ".#...#.."
                DROW    "........"
                ENDPICT

We then define a simple MOB (Movable Object Block, the actual hardware object representing sprites). This is also a framework construct that offers an intuitive interface to define a re-usable template to describe the attributes of a movable object. In this case we say that the picture to use is the block allocated in GRAM called "ROCKET".

 

During initialization, we will load the ROCKET_PICT data into this block, and initialize MOB #0 using the "ROCKET" template definition below.

;; ======================================================================== ;;
;;  ROCKET                                                                  ;;
;;      This is a simple MOB definition record.  It defines a reusable data ;;
;;      structure that serves as a template for instantiating MOBs with     ;;
;;      user-defined default attributes.                                    ;;
;;                                                                          ;;
;;      MOB templates are defined using the MOBDEF structure. They can then ;;
;;      be referenced via their "Id" property.                              ;;
;;                                                                          ;;
;;      MOBs are initialized at run-time using the MOB.InitObject()         ;;
;;      directive and assigned a MOB definition record.  For example, to    ;;
;;      initialize MOB #0 using the ROCKET definition above, you use:       ;;
;;                                                                          ;;
;;              MOB.InitObject(0, ROCKET.Id).                               ;;
;;                                                                          ;;
;;      This DEMO uses a single MOB, declared as an 8x16 GRAM yellow sprite ;;
;       set at the center of the screen.                                    ;;
;; ======================================================================== ;;
ROCKET          MOBDEF
                DATTR.Sprite        ROCKET, CS.Yellow   ; Sprite    [block, color]
                DATTR.Position      84, 54              ; Position  [x, y]
                DATTR.Visibility    On                  ; Visibility[visb]
                ENDMD

We also declare its initial position, its color, and that it should be visible by default.

 

After that, we define a dispatch table to handle the input events of the hand-controller decoder. For this demo, we are only interested in handling Disc "press" and "release" events, so we set handlers for those and "null" all others:

;; ======================================================================== ;;
;;  TEST_DISP                                                               ;;
;;      This is the dispatch table for the "Move" state.  Include pointers  ;;
;;      to those events you want to handle in your game.  Discrete events   ;;
;;      are dispatched for "Up" (release) and "Down" (pressed) occurrences, ;;
;;      per input type, in the following order:                             ;;
;;                                                                          ;;
;;      Release:                                                            ;;
;;          DiscUp      - Disc released.                                    ;;
;;          ActionUp    - Action button released.                           ;;
;;          KeyUp       - Keypad button released.                           ;;
;;                                                                          ;;
;;      Pressed:                                                            ;;
;;          DiscDown    - Disc pressed.                                     ;;
;;          ActionDown  - Action button pressed.                            ;;
;;          KeyDown     - Keypad button pressed.                            ;;
;;                                                                          ;;
;;      This DEMO supports only two events: "DiscUp" and "DiscDown".  All   ;;
;;      other event handlers are set to PM.NULL to be ignored.              ;;
;; ======================================================================== ;;
TEST_DISP       PROC
                DECLE   TEST_CTRL.DiscUp
                DECLE   PM.NULL
                DECLE   PM.NULL
                DECLE   TEST_CTRL.DiscDown
                DECLE   PM.NULL
                DECLE   PM.NULL
                ENDP

Alternatively, we could have use a more specialized input decoder that only dispatches disc events and ignores all other input.

 

Now we write the actual code for those event handlers. In this example, the TEST_CTRL.DiscUp and TEST_CTRL.DiscDown are just different entry points on the same TEST_CTRL procedure. This is just a matter of personal style, they could have been separate procedures.

;; ======================================================================== ;;
;;  TEST_CTRL                                                               ;;
;;      Event-handler code for the events declared above.                   ;;
;;                                                                          ;;
;;      An event is dispatched for each of the six event types defined. The ;;
;;      Information Payload included with each dispatched event is a 16-bit ;;
;;      word in the following general format:                               ;;
;;                                                        Keypad/Disc       ;;
;;                                                             |            ;;
;;                                                       ,=====|=====,      ;;
;;                                                       |           |      ;;
;;                             Port                      |    Action |      ;;
;;           Reserved            |                       |        \  |      ;;
;;               ^             ,=|=,                     |       ,=|=|      ;;
;;     ,_________|_________,   |   |                     |       |   |      ;;
;;    /                     \  v   v                     v       v   v      ;;
;;   +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+    ;;
;;   | x : x : x : x : x : x | n : n | | . : . | . : . | X : X : X : X |    ;;
;;   +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+    ;;
;;     F   E   D   C   B   A   9   8     7   6   5   4   3   2   1   0      ;;
;;    \______________,______________/   \______________,______________/     ;;
;;                   |                                 |                    ;;
;;                   v                                 v                    ;;
;;              Event Status                      Event Data                ;;
;;                                                                          ;;
;;      Constants for each controller input type are available as follows:  ;;
;;          HAND.PORT:                                                      ;;
;;              PlayerAny   ( 0)                                            ;;
;;              Player1     ( 0)                                            ;;
;;              Player2     ( 1)                                            ;;
;;              Player3     ( 2)                                            ;;
;;              Player4     ( 3)                                            ;;
;;                                                                          ;;
;;          HAND.KEYPAD:                                                    ;;
;;              0 - 9       (0 .. 9)                                        ;;
;;              Clear       (10)                                            ;;
;;              Enter       (11)                                            ;;
;;                                                                          ;;
;;          HAND.ACTION:                                                    ;;
;;              Top         ( 0)                                            ;;
;;              BottomLeft  ( 1)                                            ;;
;;              BottomRight ( 2)                                            ;;
;;                                                                          ;;
;;          HAND.Disc:                                                      ;;
;;              N           ( 0)   Disc positions, clockwise:               ;;
;;              NNE         ( 1)                                            ;;
;;              NE          ( 2)               0                            ;;
;;              ENE         ( 3)          F         1                       ;;
;;              E           ( 4)       E       N       2                    ;;
;;              ESE         ( 5)      D        ^        3                   ;;
;;              SE          ( 6)               |                            ;;
;;              SSE         ( 7)     C    W<---+--->E    4                  ;;
;;              S           (                |                            ;;
;;              SSW         ( 9)      B        v        5                   ;;
;;              SW          (10)       A       S       6                    ;;
;;              WSW         (11)          9         7                       ;;
;;              W           (12)               8                            ;;
;;              WNW         (13)                                            ;;
;;              NW          (14)                                            ;;
;;              NNW         (15)                                            ;;
;;                                                                          ;;
;;      The Information Payload word is sent as an argument to the event    ;;
;;      handler in register R3.                                             ;;
;; ======================================================================== ;;
TEST_CTRL       PROC
                ; --------------------------------------
                ; Release events:
                ;   Reset speed to zero.
                ; --------------------------------------
@@DiscUp:       CLRR    R0
                MVO     R0,     ROCKET.SpeedX           ; \_ Clear speed variables
                MVO     R0,     ROCKET.SpeedY           ; /

                MOB.SetColor(0, CS.Yellow)              ; Set MOB color to yellow (rest)
                TRETURN

                ; --------------------------------------
                ; Pressed events:
                ;   Set the speed according to the
                ;   direction pressed, as defined by the
                ;   table below.
                ; --------------------------------------
@@DiscDown:     UNPKL   R3,     R1                      ; Unpack Status and Data bytes (R3: Status; R1: Data)

                SLL     R1,     1                       ; \_ Compute the table record address
                ADDI    #@@__disp_tbl,  R1              ; /     addr = (base + (dir * 2))

                MVI@    R1,     R0                      ; \_ Set the horizontal speed
                MVO     R0,     ROCKET.SpeedX           ; /

                INCR    R1
                MVI@    R1,     R0                      ; \_ Set the vertical speed
                MVO     R0,     ROCKET.SpeedY           ; /

                MOB.SetColor(0, CS.Red)                 ; Set MOB color to red (moving)
                TRETURN

                ; --------------------------------------
                ; MOB Displacement Table (X, Y)
                ; --------------------------------------
@@__disp_tbl:   DECLE   +0, -2          ; 0
                DECLE   +0, -1          ; 1
                DECLE   +2, -2          ; 2
                DECLE   +1, +0          ; 3
                DECLE   +2, +0          ; 4
                DECLE   +1, +0          ; 5
                DECLE   +2, +2          ; 6
                DECLE   +0, +1          ; 7
                DECLE   +0, +2          ; 8
                DECLE   +0, +1          ; 9
                DECLE   -2, +2          ; 10
                DECLE   -1, +0          ; 11
                DECLE   -2, +0          ; 12
                DECLE   -1, +0          ; 13
                DECLE   -2, -2          ; 14
                DECLE   +0, -1          ; 15
                ENDP

Basically, each event handler just decodes the event payload as necessary, updates the sprite's velocity depending on the disc direction, and then changes the color the sprite: yellow when idle (disc released), red when moving (disc pressed).

 

This is a very simple demo to test the MOB movement infrastructure, so we just read deltas from a table corresponding to each of the disc 16 positions.

 

At this point, we declare that we will use two sub-states as part of the "TEST_MOB" main state:

                ; Define sub-states for TITLE
                ;   STATE.AddSub(sub, event, ctrl, disp, opts)
                STATE.AddSub(Move, TEST_MOVE_MOB, GeneralSingle, TEST_DISP, KRNOPT.Play)

                STATE.AddSub(Init, TEST_INIT_MOB, None, PM.NULL, KRNOPT.Override)

The first one, called "Move," points to procedure "TEST_MOVE_MOB" as the state event handler, and states that it will use the "GeneralSingle" decoder declared at the top. It then specifies our TEST_DISP table as the input decoder dispatch table. The "KRNOPT.Override" is a kernel option that essentially says "I want to override the framework kernel, so on every game loop iteration call this handler instead."

 

The second one, called "Init," points to procedure "TEST_INIT_MOB" as the state event handler, and specifies that it won't use any input decoder.

 

As you transition from one sub-state to another, the framework will call the appropriate event handler automatically.

 

Now, it's finally time to write these event handlers. First is the "TEST_INIT_MOB" handler, which will be called to initialize the environment: set the background color, clear the screen, load the ROCKET_PICT into the ROCKET block in GRAM, initialize MOB #0 by applying the ROCKET template defined above, and finally, transition to the "Move" sub-state.

;; ======================================================================== ;;
;;  TEST_INIT_MOB                                                           ;;
;;  Procedure to initialize a MOB for use.                                  ;;
;;                                                                          ;;
;;  INPUT for STATE.SET                                                     ;;
;;      R5      Pointer to return address.                                  ;;
;;                                                                          ;;
;;  OUTPUT                                                                  ;;
;;      None.                                                               ;;
;; ======================================================================== ;;
TEST_INIT_MOB   PROC
                ; Initialize the screen color
                KERNEL.SetBorderColor(CS.Black)
                KERNEL.SetColorStack(CS.Black, CS.Black, CS.Black, CS.Black)

                ; Load the sprite picture into its GRAM block
                GFX.LoadSpriteBlock(ROCKET, ROCKET_PIC, Packed)

                ; Initialize MOB #0 using the ROCKET template
                MOB.InitObject(0, ROCKET.Id)

                CLRR    R0                              ; \
                MVO     R0,     ROCKET.SpeedX           ;  > Initialize MOB speed to zero
                MVO     R0,     ROCKET.SpeedY           ; /

                STATE.Set(TestMob, Move, Enable)

                ; IMPORTANT:
                ;   State handlers that override the Kernel ISR
                ;   *must* return to the ISR dispatcher.
                IRETURN
                ENDP

Second is the "TEST_MOVE_MOB" handler, which as we declared above with "KRNOPT.Override," will be called on every frame. All it does is take the last known values of the sprite's position and apply them to the actual MOB.

;; ======================================================================== ;;
;;  TEST_MOVE_MOB                                                           ;;
;;  Procedure to move MOBs around the screen.  It will be called on every   ;;
;;  game kernel iteration as the event handler of the current state.        ;;
;;                                                                          ;;
;;  INPUT for STATE.SET                                                     ;;
;;      R5      Pointer to return address.                                  ;;
;;                                                                          ;;
;;  OUTPUT                                                                  ;;
;;      None.                                                               ;;
;; ======================================================================== ;;
TEST_MOVE_MOB   PROC
                MVI     ROCKET.SpeedX,  R0              ; \_ Get current speed
                MVI     ROCKET.SpeedY,  R1              ; /

                MOB.Displace(0, R0, R1)                 ; Displace MOB based on speed

                ERETURN
                ENDP

And finally, we close our module with the "STATE.End()" directive:

        STATE.End

By default, P-Machinery will define under the covers a special "ClassicTitle" main game state that draws a default title screen in the style of old Mattel Intellivision games, using the meta-data you included at the start of your program. It then waits for any key before transitioning to the "initial" state of your game. You can override this and create your own title sequence, but this is done automatically for you.

 

The final demo program runs like this:

avi_0001.mov'>avi_0001.mov

 

 

Underneath the veneer of all those directives is a highly complex underlying framework that validates inputs, automatically differentiates between immediate, indirect, and register modes, keeps track of some global design-time state, and handles errors with programmer-friendly warnings and exceptions. There is also a tightly optimized kernel and task manager that handles all state transitions and abstracts the idiosyncrasies of the Intellivision video chip and Graphics RAM access.n Animation, music, sound effects, and other sub-systems are still in development, as is a "scripting" engine built on top for cut-scenes and pre-defined sequences.

 

This demo, which is about 80% of a simple, single screen game, is only a few lines long (ignoring the comments). P-Machinery does most of the work for the programmer, so that he can concentrate on actual game logic and asset definition. :)

 

-dZ.

Edited by DZ-Jay
  • Like 1
Link to comment
Share on other sites

 

 

I'm afraid you won't like my P-Machinery framework for Intellivision. It is a high-level framework built on a huge number of macros to abstract large parts of the underlying machinery needed to make games. It's fully object-oriented (in the old-school sense, where you operate on objects, not in the modern OOP paradigm) and event-driven.

 

The idea is that you can concentrate on the game's logic as handlers for the various game-events (controller input triggers, timers, collisions, etc.). You still need to use Assembly Language to check and update the system state, but the majority of the program's objects are manipulated using macros that resemble high-level language functions. It also offers a rudimentary but effective static memory allocation manager.

 

The input decoder sub-system is designed to be "plug-and-play," in that you can select any of a few built-in generalized decoders, or create your own specialized one and "plug" it in by just registering it to the framework.

 

For example, you first include the framework at the very top of your program:

                ; Import and initialize the P-Machiner framework
                ;   NOTE:   This macro must be the very first thing
                ;           of your program.
                INCLUDE "mac/mac_prog.mac"

Then you wrap your program in PROG.Start() and PROG.End() directives to define the scope of your program:

                ; Define a new P-Machinery program.
                ;   PROG.Start title, author, year
                PROG.Start "P-MACH TEST", "DZ-Jay", 2018

                    ; You implement your game program here.  The recommended
                    ; way is to treat each game phase (Main State) as its
                    ; own self-contained sub-program, and import each module
                    ; individually below.

                    ; Demo:  Test MOB movement
                    LIB.Include  "tests/test-mob.asm"

                    ; Once all states are defined, declare which is
                    ; the starting State and Sub-State.
                    STATE.DefineInitial(TestMob, Init)

                ; Close the P-Machinery program.
                PROG.End

You use the LIB.Include() directive to include modules and sub-programs into your game, that way you can organize your code in manageable chunks. The LIB.Include() directive is a wrapper to the assembler's "include" directive, but keeps track of imported modules so that they are only imported once. That way, you don't have to worry about the order of dependencies or duplicated imports, you can just "LIB.Include" any module that is needed on any other module and the framework will make sure to only import it once.

 

In this example, the actual program code is encompassed in the module "test-mob.asm" included above.

 

P-Machinery is at its core a rather simple Finite State Machine, where each state represents a self-contained game program "phase," such as the "title screen" or "credits sequence," "game play," "game over," etc. Each one of these states is enclosed in "STATE.Start()" and "STATE.End()" directives, and all variables declared within them exist (statically) in their own space. Thus, the available RAM is re-used in each state. They also can use a different controller input decoder, so that you can have, say, a keypad entry to select from a menu in a "Menu" state, and a disc-and-action button decoder for regular game-play.

 

These main "Machine States" are themselves sub-divided into smaller sub-states, such as "initialization," "play," "finalization," etc. These sub-states are the ones that handle normal game events by defining individual event handlers via dispatch tables.

 

In the above example, the "test-mob.asm" module first declares the hand-controller it will use and starts a new main machine state called "TestMob":

        ; Declare which hand-controller decoders we'll need
        IO.UseDecoder(GeneralSingle)

        ; Define a new state: TEST
        STATE.Start TestMob

The "GeneralSingle" decoder is a general-purpose decoder for single player games that will read input from any of the hand-controllers and treat them as a single input device. It will dispatch discrete events for any key, disc, or button pressed.

 

If you intend your game to use multiple decoders, you must declare them all for use. That will take care of importing the necessary code modules and data structures.

 

We then proceed to allocate some RAM by defining a Graphics RAM block for use by the test sprite and a couple of variables:

                ; Must define resources first
                ; --------------------------------------
                GRAM.DefineSpriteBlock(ROCKET, Double)  ; GRAM block for the sprite
                RAM.WordVar            ROCKET.SpeedX    ; \_ Variables to hold sprite speed
                RAM.WordVar            ROCKET.SpeedY    ; /

It then uses the framework's PICT block directive to define the sprites graphics data. This directive will actually generate the necessary binary data to describe the picture.

                ; Define sprite picture
                ; --------------------------------------
ROCKET_PIC      PICT    Double, PM.AUTO                 ; Double vertical resolution, with
                DROW    "........"                      ;   automatic pixel character detection:
                DROW    "........"                      ;       "." = Off
                DROW    "...#...."                      ;       "#" = On
                DROW    "...#...."
                DROW    "...#...."
                DROW    "...#...."
                DROW    "..###..."
                DROW    "..#.#..."
                DROW    ".#...#.."
                DROW    "##.#.##."
                DROW    "###.###."
                DROW    "#######."
                DROW    "..#.#..."
                DROW    ".#.#.#.."
                DROW    ".#...#.."
                DROW    "........"
                ENDPICT

We then define a simple MOB (Movable Object Block, the actual hardware object representing sprites). This is also a framework construct that offers an intuitive interface to define a re-usable template to describe the attributes of a movable object. In this case we say that the picture to use is the block allocated in GRAM called "ROCKET".

 

During initialization, we will load the ROCKET_PICT data into this block, and initialize MOB #0 using the "ROCKET" template definition below.

;; ======================================================================== ;;
;;  ROCKET                                                                  ;;
;;      This is a simple MOB definition record.  It defines a reusable data ;;
;;      structure that serves as a template for instantiating MOBs with     ;;
;;      user-defined default attributes.                                    ;;
;;                                                                          ;;
;;      MOB templates are defined using the MOBDEF structure. They can then ;;
;;      be referenced via their "Id" property.                              ;;
;;                                                                          ;;
;;      MOBs are initialized at run-time using the MOB.InitObject()         ;;
;;      directive and assigned a MOB definition record.  For example, to    ;;
;;      initialize MOB #0 using the ROCKET definition above, you use:       ;;
;;                                                                          ;;
;;              MOB.InitObject(0, ROCKET.Id).                               ;;
;;                                                                          ;;
;;      This DEMO uses a single MOB, declared as an 8x16 GRAM yellow sprite ;;
;       set at the center of the screen.                                    ;;
;; ======================================================================== ;;
ROCKET          MOBDEF
                DATTR.Sprite        ROCKET, CS.Yellow   ; Sprite    [block, color]
                DATTR.Position      84, 54              ; Position  [x, y]
                DATTR.Visibility    On                  ; Visibility[visb]
                ENDMD

We also declare its initial position, its color, and that it should be visible by default.

 

After that, we define a dispatch table to handle the input events of the hand-controller decoder. For this demo, we are only interested in handling Disc "press" and "release" events, so we set handlers for those and "null" all others:

;; ======================================================================== ;;
;;  TEST_DISP                                                               ;;
;;      This is the dispatch table for the "Move" state.  Include pointers  ;;
;;      to those events you want to handle in your game.  Discrete events   ;;
;;      are dispatched for "Up" (release) and "Down" (pressed) occurrences, ;;
;;      per input type, in the following order:                             ;;
;;                                                                          ;;
;;      Release:                                                            ;;
;;          DiscUp      - Disc released.                                    ;;
;;          ActionUp    - Action button released.                           ;;
;;          KeyUp       - Keypad button released.                           ;;
;;                                                                          ;;
;;      Pressed:                                                            ;;
;;          DiscDown    - Disc pressed.                                     ;;
;;          ActionDown  - Action button pressed.                            ;;
;;          KeyDown     - Keypad button pressed.                            ;;
;;                                                                          ;;
;;      This DEMO supports only two events: "DiscUp" and "DiscDown".  All   ;;
;;      other event handlers are set to PM.NULL to be ignored.              ;;
;; ======================================================================== ;;
TEST_DISP       PROC
                DECLE   TEST_CTRL.DiscUp
                DECLE   PM.NULL
                DECLE   PM.NULL
                DECLE   TEST_CTRL.DiscDown
                DECLE   PM.NULL
                DECLE   PM.NULL
                ENDP

Alternatively, we could have use a more specialized input decoder that only dispatches disc events and ignores all other input.

 

Now we write the actual code for those event handlers. In this example, the TEST_CTRL.DiscUp and TEST_CTRL.DiscDown are just different entry points on the same TEST_CTRL procedure. This is just a matter of personal style, they could have been separate procedures.

;; ======================================================================== ;;
;;  TEST_CTRL                                                               ;;
;;      Event-handler code for the events declared above.                   ;;
;;                                                                          ;;
;;      An event is dispatched for each of the six event types defined. The ;;
;;      Information Payload included with each dispatched event is a 16-bit ;;
;;      word in the following general format:                               ;;
;;                                                        Keypad/Disc       ;;
;;                                                             |            ;;
;;                                                       ,=====|=====,      ;;
;;                                                       |           |      ;;
;;                             Port                      |    Action |      ;;
;;           Reserved            |                       |        \  |      ;;
;;               ^             ,=|=,                     |       ,=|=|      ;;
;;     ,_________|_________,   |   |                     |       |   |      ;;
;;    /                     \  v   v                     v       v   v      ;;
;;   +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+    ;;
;;   | x : x : x : x : x : x | n : n | | . : . | . : . | X : X : X : X |    ;;
;;   +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+    ;;
;;     F   E   D   C   B   A   9   8     7   6   5   4   3   2   1   0      ;;
;;    \______________,______________/   \______________,______________/     ;;
;;                   |                                 |                    ;;
;;                   v                                 v                    ;;
;;              Event Status                      Event Data                ;;
;;                                                                          ;;
;;      Constants for each controller input type are available as follows:  ;;
;;          HAND.PORT:                                                      ;;
;;              PlayerAny   ( 0)                                            ;;
;;              Player1     ( 0)                                            ;;
;;              Player2     ( 1)                                            ;;
;;              Player3     ( 2)                                            ;;
;;              Player4     ( 3)                                            ;;
;;                                                                          ;;
;;          HAND.KEYPAD:                                                    ;;
;;              0 - 9       (0 .. 9)                                        ;;
;;              Clear       (10)                                            ;;
;;              Enter       (11)                                            ;;
;;                                                                          ;;
;;          HAND.ACTION:                                                    ;;
;;              Top         ( 0)                                            ;;
;;              BottomLeft  ( 1)                                            ;;
;;              BottomRight ( 2)                                            ;;
;;                                                                          ;;
;;          HAND.Disc:                                                      ;;
;;              N           ( 0)   Disc positions, clockwise:               ;;
;;              NNE         ( 1)                                            ;;
;;              NE          ( 2)               0                            ;;
;;              ENE         ( 3)          F         1                       ;;
;;              E           ( 4)       E       N       2                    ;;
;;              ESE         ( 5)      D        ^        3                   ;;
;;              SE          ( 6)               |                            ;;
;;              SSE         ( 7)     C    W<---+--->E    4                  ;;
;;              S           (                |                            ;;
;;              SSW         ( 9)      B        v        5                   ;;
;;              SW          (10)       A       S       6                    ;;
;;              WSW         (11)          9         7                       ;;
;;              W           (12)               8                            ;;
;;              WNW         (13)                                            ;;
;;              NW          (14)                                            ;;
;;              NNW         (15)                                            ;;
;;                                                                          ;;
;;      The Information Payload word is sent as an argument to the event    ;;
;;      handler in register R3.                                             ;;
;; ======================================================================== ;;
TEST_CTRL       PROC
                ; --------------------------------------
                ; Release events:
                ;   Reset speed to zero.
                ; --------------------------------------
@@DiscUp:       CLRR    R0
                MVO     R0,     ROCKET.SpeedX           ; \_ Clear speed variables
                MVO     R0,     ROCKET.SpeedY           ; /

                MOB.SetColor(0, CS.Yellow)              ; Set MOB color to yellow (rest)
                TRETURN

                ; --------------------------------------
                ; Pressed events:
                ;   Set the speed according to the
                ;   direction pressed, as defined by the
                ;   table below.
                ; --------------------------------------
@@DiscDown:     UNPKL   R3,     R1                      ; Unpack Status and Data bytes (R3: Status; R1: Data)

                SLL     R1,     1                       ; \_ Compute the table record address
                ADDI    #@@__disp_tbl,  R1              ; /     addr = (base + (dir * 2))

                MVI@    R1,     R0                      ; \_ Set the horizontal speed
                MVO     R0,     ROCKET.SpeedX           ; /

                INCR    R1
                MVI@    R1,     R0                      ; \_ Set the vertical speed
                MVO     R0,     ROCKET.SpeedY           ; /

                MOB.SetColor(0, CS.Red)                 ; Set MOB color to red (moving)
                TRETURN

                ; --------------------------------------
                ; MOB Displacement Table (X, Y)
                ; --------------------------------------
@@__disp_tbl:   DECLE   +0, -2          ; 0
                DECLE   +0, -1          ; 1
                DECLE   +2, -2          ; 2
                DECLE   +1, +0          ; 3
                DECLE   +2, +0          ; 4
                DECLE   +1, +0          ; 5
                DECLE   +2, +2          ; 6
                DECLE   +0, +1          ; 7
                DECLE   +0, +2          ; 8
                DECLE   +0, +1          ; 9
                DECLE   -2, +2          ; 10
                DECLE   -1, +0          ; 11
                DECLE   -2, +0          ; 12
                DECLE   -1, +0          ; 13
                DECLE   -2, -2          ; 14
                DECLE   +0, -1          ; 15
                ENDP

Basically, each event handler just decodes the event payload as necessary, updates the sprite's velocity depending on the disc direction, and then changes the color the sprite: yellow when idle (disc released), red when moving (disc pressed).

 

This is a very simple demo to test the MOB movement infrastructure, so we just read deltas from a table corresponding to each of the disc 16 positions.

 

At this point, we declare that we will use two sub-states as part of the "TEST_MOB" main state:

                ; Define sub-states for TITLE
                ;   STATE.AddSub(sub, event, ctrl, disp, opts)
                STATE.AddSub(Move, TEST_MOVE_MOB, GeneralSingle, TEST_DISP, KRNOPT.Play)

                STATE.AddSub(Init, TEST_INIT_MOB, None, PM.NULL, KRNOPT.Override)

The first one, called "Move," points to procedure "TEST_MOVE_MOB" as the state event handler, and states that it will use the "GeneralSingle" decoder declared at the top. It then specifies our TEST_DISP table as the input decoder dispatch table. The "KRNOPT.Override" is a kernel option that essentially says "I want to override the framework kernel, so on every game loop iteration call this handler instead."

 

The second one, called "Init," points to procedure "TEST_INIT_MOB" as the state event handler, and specifies that it won't use any input decoder.

 

As you transition from one sub-state to another, the framework will call the appropriate event handler automatically.

 

Now, it's finally time to write these event handlers. First is the "TEST_INIT_MOB" handler, which will be called to initialize the environment: set the background color, clear the screen, load the ROCKET_PICT into the ROCKET block in GRAM, initialize MOB #0 by applying the ROCKET template defined above, and finally, transition to the "Move" sub-state.

;; ======================================================================== ;;
;;  TEST_INIT_MOB                                                           ;;
;;  Procedure to initialize a MOB for use.                                  ;;
;;                                                                          ;;
;;  INPUT for STATE.SET                                                     ;;
;;      R5      Pointer to return address.                                  ;;
;;                                                                          ;;
;;  OUTPUT                                                                  ;;
;;      None.                                                               ;;
;; ======================================================================== ;;
TEST_INIT_MOB   PROC
                ; Initialize the screen color
                KERNEL.SetBorderColor(CS.Black)
                KERNEL.SetColorStack(CS.Black, CS.Black, CS.Black, CS.Black)

                ; Load the sprite picture into its GRAM block
                GFX.LoadSpriteBlock(ROCKET, ROCKET_PIC, Packed)

                ; Initialize MOB #0 using the ROCKET template
                MOB.InitObject(0, ROCKET.Id)

                CLRR    R0                              ; \
                MVO     R0,     ROCKET.SpeedX           ;  > Initialize MOB speed to zero
                MVO     R0,     ROCKET.SpeedY           ; /

                STATE.Set(TestMob, Move, Enable)

                ; IMPORTANT:
                ;   State handlers that override the Kernel ISR
                ;   *must* return to the ISR dispatcher.
                IRETURN
                ENDP

Second is the "TEST_MOVE_MOB" handler, which as we declared above with "KRNOPT.Override," will be called on every frame. All it does is take the last known values of the sprite's position and apply them to the actual MOB.

;; ======================================================================== ;;
;;  TEST_MOVE_MOB                                                           ;;
;;  Procedure to move MOBs around the screen.  It will be called on every   ;;
;;  game kernel iteration as the event handler of the current state.        ;;
;;                                                                          ;;
;;  INPUT for STATE.SET                                                     ;;
;;      R5      Pointer to return address.                                  ;;
;;                                                                          ;;
;;  OUTPUT                                                                  ;;
;;      None.                                                               ;;
;; ======================================================================== ;;
TEST_MOVE_MOB   PROC
                MVI     ROCKET.SpeedX,  R0              ; \_ Get current speed
                MVI     ROCKET.SpeedY,  R1              ; /

                MOB.Displace(0, R0, R1)                 ; Displace MOB based on speed

                ERETURN
                ENDP

And finally, we close our module with the "STATE.End()" directive:

        STATE.End

By default, P-Machinery will define under the covers a special "ClassicTitle" main game state that draws a default title screen in the style of old Mattel Intellivision games, using the meta-data you included at the start of your program. It then waits for any key before transitioning to the "initial" state of your game. You can override this and create your own title sequence, but this is done automatically for you.

 

The final demo program runs like this:

http://atariage.com/forums/index.php?app=core&module=attach&section=attach&attach_rel_module=post&attach_id=557706

 

 

Underneath the veneer of all those directives is a highly complex underlying framework that validates inputs, automatically differentiates between immediate, indirect, and register modes, keeps track of some global design-time state, and handles errors with programmer-friendly warnings and exceptions. There is also a tightly optimized kernel and task manager that handles all state transitions and abstracts the idiosyncrasies of the Intellivision video chip and Graphics RAM access.n Animation, music, sound effects, and other sub-systems are still in development, as is a "scripting" engine built on top for cut-scenes and pre-defined sequences.

 

This demo, which is about 80% of a simple, single screen game, is only a few lines long (ignoring the comments). P-Machinery does most of the work for the programmer, so that he can concentrate on actual game logic and asset definition. :)

 

-dZ.

 

I think P-Machinery sounds awesome for RAD game development; I like programming games in Abstract Assembly for the reasons you mentioned, being able to focus on the game design. I programmed KC Munchkin Monster Maze and Scrollout Follow the Ball in abstract Assembly with the gameloops just making calls to the runtime/ASDK Framework.

 

Abstract Assembly programming is so RAD it feels like using a high level language in some ways because you're basically calling the same routines a language would call, though using a high level language to call the routines and write the game loop Assembly takes the abstraction further still into the RADness.

 

P-Machinery sounds like it is going in that direction too with the scripting engine - that sounds really cool! What language will you let the users use with the Framework/runtime? Tiny BASIC, or a Tiny C variant?

 

Macros are powerful but can turn out bloated asm; as you implement the scripting language you will have the opportunity to create more faster more optimized blocks of asm from the scripting language (I consider a scripting language to be a more intelligent macro assembler) than the more generalized macro script can do. Some spots it won't matter but that's what I like better about not using them.

 

Is there a link where I can read about P-Machinery and download it to check out?

Link to comment
Share on other sites

P-Machinery sounds like it is going in that direction too with the scripting engine - that sounds really cool! What language will you let the users use with the Framework/runtime? Tiny BASIC, or a Tiny C variant?

 

Right now, it's just Assembly. Eventually (if I ever finish the framework!), I'll consider writing a compiler for a language on top of it, or even adapting IntyBASIC for it. However, as of now, the plan is just to support Assembly Language programmers.

 

Macros are powerful but can turn out bloated asm; as you implement the scripting language you will have the opportunity to create more faster more optimized blocks of asm from the scripting language (I consider a scripting language to be a more intelligent macro assembler) than the more generalized macro script can do. Some spots it won't matter but that's what I like better about not using them.

 

You know, I get that a lot, but the plan of P-Machinery is to abstract the mundane stuff and do it in the most effective way possible. A lot of heuristics are put in place in those macros to determine the best path to take. They are not really just chunks of code, they are actually code generators.

 

Moreover, what a lot of people forget is that this is a very niche and highly specialized domain: writing video games. It is even more specialized by being tuned to a specific (and very minimalist) hardware platform. It is entirely possible to write hyper-optimized frameworks for this domain -- people have been doing it for decades. Just look around the game industry today, and those are highly complex productions. In contrast, we're talking about abstracting the machinery of a little tiny system.

 

Besides, the programmer always has access to the underlying machinery, and the source of the framework is fully commented, so nothing stops him from bypassing the macros. In my experience, though, those who would use something like P-Machinery would most likely not really be the low-level hardcore wizards that would hand-tune their code to maximize performance.

 

P-Machinery, like any other framework, does impose a programming model a structure; it is just a tool, after all.

 

Is there a link where I can read about P-Machinery and download it to check out?

 

Sorry, but not yet. It's still a work in progress. I've written some articles on a few features in the past, like the system-clock and timer sub-system, but nothing major.

 

My plan is to bring it up to a level of utility at which I can start writing a game, and then continue evolving it as the game develops. At that point, I will publish it. It's close but not there yet.

 

Well, it is actually useful for many things right now, it just doesn't offer some key features that I always intended to be the actual differentiators of P-Machinery, like event-driven autonomous sprite movement (set X and Y velocities and the engine updates their positions automatically) and a graphics animation engine. Then there's sound and music processing, high-level object manipulation, etc.

 

If you're really serious about checking it out and would like to try it, send me a PM and I can send you what I have. :)

 

Here are a couple of links:

 

-dZ.

  • Like 1
Link to comment
Share on other sites

 

Right now, it's just Assembly. Eventually (if I ever finish the framework!), I'll consider writing a compiler for a language on top of it, or even adapting IntyBASIC for it. However, as of now, the plan is just to support Assembly Language programmers.

 

 

You know, I get that a lot, but the plan of P-Machinery is to abstract the mundane stuff and do it in the most effective way possible. A lot of heuristics are put in place in those macros to determine the best path to take. They are not really just chunks of code, they are actually code generators.

 

Moreover, what a lot of people forget is that this is a very niche and highly specialized domain: writing video games. It is even more specialized by being tuned to a specific (and very minimalist) hardware platform. It is entirely possible to write hyper-optimized frameworks for this domain -- people have been doing it for decades. Just look around the game industry today, and those are highly complex productions. In contrast, we're talking about abstracting the machinery of a little tiny system.

 

Besides, the programmer always has access to the underlying machinery, and the source of the framework is fully commented, so nothing stops him from bypassing the macros. In my experience, though, those who would use something like P-Machinery would most likely not really be the low-level hardcore wizards that would hand-tune their code to maximize performance.

 

P-Machinery, like any other framework, does impose a programming model a structure; it is just a tool, after all.

 

 

Sorry, but not yet. It's still a work in progress. I've written some articles on a few features in the past, like the system-clock and timer sub-system, but nothing major.

 

My plan is to bring it up to a level of utility at which I can start writing a game, and then continue evolving it as the game develops. At that point, I will publish it. It's close but not there yet.

 

Well, it is actually useful for many things right now, it just doesn't offer some key features that I always intended to be the actual differentiators of P-Machinery, like event-driven autonomous sprite movement (set X and Y velocities and the engine updates their positions automatically) and a graphics animation engine. Then there's sound and music processing, high-level object manipulation, etc.

 

If you're really serious about checking it out and would like to try it, send me a PM and I can send you what I have. :)

 

Here are a couple of links:

 

-dZ.

 

I read both threads, very cool!

 

Excellent choice of plan to write games to the Framework and evolve it during development. :thumbsup:

 

I'm looking forward to seeing P-Machinery games and reading about their development as it progresses.

 

Making P-Framework work with IntyBASIC also sounds interesting.

Link to comment
Share on other sites

  • 4 weeks later...

Ran across this interesting artilcle, why BASIC is better than C

 

The author has a lot of good points, though I do favor modern c implementations like PowerShell that meet the sensible criteria list quite well.

 

Also finished my latest 10 line BASIC game GATES for the international BASIC 10 liners contest - lot's of amazing BASIC games on that page, reminds me of the 80's BASIC programming books with awesome looking screenshots of the BASIC games on the cover.

 

Link to comment
Share on other sites

  • 4 months later...

Ran accross this interesting article in TIME about the creation of BASIC and it's impact:

 

http://time.com/69316/basic/

 

Lots of fascinating stuff, the reaction from contemporary professionals accusing BASIC of mutilating minds is particularly interesting, equally so the response from the inventor of the language.

 

This was the BASIC Allen, Gates and Woz learned in High School in it's original form on Teletype time sharing systems; didn't seem to mutiliate their minds rather a catalyst for creativity :)

Very informative, unfortunately, it is somewhat slow on my laptop. i do not share the opinion that BASIC will lead to degeneration. That's fortran territory

Link to comment
Share on other sites

  • 2 months later...

My first exposure to BASIC was around 1969, when the local college had their annual "open house". It was kind of a, "Hi, what's your name?" program running on a teletype. It was fun, because you could take the print-out of your session home with you as a souvenir. Just this past year, I've become somewhat intrigued by the version of BASIC on the British Acorn Atom computer. It has several features that really set it apart - such as the ability to have inline Assembly Language code that uses the "LINK" command with a label to access the entry point. Here's an example program that converts Arabic numerals to Roman:

 

post-12574-0-76596400-1538852821_thumb.png

 

This image was created by merging screenshots. Just for fun, I made an inverse copy of it, and I think it looks like a cash register receipt:

 

post-12574-0-75178500-1538852835_thumb.png

 

Even without knowing much about how Assembly Language works, or even BASIC for that matter, you can kind of follow the flow of the logic.

 

When I'm experimenting with a different version of BASIC, I have sort of my version of a "Hello World!" that I like to use. I display the numbers from zero to 255, and then the Hexadecimal equivalent, followed by the Binary equivalent, and then the ASCII character (if it's within a certain range). Here's my Atari 8-bit version:

 

post-12574-0-72659900-1538852867.png

 

Notice that I can indent the code within a "for" loop while typing it in, but that is lost when I "LIST" the program. Now, here's the Acorn Atom version:

 

post-12574-0-98306900-1538852918.png

 

Not only does the LISTing of this program retain the indentation, this version of BASIC also allows for the use of inverse letters as labels, so you can change line numbers around without having to go back and change all of your goto's/gosub's.

 

But wait! There's more! There are short-hand equivalents for most commands; the program can be condensed even more...

 

post-12574-0-70826500-1538852965.png

 

I'm aware that several versions of BASIC did this, but something I've never seen a "BASIC" do, is automatically convert decimal to hexadecimal. You simply enter "PRINT I" for decimal, and "PRINT &I" for hex... AND, the print-out is in neat columns with no additional formatting.

 

The one draw-back to the "Atomulator" emulator I'm using, is there is no way to paste text into it. This is possible with the "Beeb" emulator for Acorn's next 6502-based computer, the "BBC Micro"; and that model also allows mixing of BASIC and Assembly. There's also an emulator for the next model, the Atom Electron - which was basically a simplified model of the BBC Micro created as a low-cost model to compete with the computers coming from Sinclair. Sadly, I haven't found an emulator for this one either that allows pasting in source code.

 

You can play with an online JavaScript "Beeb" here: https://bbc.godbolt.org/

...and there's an online "Elk" here: http://elkjs.azurewebsites.net/

 

Here's the output of this program on the Atari:

 

post-12574-0-02672800-1538854675_thumb.png

 

...and here's the output on the Atom:

 

post-12574-0-51239500-1538854943_thumb.png

 

Edited by almightytodd
  • Like 3
Link to comment
Share on other sites

So... I've been taking a look back at the famous David H. Ahl "BASIC Computer Games" book from 1978. I was 18 when it came out; didn't have access to a computer, so I never bothered to buy it. I was aware of it though, and through the years I've browsed through it at bookstores and libraries.

But now I decided to take a look back at it. Images of the pages of the book, and text files of the source code are available here: http://www.vintage-basic.net/games.html So of course, I started with the first featured program "Acey Ducey". My thought was I would edit the code to allow it to run on several of the different emulators I have, including Atari, Commodore, TRS/Radio Shack, Sinclair/Timex, and the Acorn line that I've recently discovered.
But when I pasted the source code (http://www.vintage-basic.net/bcg/aceyducey.bas) into an editor and started looking at it, I quickly realized that this is a horrible example of computer programming. I remember looking at stuff like this years ago and thinking, "Wow, the guy who wrote this must be really smart to keep track of all these "goto" jumps and make the logic all work out". I know BASIC language has a reputation for not being a structured language, but it isn't just the language, it's how you use it. Take a look at this snippet:

 

post-12574-0-67826600-1538861616.png

 

It's the exact same lines of code repeated for card "A" and card "B"! Later in the program, the author does this a third time for card "C". I guess he's never heard of a subroutine... So I decided that rather than simply getting the code to run on different platforms, I would re-write it in a structured way, using string variables instead of in-line literals, and avoiding the use of unconditional "goto's".

The result is a version of the program that is a few lines shorter than the original, even with the addition of several comment lines, and breaking up multi-command program lines (PRINT:PRINT) into separate lines.

Since the book states that each program example was written to be compatible with Microsoft BASIC, I used the "PC BASIC" emulator (http://robhagemans.github.io/pcbasic/) as my test platform and then ported the program with minor changes to some of the different 8-bit home computers I listed above.

 

The PC BASIC emulator doesn't have a way to paste source code in, but what it does do is take a text file as an input parameter using drag-and-drop on the icon. Here's the source file:

 

 

aceyducyV2.bas aceyducyV2.txt

 

Next, I created modifications for some of the Commodore computers, since their versions were licensed from Microsoft. The one interesting change, is that all of the source code had to be converted to lower-case. But after that, for the 80-column PET models, it pretty much runs as-is (the orignal source does too). The VIC-20 was a challenge with its stretched-out 22×23 char/line screen layout.
Both the Commodore and Atari 8-bit models use a "PRINT CHR$()" command to clear the screen (...really guys? Even the lowly ZX81 had a CLS command). For Commodore it's CHR$(147); for Atari it's CHR$(125). I'm also not a big fan of having borders around my screen, so I do some minor color changes to get rid of those.
Here's the program running on a VIC-20 emulator:
post-12574-0-48394500-1538862241_thumb.png
...and here's the same thing, but with the aspect ratio squashed, so it's actually readable:
post-12574-0-14638900-1538862315.png
I think it's a shame that Commodore didn't consider some kind of option to have the 22 characters per line display in a narrow column in the middle of the screen instead of stretching them out.
The main difference for the Atari 8-bits, is that the string arrays have to be dimensioned before they can be used.
Here are some screenshots of the running program...
post-12574-0-98900800-1538862350_thumb.png
post-12574-0-43833600-1538862357_thumb.png
post-12574-0-46161900-1538862364_thumb.png
...and here's the C-64 version (in glorious black & white):
post-12574-0-64715800-1538863466_thumb.png

post-12574-0-43484400-1538863487_thumb.png
post-12574-0-40399800-1538863507_thumb.png
post-12574-0-53015300-1538863521_thumb.png

...now if I could just start getting a handle on 6502 Assembly Language...

 

 

 

 

 

Edited by almightytodd
  • Like 1
Link to comment
Share on other sites

  • 2 months later...

So... I've been taking a look back at the famous David H. Ahl "BASIC Computer Games" book from 1978. I was 18 when it came out; didn't have access to a computer, so I never bothered to buy it. I was aware of it though, and through the years I've browsed through it at bookstores and libraries.

But now I decided to take a look back at it. Images of the pages of the book, and text files of the source code are available here: http://www.vintage-basic.net/games.html So of course, I started with the first featured program "Acey Ducey". My thought was I would edit the code to allow it to run on several of the different emulators I have, including Atari, Commodore, TRS/Radio Shack, Sinclair/Timex, and the Acorn line that I've recently discovered.
But when I pasted the source code (http://www.vintage-basic.net/bcg/aceyducey.bas) into an editor and started looking at it, I quickly realized that this is a horrible example of computer programming. I remember looking at stuff like this years ago and thinking, "Wow, the guy who wrote this must be really smart to keep track of all these "goto" jumps and make the logic all work out". I know BASIC language has a reputation for not being a structured language, but it isn't just the language, it's how you use it. Take a look at this snippet:

 

attachicon.gifA.D.Source.png

 

It's the exact same lines of code repeated for card "A" and card "B"! Later in the program, the author does this a third time for card "C". I guess he's never heard of a subroutine... So I decided that rather than simply getting the code to run on different platforms, I would re-write it in a structured way, using string variables instead of in-line literals, and avoiding the use of unconditional "goto's".

 

The result is a version of the program that is a few lines shorter than the original, even with the addition of several comment lines, and breaking up multi-command program lines (PRINT:PRINT) into separate lines.

 

Since the book states that each program example was written to be compatible with Microsoft BASIC, I used the "PC BASIC" emulator (http://robhagemans.github.io/pcbasic/) as my test platform and then ported the program with minor changes to some of the different 8-bit home computers I listed above.

 

The PC BASIC emulator doesn't have a way to paste source code in, but what it does do is take a text file as an input parameter using drag-and-drop on the icon. Here's the source file:

 

 

attachicon.gifaceyducyV2.bas attachicon.gifaceyducyV2.txt

 

Next, I created modifications for some of the Commodore computers, since their versions were licensed from Microsoft. The one interesting change, is that all of the source code had to be converted to lower-case. But after that, for the 80-column PET models, it pretty much runs as-is (the orignal source does too). The VIC-20 was a challenge with its stretched-out 22×23 char/line screen layout.
Both the Commodore and Atari 8-bit models use a "PRINT CHR$()" command to clear the screen (...really guys? Even the lowly ZX81 had a CLS command). For Commodore it's CHR$(147); for Atari it's CHR$(125). I'm also not a big fan of having borders around my screen, so I do some minor color changes to get rid of those.
Here's the program running on a VIC-20 emulator:
...and here's the same thing, but with the aspect ratio squashed, so it's actually readable:
I think it's a shame that Commodore didn't consider some kind of option to have the 22 characters per line display in a narrow column in the middle of the screen instead of stretching them out.
The main difference for the Atari 8-bits, is that the string arrays have to be dimensioned before they can be used.
Here are some screenshots of the running program...
...and here's the C-64 version (in glorious black & white):

...now if I could just start getting a handle on 6502 Assembly Language...

 

 

 

 

 

 

Very cool almightytodd! That is a most fantastic way to learn - typing in the programs and then even improving them where you see potential! :)

 

Learning programming is an area where BASIC excels and it ties right back to how simple programming routines could be included in science texts and easily used and understood but now there is no equivelent computational medium for educational texts.

 

I really like the ACORN's ability to embed routines piecemeal interchangeably with the BASIC too, awesome functionality and it makes learning Assembly easy by adding the friendly mnemonics directly to the BASIC.

 

I was inspired by the BBC ACORN to add this previoulsy undocumented ability to Atari Flashback BASIC - I've now added it to the manual and have a thread here about it because the Flashback 9 requires an Assembly hotfix, it can be added in this same style:

 

post-30777-0-10853000-1546744591_thumb.jpg

 

I also tried to extend the friendly design further, tieing it directly to gosub and goto since these are direct translations rather than adding the new keyword dolink to call the Asembly routines.

 

Link to comment
Share on other sites

  • 2 months later...
  • 1 month later...
  • 2 weeks later...

Hello

 

Laurent from FRANCE

 

I am fan of BLITZ3D (abandonware), best basic ever (?)

 

In your opinion which is in 2019 the best classic basic langage (with possibility of stand alone .EXE) ?

 

Hello Laurent, I check out BLITZ3D, that BASIC looks awesome! :)

 

What is your perspective on classic and how far back? How about what is "stand alone"?

 

Microsoft Visual BASIC for DOS compiles to a stand alone .exe with the runtime and static libraries built in; some people conceptualize "stand alone" as VB6 which produces an executable that marshals dynamic libraries.

 

Even in the former example the executable cannot truly stand alone as it relies on the libraries in the OS and cannot run without them.

 

While BASIC compilers for classic 8-bit Micro's produced executables that also relied on OS libraries resident in ROM, those libraries were built into the hardware so the executables were effectively stand-alone with nothing else required to run them.

 

batari BASIC, Flashback BASIC and SuperCharger BASIC all compile to produce truly stand alone executable for the Atari 2600 because the machine has no routines in ROM to call, or in the case of SuperCharger BASIC, the ROM transfers control to the executable and no calls need be made to the OS libraries (if they are the executable is no longer stand alone).

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...