I really like the idea of the conditional codes. It's something i really like about ARM assembly, pretty much any instruction can take conditions. So you can do things like addc r1,r2 or even cmpz r1,r2. You can actually also add shifts, so you can do addc r1,r2,r3 LSL 4, which adds r2 + [r3 shifted to the left 4 bits] and stores the result in r1, but only if the carry flag is set (that's one instruction!)
Right now, what does sMask do? I assumed that a sprite wouldn't be processed unless you turned on that sprite's corresponding sMask bit. If i set the sprite delay to 1 and only have one sprite set in the sMask, it'll still process the other 7 sprites before coming back to the one i've enabled?
Yes, that is how sMask works. Processing is rather quick if the sprite is not to be drawn, though.
Hehe, I have a double buffer routine ready, but in the current version I just use the horribly slow OS routine _GrBufCpy. The way I plan to implement it should update the screen slightly faster than the code linked to, only because I keep the habit of setting the X coordinate at the beginning of the program (the one that is 0 to 63). Since my routine returns with it being the same, there is no need to keep setting that coordinate.
I think your other ideas are also good, in particular updating the LCD after each sprite is drawn will probably cause quite a bit of a slowdown. Waiting until all sprites have been drawn before updating is probably a good idea. I was also wondering if bit-instructions were going to be added
Currently, you can set bits using orc() and reset bits with andc() and toggle with xorc(), but testing bits is more complicated without modifying RAM, so that is why I wanted to add the bits() command.
There is one thing that is very lacking with this system and that is pixel testing for collision detection. However, we can use the sprite coordinates to figure out if they collide with each other.
EDIT: I started work on rewriting the whole code for the new instruction set. I only have a few more instructions to finish and it is only 472 bytes. I still need to add in two larger pieces of code, though, so I think the size will be around 900 bytes when finished. It should be a bit faster than before, too, and coding should be cleaner.
I have an issue with the keypresses and it apparently isn't reading the last key group. I have an issue with the sprite drawing (I forgot to include clipping), so this probably caused any crashes. The reason for why it seems like you aren't moving the sprite is because with a sprite delay of 0, it is actually being interpreted as 256. Essentially, when you press a key like the left key in your code, it is incrementing the X coordinate a bunch of times before the update Without updating the sprite so often, code is executed pretty quickly. I think it averages about 10000 instructions per second. To set up the update timer, try:
ldirc(keyMask,4) .db 3,1,1,1 The last '1' says to update the next sprite at each instruction. Keep in mind that it cycles through each sprite one at a time, so Sprite0 gets updated every 8th instruction. With a delay of 256, there was time for 2048 instructions to execute and pressing down looped through 4 instructions in your code meaning Y could get incremented 512 times before updating the sprite.
To answer your question about making an easier way to make/edit programs, I should have probably mentioned that the code can be in a program or appvar. So if you can get some kind of assembler to compile the code to a program, you can do something like:
"[JADETEST":Asm(prgmJADE And that will execute the program named JADETEST as a JADE program. Use '5' instead of '[' to execute an appvar.
Now I have a few propositions for changes to the current instruction set and port setup. After trying to code in this language, it is obviuos that it is rather clunky. So I want to use an idea that Runer suggested that might make the code smaller and faster. Right now, all of the instructions fit in 6 bits, but take up a byte. I could make all of the instruction have flag arguments. For example, you could do something like this:
Loop: cpc(key0,kDown) incz(sY0) cpc(key0,kUp) decz(sY0) cpc(key0,kLeft) incz(sX0) cpc(key0,kRight) decz(sX0) cpc(key1,kClear) ldcz(status,$80) jrb(Loop) And that would be the code to move the coordinates of the sprite, which is 20 bytes compared to 50 bytes.
If I do this, then we will free up room for other types of instructions, too. For example, I think an instruction to set, reset, or toggle a flag is useful, as well as these commands:
bits(const,addr) ;will perform bitwise AND logic, but doesn't change (addr), only flags. Useful to see if one of the bits is set. inv(addr) ;invert the bits rotl(addr) ;rotate the bits left rotr(addr) ;rotate the bits right shftl(addr) ;shift the bits left shftr(addr) ;shift the bits right Here is my proposed outline for a new instruction set:
0123456789ABCDEF 0lda()adda()adca()suba()sbca()xora()ora()anda()cpa()inc()rotl()shftl()pusha()pop()inv()ldira() 1ldc()addc()adcc()subc()sbcc()xorc()orc()andc()cpc()dec()rotr()shftr()pushc()ex()bits()ldirc() 2ret()setz()setc()togz()togc()jp1()jp2()jrf()jrb()call1()call2()callf()callb() 3 ;Duplicate for execution on the c flag condition 4x,5x,6x,7x ;Duplicate for execution on the z flag condition 8x,9x,Ax,Bx ;??? Cx,Dx,Ex,Fx There will then be room for 19 more instructions and possibly 64 more depending on what is done for the last set. As a quick note,these instructions are not included:
togzz()togcc() For example, togzz() will only toggle the z flag if the z flag is set,so it only resets the z flag.
Proposed changes to the ports: We need a better way to handle sprites. I have a few ideas:
Draw a sprite only if sMask has the bit set and sUpdate has the bit reset.
Implications: When a sprite is drawn, sUpdate gets the corresponding bit set. This means that in order to update the sprite again, you need to reset the bit, essentially acknowledging that it has been drawn. This will allow the programmer to do proper cleanup on the LCD.
Only update the LCD when bit 0 of the status port is set and Sprite7 has been drawn.
Implications: It is faster than updating the LCD every time a sprite is drawn and looks cleaner.
When the LCD is updated, reset bit 0 of the status port.
Implications: All the sprites are drawn, so if you need to cleanup the buffer you can without the screen being updated.
If these changes are made, the sTimer port can be removed and updates will be made as the ports are edited.
So if all of these changes are made, here is an example routine for moving a sprite:
#include "jade.inc" .org $00 jrf(start) sprite: .db %00111100 .db %01100110 .db %11000011 .db %11100111 .db $FF .db $FF .db $FF .db $FF start: ldirc(sprite0,5) .dw $100+sprite \ .db 0,0,2 ldirc(keyMask,2) .db 3,1 Loop: orc(status,1) ;set bit 0 of the status port KeyLoop: cpc(key1,kClear) ;Check if clear is pressed ldcz(status,$80) ;Turn off Jade if the z flag is set by writing 80h to the status port cpc(key0,-1) ;test if no keys are being pressed jrbz(KeyLoop)
callf(SpriteWait) ;In case not enough time has passed for the sprite to be drawn ldc(sUpdate,0) ;Acknowledge the sprite drawing, allowing it to redraw the sprite (XORing twice results in no change) callf(SpriteWait) ;We need to wait until the sprite is drawn before updating coordinates
cpc(key0,kDown) incz(sY0) cpc(key0,kUp) decz(sY0) cpc(key0,kLeft) incz(sX0) cpc(key0,kRight) decz(sX0) ldc(sUpdate,0) ;Acknowledge any sprites drawn, allowing the updated coordinates to be displayed jrb(Loop)
SpriteWait: cpa(sUpdate,sMask) ;Check if all of the sprites are drawn retz() jrb(SpriteWait)
Matrefeytontias: This is amazing o.o It works so smoothly! I had just finally figured out 3D stuff yesterday, so I made a BASIC program, but this is gorgeous o.o I am now looking forward to the upcoming TI-Concours stuff in Axe/Assembly If we do Snake or something, are you going to make it 3D?
The two LDIRs are different in that one writes to a fixed address (1F.address.size.data) and the other uses a byte in RAM to figure out where to write the data.
And yes, I saw that I had an extra sMethod after I got offline, sorry .__. Your assembly translation is exactly correct, too!
The extra 3 after the LCD update thingy is something I forgot to document and that is the frequency for updating the sprites. 3 means it updates a sprite every three instructions, so this can really slow down code as you might imagine. As a note, this doesn't update all of the sprites at once, only one sprite at a time. It also updates the LCD when each sprite is drawn, so I should probably change that to update only after all the sprites are scanned.
Also, looking at your code, I like your naming syntax better for cpc, ldc, and ldirc. It makes more sense. I was actually worried that cpc() would not work, so I am glad that it does!
For the .inc file, that is lovely! Any ideas on a good port 34h? Also, saveSScreen is typically 86ECh, but it will work on 9872h, too.
Another thing that I realised is that savestates can be 128 bytes or 256 bytes, depending on the RAM being used.
That looks awesome and is definitely pretty close to what I was talking about. I was more going for what genetic algorithms are, I think. So basically, the computer tries out different types of code to achieve a task and the 'organisms' interact with each other using their 'genetic' code. With the idea that the best suited for the task will survive, you should eventually get a fairly well coded routine for your task. I think this process has been proposed for creating quantum algorithms.
Also, finally some content! I haven't added in any of the ports, so you cannot exit Jade yet and you cannot see what is going on with the sprites, but the instructions appear to be working. There could be issues I haven't caught yet, but the source code is below. It is currently pretty ugly and not very optimised, but I will worry about that later. If you use the code from the previous routine, it will do what it is supposed to do, too. (Set up the keymask port and spritemask port and then set up the first two sets of sprite ports, then enter an infinite loop).
As a note, I haven't documented the hex codes for all of the jumps and calls yet.
Spoiler For Source:
#include "ti83plus.inc" #include "Jade.inc" #define progStart $9D95 .org progStart-2 .db $BB,$6D bcall(_RclAns) sub 4 ret nz ex de,hl ld c,(hl) inc hl ld b,(hl) inc hl ld de,OP1 ldir ld (de),a bcall(_ChkFindSym) ret c ld ix,saveSScreen ld bc,0 ld hl,saveSScreen ld (hl),c inc hl djnz $-2 ex de,hl ld b,2 inc hl inc hl ldir ;now all of the ports and RAM are loaded ld ix,saveSScreen+128 MainLoop: ld hl,MainLoop push hl call GetInc ld a,%00100000 and c jp z,CMD0 CMD1: bit 4,c push bc push af call GetInc pop af ld d,0 jr nz,$+8 ld e,c ld hl,saveSScreen add hl,de ld c,(hl) ld e,c pop bc ld a,15 and c jr z,callf+3 ;jrf dec a jr nz,$+6 ;jrf c bit 0,b jr nz,callf+3 dec a jr nz,$+6 ;jrf z bit 1,b jr nz,callf+3
dec a jr z,callb+3 ;jrb dec a jr nz,$+6 ;jrb c bit 0,b jr nz,callb+3 dec a jr nz,$+6 ;jrb 1 bit 1,b jr nz,callb+3
;calls
dec a jr nz,$+8 ;callf callf: call PushPC add hl,de jr SetPC dec a jr nz,$+6 ;callf c bit 0,b jr nz,callf dec a jr nz,$+6 ;callf 1 bit 1,b jr nz,callf
dec a jr nz,is_callbc ;callb callb: call PushPC or a sbc hl,de SetPC: ld a,h and 1 ld h,a ld a,(PClow+1) and $FE or h ld h,a ld (PClow),hl ret is_callbc: dec a jr nz,$+6 ;callb c bit 0,b jr nz,callb dec a ret nz ;callb 1 bit 1,b jr nz,callb
PushPC: ex de,hl push hl ;DE is the value to push call PushByte ld a,e ld e,d ld d,a call PushByte ld h,e ld l,d pop de ret PushByte: ;E is the value to push ld a,(stackptr) push af and 7Fh ld hl,stackbase add a,l ld l,a jr nz,$+3 inc h ld (hl),e pop af inc a ld (stackptr),a ret ;DE is offset ;HL = PC
CMD0: ld a,c cp 12 jr nz,CheckRetC PopPC: call PopByte ld d,e call PopByte ld a,(PClow+1) and $FE or h ld h,a ld (PClow),hl ret PopByte: ld a,(stackptr) dec a and 7Fh ld (stackPtr),a ld hl,stackbase add a,l ld l,a jr nz,$+3 inc h ld e,(hl) ret CheckRetC: cp 13 jr nz,$+7 bit 0,b jr nz,PopPC ret cp 14 jr nz,$+7 bit 1,b jr nz,PopPC ret push af call GetInc ld a,c ld hl,saveSScreen add a,l ld l,a jr nc,$+3 inc h pop af push af push hl and 15 ld hl,MathLUT add a,a add a,l ld l,a jr nc,$+3 inc h ld e,(hl) inc hl ld d,(hl) pop hl ex de,hl pop af ;A has the main instruction ;DE points to the byte that the operation is on ; call JPHL ; jp MainLoop JPHL: jp (hl) instrLD: call GetNumOrAddr ld (hl),a ret instrADD: call GetNumOrAddr add a,(hl) SetFlags: ld (hl),a ld b,0 jr nz,$+4 inc b inc b ret nc inc b ret instrADC: call GetNumOrAddr rrc b rlc b adc a,(hl) jr SetFlags instrSUB: call GetNumOrAddr neg add a,(hl) ccf jr SetFlags instrSBC: call GetNumOrAddr neg rrc b rlc b adc a,(hl) ccf jr SetFlags instrXOR: call GetNumOrAddr xor (hl) jr SetFlags instrOR: call GetNumOrAddr or (hl) jr SetFlags instrAND: call GetNumOrAddr and (hl) jr SetFlags instrCP: call GetNumOrAddr cp (hl) jr SetFlags instrINC: bit 4,a jr z,$+5 dec (hl) jr SetFlags inc (hl) jr SetFlags instrPUSH: ld e,a jp PushByte instrPOP: push de call PopByte pop hl ld (hl),e ret instrRET: instrLDIR: cp 31 jr z,$+5 ld a,(de) jr $+7 ld a,saveSScreen ;LSB->A sub e neg ld l,a ld h,0 dec l push bc push hl call GetInc ;C is the size of the data ld b,c LDIRLoop: call GetInc pop hl inc l push hl ld de,saveSScreen add hl,de ld (hl),c djnz LDIRLoop pop bc pop bc ret
;B always contains the flags GetNumOrAddr: ;HL is what DE was input as ;Returns A as the value push de push af call GetInc pop af pop hl bit 4,a ld a,c ret nz ex de,hl ld hl,saveSScreen add a,l ld l,a jr nc,$+3 inc h ld a,(hl) ex de,hl ret MathLUT: .dw instrLD .dw instrADD .dw instrADC .dw instrSUB .dw instrSBC .dw instrXOR .dw instrOR .dw instrAND .dw instrCP .dw instrINC .dw instrPUSH .dw instrPOP .dw instrRET .dw instrRET .dw instrRET .dw instrLDIR
GetInc: ;Outputs: ; C as the next byte ; PC incremented ; HL = PC ld de,(PClow) ld a,d ld c,d and 1 ld d,a ld hl,saveSScreen+256 add hl,de res 0,c inc e jr nz,$+3 inc c ld d,c ld c,(hl) ex de,hl ld (PClow),hl ret
;RAM equates stackptr equ 2Ah+saveSScreen PClow equ 2Bh+saveSScreen ;** note that the upper bit of PC is in the status port
stackbase equ 80h
Spoiler For Instruction Set:
;Instr arg1 arg2 arg3 hex ld (addr1),(addr2) 00xxyy add (addr1),(addr2) 01xxyy adc (addr1),(addr2) 02xxyy sub (addr1),(addr2) 03xxyy sbc (addr1),(addr2) 04xxyy xor (addr1),(addr2) 05xxyy or (addr1),(addr2) 06xxyy and (addr1),(addr2) 07xxyy cp (addr1),(addr2) 08xxyy inc (addr1) 09xx push (addr1) 0Axx pop (addr1) 0Bxx ret 0C ret z 0D ret c 0E ldir (addr1),size, data 0Fxxyy[data]
ld (addr1),const 10xxyy add (addr1),const 11xxyy adc (addr1),const 12xxyy sub (addr1),const 13xxyy sbc (addr1),const 14xxyy xor (addr1),const 15xxyy or (addr1),const 16xxyy and (addr1),const 17xxyy cp (addr1),const 18xxyy dec (addr1) 19xx push const 1Axx ;not added yet ex (addr1),(addr2) 1Bxxyy ;not added yet ldir addr1 ,size, data 1Fxxyy[data]
jrf const 30xx jrb const 33xx
Currently the program is 522 bytes, but I still need to add in a decent sprite routine (I will probably use some heavy SMC).
Yay, the ports have been added I made a more complicated program and I changed the port values so now each group of sprite ports is five bytes (the fifth is for the drawing method). The code isn't clean, but it shows that it is working. The code in the screenshot increments the x coordinate of the first sprite until [Clear] is pressed. It doesn't wait to acknowledge if the sprite has been drawn which is why I consider it to not be cleaned up code.
Also, the hex is compressed using BatLib, so if you want to give it a try, you will need to use BatLib or some other method of compressing the code.
Haha, that does sound like something he would do o.o It is like a complicated cellular automata. Actually, if I do figure out something that works, I might have to use this for a talk that I should be giving about the subject.
As an update, I am about to run the code through ORG to see how many mistakes I have made with it. If no mistakes have been made, then I have completed the assembly version emulating the instruction set and I will need to add in ports (which should be rather trivial).
Ooh, maybe I will have time today to continue with the assembly version. It actually reads the code in compressed hex form and parses it that way, so if I do not make an editor, it might be a pain to program. Another neat application of this project that I thought about was to create a different CPU and port system with only 256 bytes to work with in all and an even more simple instruction set. Because it cannot damage the calculator, you can fill the code with random data and see what happens. The ports could be for returning how close the nearest pixel is in a given direction and for incrementing an X and Y coordinate. Because it is 256 bytes, you can fit a good number of CPUs in RAM and you can make them all run in parallel and see if they interact on screen. We could set some conditions for survival, death, and procreation, and see if they create anything semi-intelligent
I have begun the process of converting this to assembly now, so once it is complete, I will be able to have a copy of it on my computer (and so I can upload it here). I currently have all of the jumps and calls emulated and now I just have the math operations to finish and the others random things. For an example of the code that I wrote (in hex on my calc) that was emulated:
3020 ;jrf 32 ;jumps over the next 32 bytes <32 bytes of masked sprite data> 1F2802 ;ldir keymask,2 ;copies two bytes to the RAM location at keyMask (these are ports keymask and sMask) 0303 ;.db 3,3 1F0008 ;ldir sLSB0,8 ;copies 8 bytes to the first two sets of sprite data ports 02000303 ;.dw 2 \ .db 3,3 ;location of the sprite, coordinates 12005535 ;.dw 18 \ .db 85,53 ;location of the second sprite, coordinates 3302 ;jrb $ ;jumps back two bytes to the start of this instruction. Causes an infinite loop.
Yes, that is normal and for the reason chickendude gave. Basically, it is considered a hidden variable. I like the idea of changing the case of the letters. It is a good way of making a unique name
It is kind of like a newline, but not really. You can use a colon in conditions for If, While, and Repeat, among other things. A colon just separates chunks of math.
The simple answer is that it makes things so much easier to handle, you get a ton of variety in commands, and there are a lot less instructions. For example, you can use 32 bytes of RAM as variables and perform all math operations on any combination of variables using the same instructions.
Yes, RecallPic has those abilities, currently and that will definitely be still around for Grammer 3. I might have separate tokens like xorSprite(), though.
EDIT: @Sorunome: Grammer 3 has a variable type for sprites, so definitely. Also, I like the idea of having static pointers and about the logic, I should do that for sure. Maybe I will have bitand, bitor, and bitxor instead. Currently, you no longer need to use + or *, as long as your tests produce a 1 or 0. For example, this works as you would expect it:
Earlier I tried making the sprite grayscale, but it was pretty laggy when the screen scrolled. I know how to fix it, but it would be a bit of a pain to implement it
I just wanted something to try out, but it definitely provides a safe way for people to use an assembly-like language. There is no risk of it editing any other data outside of saveSScreen. I might turn this into an app with a bunch of silly features like a debugger, hex editor/viewer, and program editor since the instruction set is ridiculously easy to create a compiler for. On top of that, games or game packs are limited to 512 bytes and they can be archived. Even if they were in RAM, they require no additional user RAM to run. This means that you could fit 32 "roms" in a single RAM page which would be great for TI-83, 82, and 82 STATS users!
Programs that I think are quite feasible: Pong A tunnel game Tic-Tac-Toe A Space Invaders like game Block Eater (a game I made) Maybe pacman
And I do think these are feasible in 512 bytes, but my thoughts might change