Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - Xeda112358

Pages: [1] 2 3 ... 306
1
TI Z80 / Re: YATE - Yet Another Tilemap Engine
« on: June 03, 2019, 12:34:45 am »
Just a minor update: I'm working on making a tilemap editor in Python. It isn't great, but it will hopefully make development easier. I figured that I'd upload my progress here in case someone wants to modify it.

You need to have a "tiles.png", but otherwise you can run tilemap.py to start editing the map and tiles. This program requires pygame and uses Python 2 (not 3 :( ).

Layout:
The left-side 800x800 part of the screen is the tilemap.
The middle 512x800 is the tileset. (actually 512x512 is used)
The right-side is a tile editor

Controls:
Click on a tile and that loads it as the current tile to draw with or edit. Click or click-and-drag on the map area to draw the tile, or on the tile editing area to toggle the pixel. Press the 's' key to save, or the 'l' to load a new map (prompt is in the commandline!).




2
TI Z80 / Re: YATE - Yet Another Tilemap Engine
« on: May 29, 2019, 06:45:47 pm »
Updates!

I now have saving working. Currently the save file is allotted 350 bytes. Whether or not it is archived, it gets copied to a temporary file in RAM. When you exit, the new data is automatically written back to the main save file. In the future, I want this to be a toggleable feature.

I also added in dialog boxes! This was tough to code. Due to the strange screen buffer format, I had to write text and rectangle routines from scratch.

I extended the abilities of the scripting language. Now it can initiate dialog boxes, read or write flags and tiles, and a few other things, probably.

I have now added in two more kinds of events-- one for when the user selects a tile (presses [Enter] to pick up an item or talk to someone, for example), and another for when the map is being loaded. In the example screenshot that I'll post below, map 0 checks two flags to determine if it should erase the flower tile upon loading. map 1 will warp the player out of that map until they've completed the task of bringing a flower.

I've included some defines to facilitate writing scripts in "parse.inc". You can view the (uncompressed) map files for example usage. For example, the map1 event code:
Code: [Select]
;events
.dw onload-$-1
.dw onselect-$-1
;if (y == 4 or y == 5) and x == 7:
;    warp(0,2,5)
.db "y"
num(4)
.db "-"
num(2)
.db "<x"
num(7)
.db "=&i"
warp(0,2,5)
.db 0

onload:
  ifnflag(0)
  jump(getout)
  ;else if flag 0 and flag 1 are set, need to do fancy stuff
  ifnflag(1)
  .db 0
  resflag(1)
  dialog(d_1)
  .db 0
getout:
  setflag(1)
  dialog(d_0)
  warp(0,2,5)
  .db 0

onselect:
  .db 0

d_0:
  .db "Get out stinky child!",c_nl
  .db "Don't come back",c_nl
  .db "without a floral",c_nl
  .db "aroma!",0

d_1:
  .db "Oh! What a lovely",c_nl
  .db "aroma! Please come in!",0

Finally, I added a tool to convert from Pixelscape's tilemap format to the format used here. Just run conv.py, passing the input file name on the commandline.

Here is the screenshot:

For some reason, Chrome renders the last dialog of the gif at the wrong speed.

3
TI Z80 / Re: YATE - Yet Another Tilemap Engine
« on: May 28, 2019, 08:16:32 am »
You'd basically have to write it from scratch, but the general idea isn't too complicated to port.

4
TI Z80 / YATE - Yet Another Tilemap Engine
« on: May 27, 2019, 09:58:09 pm »
Hi all! At Eeems' suggestion, I am posting this work-in-progress code :P

I've been working on-and-off on my tilemap engine since my Pokemon Amber attempt. The current rewrite is slower, but a bit easier to work with on the my end. It also allows the surrounding map to still be animated if I need to pull up a menu or dialog! I've also added in map compression using modified code from my LZD project, as well as event tiles!

In this version, I mess around with OS RAM to make larger chunks of contiguous non-user RAM, so it might cause crashes.

As of now, all you can do is walk around the map and enter/exit map editing mode. Since maps are currently stored in the app, map editing is not permanent. If you leave the map and come back, all changes are reverted.

If you want to edit the map:
Code: [Select]
Press [mode] to enter map editing mode
Use [+] and [-] to scroll through the available tiles for the map
Use [*] to toggle walkability. You'll see corner guards if the tile can't be walked on.
Use [Enter] to set a tile.
Use [stat] to exit map editing

When you hover over a tile that can't be walked over, a solid border will be drawn.
When you walk over an event tile, a dotted border will be drawn.


If you want to *actually* edit or create maps, you'll have to do that on your computer and recompile the app. You'll need spasm (with app signing enabled!) and Python with numpy. The map structure is:
Code: [Select]
.db height,width
 .db start_y,start_x   ;The player will be drawn at y+5 and x+6 !
 .db number_of_tiles
 .dw tile_index_0
 .dw tile_index_1
 ...

;map data
 .db blah
 .db blah
 .db blah
...

;Event code
 .db 0
The map data is actually stored transposed. So the tiles you see across the top row are actually stored in the left column of bytes! This makes it a headache to code.

You can use up to 64 different tiles in a map.

Each byte in the map has the index into the tileset stored in the lower six bits. Setting  the top bit means it can't be walked on, and setting bit 6 means walking into it triggers an event.

When events are triggered, the map's event handler is parsed an executed. It is a very basic scripting language that currently handles addition, subtraction, =, <, &, warping, and conditional execution. Scripts end with a null byte (0x00) and are stored similarly to how they'd be parsed in RPN format, currently with the exception of conditional execution (since the condition is checked before executing). 'x' and 'y' can be used to read the player's current coordinates, 'i' evaluates a condition (like an If statement) and if it is true, it executes the next command, otherwise it skips. 'w' is used to warp and requires bytes in the form of `map_id,start_y-5,start_x-6`. Finally, 8-bit integers are prefixed by a `.db 1`. For a convoluted example, here is the event handler for inside the "house" :
Code: [Select]
;if (y == 3 or y == 4) and x == 7:
;    warp(0,2-5,5-6)
.db "y",_num,3,"-",_num,2,"<x",_num,7,"=&iw",0,2-5,5-6
.db 0


Once you've made your map, you'll need to compress it. First you'll compile it to a binary, then you need to compress that data and convert the compressed data back to assembly. For example:
Code: [Select]
spasm maps/map9.z80 maps/map9.bin
python zlz.py maps/map9.bin maps/map9_comp.z80 map9

And finally, you need to add the map to the mapLUT found near the bottom of main.z80, just add the label name (in the above case, `.dw map9`) and include the compressed version of the file at the bottom.



The system is largely interrupt-based. A custom interrupt updates the current keypress, updates the tile animation, updates the LCD, and draws the tilemap. All you have to do is set the appropriate flag:
Code: [Select]
rpgflags  = 33
updateLCD = 0   ;Tells the interrupts to update the LCD
drawmap   = 1   ;Tells the interrupts to redraw the map
animate   = 2   ;Tells the interrupts to animate tiles
noupdate  = 3   ;Overrides the interrupt's LCD update and tilemap redraw
When a tile's animation counter runs out, it automatically sets the drawmap flag. When a map is redrawn, it automatically set the updateLCD flag and resets the drawmap flag (no need to keep doing it). And if the noupdate flag is set, then interrupt-based map drawing and LCD updating is disabled (but these can still be used manually).

There are three graphics buffers, one of which is quite non-standard. They are all vertically aligned instead of horizontally aligned (the OS' format is horizontal). That means the second byte corresponds to the 8-pixel chunk below the first byte, as opposed to adjacent to it.
Code: [Select]
gbuf     : This is a 1120-byte buffer! It is 80 pixels tall and 112 pixels wide. Only the center part is shown. This is where the tiemap is drawn.
maskbuf : A 768 byte buffer. During LCD updates, the data here is ANDed on top of the gbuf data.
topbuf  : A 768 byte buffer. During LCD updates, the data here is ORed on top of the previous data.

The latter two buffers are used for overworld graphics, like the player sprite, or in the future, dialog, menus, and NPCs, etc.

5
Introduce Yourself! / Re: Greetingz
« on: May 09, 2019, 08:53:54 pm »
Hi, welcome to Omni! It is customary to give newcomers !peanuts

What's your Discord name?

6
While I don't know of a solution for any calc, what calculator is this for?

7
ASM / Re: Miscellaneous ASM Questions
« on: April 25, 2019, 06:34:33 pm »
Oh, good point, I was still stuck on translating the tau tokens at parse time.

I wasn't intending to come off as sarcastic, I just went from point A to C without sharing point B, so to speak. I was thinking context hooks and whatnot, but the hooks I listed are easier to work with. "Only" those hooks seemed like a way easier task to me :P

Your idea(s) are legitimately really clever and I just got really excited when I read it (how I feel whenever I see a really clever implementation or optimization).

8
Other / Re: Variables in TI-84 Plus Ce
« on: April 25, 2019, 08:57:20 am »
That requires a Computer Algebra System (CAS). I don't have that calc, so I am not aware of any CAS or symbolic manipulation programs for it.

9
TI Z80 / Re: Unnamed Optimizing Compiler
« on: April 24, 2019, 01:28:08 pm »
I put it up on GitHub. I added the GotoIf() function, which is really important for a programming language. From this, I'll build blocks like If, While, For, etc.
Here is input code:
Code: [Select]
4->k
0->x
1->y
lbl0:
  x+y->z
  x+1->x
  y+1->y
  Disp(10*z)
  k-1->k
  GotoIf(lbl0,k!=0)
2->x
Disp(x)
And attached is a screenshot of what the program displays.
As cool as that is, the generated assembly code is pretty ugly:
Code: [Select]
#include "jq.inc"
#include "ti83plus.inc"
scrap           = 8000h
var_k           = scrap+0
var_x           = scrap+2
var_y           = scrap+4
var_z           = scrap+6
.db $BB,$6D
.org $9D95
 ld hl,4
 ld (var_k),hl
 ld hl,0
 ld (var_x),hl
 ld hl,1
 ld (var_y),hl
lbl0:
 ld hl,(var_x)
 ld de,(var_y)
 add hl,de
 ld (var_z),hl
 ld hl,(var_x)
 inc hl
 ld (var_x),hl
 ld hl,(var_y)
 inc hl
 ld (var_y),hl
 ld hl,(var_z)
 add hl,hl
 add hl,hl
 ld de,(var_z)
 add hl,de
 add hl,hl
 call disp_uint16
 ld hl,(var_k)
 dec hl
 ld (var_k),hl
 ld a,h
 or l
 jqnz(lbl0 )
 ld hl,2
 ld (var_x),hl
#include "disp_uint16.z80"

10
TI Z80 / Re: HYBRID (8X+)
« on: April 24, 2019, 08:39:44 am »
Oh, great news! I've had a few big projects like that (lost and then years later found a place that had an earlier backup). Good luck; it's a lot of work trying to relearn your old code.

11
ASM / Re: Miscellaneous ASM Questions
« on: April 23, 2019, 02:10:19 pm »
Holy heck that is clever! So all you'd need a token hook, RawKeyHook, and parser hook.

12
TI Z80 / Re: Unnamed Optimizing Compiler
« on: April 23, 2019, 11:23:08 am »
I finally compiled a real program!

With this new iteration of the program, I am using an AST to perform algorithmic optimizations, but I am compiling with a modified version of the original method. Now it is producing better code that is (importantly) correct. My first attempt with this version got really, really slow on slightly complicated inputs-- I terminated the process after two hours trying to compile one line (it wasn't an infinite loop, either; the search space got huge).

So I modified it to allow limiting the amount of paths it kept in memory Keeping a single path would produce code like the previous version, but I found that 60-100 would produce decent code, while 200 produces better code.

It *still* doesn't track state, so there are lots of unnecessary ld hl,() and whatnot.

Anyways, here is what compiling via the commandline might look like:
Code: [Select]
#Convert the code to an intermediate representation
python sy.py test.txt

#Convert the intermediate representation to Z80 assembly, with the ti-83+ family header
python compile.py test.ir -TI8X -MAX_PATHS=150

#Compile
spasm test.asm bin/test.8xp -I inc -I routines

It will take a few seconds to generate the assembly, but it works! Attached is a screenshot of the following code:
Code: [Select]
3->x
6->y
Disp(3*(2*x+y*3)+9*(x+y))
Disp(1337)
Disp(3*(x+y)+9*(x+y))

13
ASM / Re: Miscellaneous ASM Questions
« on: April 22, 2019, 09:34:37 pm »
I'm fairly sure that the parser hook doesn't let you intercept non-function tokens. I could be wrong about that, but I don't think so.

14
TI Z80 / Re: Unnamed Optimizing Compiler
« on: April 21, 2019, 03:45:43 pm »
Looks pretty cool Xeda. Somewhat unrelated, but I was looking up info on Game Boy programming (the Game Boy uses a Z80 clone +/- some things) and apparently you can do C on it via SDCC (or so I've read). Not sure how efficient it is or fast, but it's a thing.
SDCC produces working, but somewhat inefficient code. One of the lofty goal for this project is to be able to convert LLVM IR code to (efficient) z80 assembly, for which there are existing C-->LLVM IR programs.

I know jacobly has done some pretty good work on llvm-z80, producing some pretty nice code, but there are some shortcomings still.

EDIT: I forgot to update :P I'm tired.

I'm running into a problem with the AST method. I'm trying to compile directly from the AST, and while the code seems correct, it isn't as efficient. The reason for this is that I haven't figured out an analogous algorithm that will work on ASTs without needlessly searching the entire set of potential codes. Most operations have four to eight implementations using different inputs/outputs. so, even compiling a short 9-token expression like (4+x)*3+7-y could have hundreds of thousands to millions of potential paths. The previous algorithm is able to drastically reduce the search space.

That said, I'm still able to get more clever with algorithmic optimizations. For example, if you try something like 3*(x+1)+5*(x+1), it will be optimized 8*(x+1) and then (x+1)<<3, producing the code:
Code: [Select]
ld hl,(x)
 inc hl
 add hl,hl
 add hl,hl
 add hl,hl
 call disp_uint16

However, 3*(x+1)+9*(x+1) will convert to 12*(x+1) and try to make a routine faster than calling multiplication since only two bits are set in 12, producing this code:
Code: [Select]
ld hl,(x)
 inc hl
 ld bc,(x)
 inc bc
 sla c
 rl b
 add hl,bc
 add hl,hl
 add hl,hl
 call disp_uint16
With the other method, this code would have compiled 12(x+1) to something like:
Code: [Select]
ld de,(x)
 inc de
 ld hl,(x)
 inc hl
 add hl,hl
 add hl,de
 add hl,hl
 add hl,hl
 call disp_uint16
It still isn't optimal since neither algorithm keeps track of the current state aside from input and output, but the latter code tries more code paths, even if they start out suboptimal. The AST-based algorithm makes local optimizations, while the previous algorithm took into account the whole code.

For reference, this is how I, a human (I swear), would implement it:
Code: [Select]
ld hl,(x)
 inc hl
 ld b,h
 ld c,l
 add hl,hl
 add hl,bc
 add hl,hl
 add hl,hl
 call disp_uint16
The main difference is that I take advantage of the fact that X+1 is held in HL, so I don't need to compute it again to put in BC or DE. This is because I am taking into account "state." That is, I know that HL already contains the value I want, whereas the compilers currently only know that HL holds some value.

One thing that I learned about in looking into LLVM is the idea of Static Single Assignment (SSAs), which I think will make it a lot easier to track state and optimize accordingly, providing a great improvement in code quality. The challenge with that is trying to optimally allocate variables, using registers, stack space, or RAM.

15
ASM / Re: Miscellaneous ASM Questions
« on: April 21, 2019, 03:35:41 pm »
Oh, you are right about that. That would be a little more difficult as you would have to resize variables.
When the parser hook triggers mode 0, the name of the program being parsed is in basic_prog. You could first scan for all tau tokens that can be directly replaced (and replace them), while counting all of the ones that need extra work. Then verify there is enough memory to insert an additional two bytes for each remaining entry. Use InsertMem at the end of the program, but remember to adjust the variable's size bytes as well as basic_end. Finally, replace the remaining tau tokens with "(2pi)".

It is a complicated process, so good luck!

Pages: [1] 2 3 ... 306