« Last post by c4ooo on Yesterday at 03:44:33 am »
hey (mostly) take the shortest path to the player from their starting position, and do it pretty fast too! With 2 enemies utilizing the routine, and a 3rd one moving without it, I get an uncapped FPS of about 450, which I find very impressive
Ideally you should use VBOs and shaders for all rendering and do the enemy AI in a separate thread. Even if not exactly necessary for this project, as you get enough FPS already, learning how to "properly" render with VBOs and shaders will be a big help for more advanced SDL/openGL projects
« Last post by 123outerme on Yesterday at 03:32:52 am »
I've been working on this still, even though I've been rather silent. What's here isn't even a big update, but I've been preparing the engine for several more big things. After those, almost all work on the engine is finished, and then off to map-pack making, which should hopefully be easy after all the work I did on the toolchain program.
I did a lot of "grease" work, aka lots of engine work for things you won't directly see. I've been working to ensure the Linux/GNU version actually runs, reorganizing some code I made before this project, and more stuff. Here, I actually get to show it off. I've added text input, so instead of having to input various data in the command line, you no longer need that. Another thing I did was revamp the Gateway transition slightly and added a boss into transition.
« Last post by _iPhoenix_ on March 17, 2018, 06:54:14 pm »
Some of you may remember my old TI-BASIC raycaster. It was made back when I wasn't that good at the language, and I think I broke it with some long-forgotten edit. I was reminded of this by a recent post on Cemetech by someone who was trying to understand my old code to try and make it work.
They weren't very successful. It was a crappy, unreadable script that completely ignored the points that make a good TI-BASIC program. In short, it was a prototype.
A few months ago, I happened to come back to this concept and completely rewrote my code for it. Here's my complete new code (import this with SC3):
DelVar DRepeat K=45
DelVar NRepeat Ans or N=10
In concept, this is essentially the same program, but I like it much better because it is more concise and readable. It also works. That's somewhat important.
To turn, you use the top 5 buttons on the keypad, namely [y=], [window], [zoom], [trace], and [graph]. To move forward, press [zoom]. To move forward faster, press [del].
This code is not the most optimized I could make it, for the sake of making a understandable tutorial. This tutorial/explanation assumes you understand at least a little trig and TI-BASIC.
Let's break this program down into manageable chunks, which I will explain one-by-one.
This section is pretty much my standard "initialize the graphscreen" set of commands. I store the user's settings to GDB0 to retrieve them when the program is over.
The line storing colors of increasing brightness to L1 is important. It will be explained thoroughly later in this post.
This code loops through matrix A, drawing a 5x5 "pixel" on the minimap in the bottom left corner of the screen if the value is truthy (i.e. not 0). We use this minimap to do the raycasting.
The variables A and B store the row and column (because we are using pixels, they are not the X and Y coordinates) of the player. The D variable stores our rotation. Because DelVar effectively sets it to 0 and 0° is directly left, the player starts out facing left.
Here, we start the main loop. We clear the area where the compass will be redrawn each frame.
This draws a nice little compass in the top right corner of the screen, so that the player knows which direction he or she is facing.
Here is the start of the meat of the program.
We sweep through an 80° arc, centered around the player's rotation value.
The [mono]If not(remainder(abs(theta-D),10[/mono] part and the line following it draw markers on the compass to show the progress. The If statement makes sure that we only draw lines at 10-degree intervals.
As an extra layer of optimization, we store the sine and cosine values of our angle to U and V, respectively, so that we don't have to recalculate them in the next step.
DelVar NRepeat Ans or N=10
This is where the actual raycasting happens.
I reset the variable N to 0. It stores the length of the ray.
We basically keep increasing the radius of an imaginary circle (centered on our player) by 1, calculating the point on this circle at our angle, θ, and checking if there is a pixel drawn there. To save time, if the circle has a radius equal to 10 and we still haven't hit anything, we stop calculating the ray. The value of 10 is essentially our "maximum render distance".
A raycaster basically casts a ray from the player's position at each angle in the player's FOV, measuring the distance the ray travels until it hits a wall.
This is most of our drawing code, and also the ending of our drawing loop, where we were iterating through each of the angles.
We clear out anything on our "canvas" where the line is going to go. If we didn't prevent the ray from continuing to propagate, we draw a line on our graph. The X coordinate is reliant on the angle, while the vertical length of the line and the color of the line is solely influenced by the length of the ray. If the ray took longer to hit a wall, the line should be shorter and lighter in color, because it is further away.
You might have noticed that we never actually drew the player in on the minimap yet. This is because some rays would hit the player and register as there being a wall right where the player is, even if there isn't one. If we didn't stop the ray from continuing to propagate, we can safely draw this in now. I first do a check to make sure that we are not inside of a wall. I should probably add player collision checking, but it's a trivial thing in this scenario.
This section of code heads our user input.
The first getKey prevents any keys that were pressed during the drawing from being registered in our movement-handling code. I don't fully understand how or why this happens, but it does.
We then wait for the user to press a key, and store the key code they pressed to K.
Next, we turn off the pixel on the minimap representing the player. I have explained why we need to do this earlier in the post. The next section, which I intentionally left unoptimized, adjusts the rotation of the player. If the user presses the [y=] button, we assume they want to rotate more than if they pressed the [window] button. After this section of code, we move the player.
If the player pressed the [zoom] button, we move them forward by (approx.) 2 pixels. Because the rendering process is so slow, though, we also give them the option to move forward by 5 pixels by pressing [del].
This last little bit of code closes up our main loop. When the user presses [clear], the loop stops (because of the "K=45" at the beginning), and we recall the user's graph settings back.
If you enjoyed reading this, it helped you, or you need help understanding something, please do not hesitate to reply below. If you do not have an account yet, they are free and only require a few minutes of your time to create.
« Last post by _iPhoenix_ on March 17, 2018, 04:58:59 pm »
I got a ton done since I last posted! Here's a changelog.
- Code and Essay length are now stored and re-drawn correctly.
- Google Docs screen updated to better reflect the improved Google Docs. I really like how it turned out, despite the fact that it still needs work.
- Optimizations- Program is now 10% faster, according to my tests.
- Added a clock! Game is now 10 minutes long, excluding screen redraw times and the "Pause Clock" upgrade effect.
- Added names to the upgrades, as well as a shop info thingie.
- Added upgrade functionality to certain upgrades:
- Point Booster: Dramatically increases minimum number of points received while writing code.
- Pause Clock: Pauses the countdown clock for 30 seconds immediately after purchase.
- Grammar Check: Increases the speed at which you can write your essay.
- (planned) First-Person War Narrative: Decreases the rate at which you get bored, increases essay writing speed, and significantly slows down (not freezing) the clock.
- If you are too bored, you no longer are able to complete your essay and need to do some programming.
It took me an extremely long time to figure out how I could pause my clock in TI-Basic for a certain duration of time. It's relatively simple to pause the clock while other code executes, as shown below.
//Note that the startTmr token is essentially a variable holding a number that represents the current
//time, and the checkTmr( command essentially takes the current value of startTmr at execution and
//subtracts the value stored in the provided variable from it.
//Initialize a timer to variable A by storing the current time to A
<put code to execute while clock is running here>
//Get the amount of time elapsed since the time was stored to A, and store it to B, for safe keeping. Optionally, you can even store it back into A.
//note that the clock still technically runs here, so none of the calculations here can use the timer stored in A.
<put code to execute while clock is paused here>
//Take the current time, and move backwards B seconds, storing it back into A
Having it pause for a certain duration is much harder, and I leave that as an exercise to the reader. I'm sure some form of microscopic code snippet for what I am trying to do is posted somewhere, but I'm quite proud of my solution nonetheless.
I also want to say that you can skip the intro by pressing [sto->]. You could do it in the last update, but I forgot to post it.
I updated the GitHub repo, and the previous challenge still applies. If you can shave off some bytes in the program, I will include your username in the README, as well as the number of bytes you shaved off!
Believe it or not, the code was at 4500-ish bytes, but I have since shaved off about 800 bytes off of the already-optimized program, which I am extremely proud of.
« Last post by Juju on March 15, 2018, 09:13:23 am »
As I mentioned earlier on Discord, the 286 is a 16-bit CPU and as such can't address more than 2^16 bytes (64 KiB) of memory at once, so you can't just dump the entire image to the graphics adapter all at once. However, you can change the segment of memory the CPU can see so 2^24 bytes (16 MiB) is accessible. (Older Intel CPUs had a 20-bit address bus, though, starting from the 80286 they upgraded it to 24, but you have to enable the last 4 bits with the A20 line for compatibility with older software who expected the memory space wrapping around after 1 MiB.) In DOS, the first 640 KiB of it is directly mapped to the RAM, while the rest is memory-mapped I/O and you need a memory manager software to map the rest of the RAM there. (You can also access more than 16 MiB of RAM with some sort of bank switching, but it's starting to get complicated here.) (Read more about it: https://en.wikipedia.org/wiki/DOS_memory_management
A 1024x768x8 picture is 768 KiB, so theorically, you could fit it all into RAM, but you'd have to segment switch every 64 KiB, avoid the space that isn't mapped to RAM and use a memory manager. It's a big mess, really. And even then, unlike CGA, EGA, VGA and the like, which merely maps the screen somewhere into the memory-mapped I/O space so you can just copy your bytes on it as if it were RAM, the 8514 has a GPU you'd have to send commands to (which is likely why you need the AI layer), and I don't think you can send all your bytes at once to it either.
So I guess your best bet is probably streaming all your file reads directly to the graphics adapter, byte by byte, or chunk by chunk if you can. That's it, read a pixel, draw it and loop for every pixel. If it's a rectangle function, you might even want to use some sort of RLE as @Jarren Long
here described, as in, if you encounter say 16 pixels of the same color, draw a rectangle 16 pixels long. RLE decoding this way would be extremely simple.
« Last post by Jarren Long on March 15, 2018, 06:51:08 am »
If you don't want to split up your image, you might be able to get away with using some simple compression on the bitmap, like Run-Length Encoding (RLE). If your image has scanlines that have the same color repeated over multiple pixels, RLE could reduce your image size quite a bit, which would allow you to load the whole thing in memory. Example being that, if the first scanline of the top of your bitmap is all the same color (we'll say black, 0x00 for this example), you could RLE that entire line, and store it in 8 bytes instead of 1024 (4 bytes of actual color, and 4 bytes for the number of times to repeat that color). That would look like FF00FF00FF00FF00 (read it as "256 pixels of color 0x00, repeated 4 times), instead of 1024 00's. At that point, you would just need to update your code to parse the RLE pixels and reinflate the bitmap on the fly while you render it. Though, you're just sacrificing clock cycles to spare memory by doing that.
Extra credit: instead of encoding the image beforehand, write code that will RLE the bitmap as you read it in.
That would solve the image size issue at least, so long as RLE would be appropriate for your image. If you're trying to display something like a big color gradient, your S.O.L., RLE would actually make the image larger in size.
For drawing more than one pixel at a time with the library you have, you'll need to dig into the API to see how it accesses the video buffer to write out to the graphics area of memory, and then reproduce it yourself. The hardware specs for your device will probably have a breakdown of the memory locations somewhere. And assembly will probably be required. If you're willing to dig in that deep, you can probably just read the bitmap directly into the graphics memory area, and skip the arrays all together.
Now the big question: why on earth would you want to play with a 286?!?
« Last post by gameblabla on March 13, 2018, 10:06:14 am »
I actually bought Asterix & Obelix XXL a few years later for my GBA SP.
I found the graphics to be pretty nice. (though if you look harder, you can see they are merely using 2D sprites in a 3D environment).
However, the game was pretty repetitive. (and also pretty hard later on)
Plenty of other 3D games got cancelled as well : Dune Ornithoper Assault, Shi'en GBA racer, GP Advance (very impressive!)...
Shame that most of these games for the GBA turned out to be cancelled though.
« Last post by gameblabla on March 13, 2018, 09:39:29 am »
i was having some fun trying to display graphics in all kinds of different graphics mode for DOS.
So far, i've been able to try out CGA, EGA and (ofc) VGA, including the various Mode-X resolutions.
Just recently, i managed to find out how to support and write pixels for the IBM 8514. (I'll talk about it in another post)
However, i encountered some issues. The IBM 8514 is supposed to work with an IBM AT, which comes with a 80286 and that processor is 16-bits.
Making matters worse, arrays/segment cannot be bigger than 64k, even if you have enough memory.
(and even if you are using protected mode, as i found out later. Not to mention, its full of bugs)
Also, IBM never released hardware documentation for it. It only released documentation for AI, a software layer.
And that software layer is very unsuitable for pixel drawing and framebuffer access.
That adapter supports a resolution of 1024x768 with 256 colors. And the only thing i can do is to draw one pixel at a time.
The picture i want to display is that resolution and its like 768kb big.
You can see where this is going...
Here's the relevant C code for it.
typedef struct tagBITMAP /* the structure for a bitmap. */
void drawquad(unsigned long col, short x, short y, unsigned short w, unsigned short h)
static HRECT_DATA quad;
quad.coord.x_coord = x;
quad.coord.y_coord = y;
quad.width = w;
quad.height = h;
hscol_data.index = (long) col;
HSCOL(&hscol_data); /* set supplied colour */
HBAR(); /* begin area */
HRECT(&quad); /* draw quadrilateral */
HEAR(&hear_data); /* end area */
void draw_pict(BITMAP *bmp,int x,int y)
unsigned short i,j;
drawquad(bmp->data[i+(j*bmp->width)], i, j, 1, 1);
void load_bmp(const char *file,BITMAP *b)
/* open the file */
if ((fp = fopen(file,"rb")) == NULL)
printf("Error opening file %s.\n",file);
/* check to see if it is a valid bitmap file */
if (fgetc(fp)!='B' || fgetc(fp)!='M')
printf("%s is not a bitmap file.\n",file);
/* read in the width and height of the image, and the
number of colors used; ignore the rest */
fread(&b->width, sizeof(word), 1, fp);
fread(&b->height,sizeof(word), 1, fp);
fread(&num_colors,sizeof(word), 1, fp);
/* assume we are working with an 8-bit file */
if (num_colors==0) num_colors=256;
/* try to allocate memory */
if ((b->data = (byte *) malloc((word)(b->width*b->height))) == NULL)
printf("Error allocating memory for file %s.\n",file);
/* Ignore the palette information for now.
See palette.c for code to read the palette info. */
/* read the bitmap */
So with the 286's limitation of 64k for arrays, i am stuck.
Actually i thought of several solutions, none of which worked or are ideal :
- Separate the picture into several parts. This would work but it's a huge inconvenience.
- Call the 80286 a brain dead chip (thanks billy) and make it 32-bits only.
The problem is that AI comes with a small bit of assembly and i was unable to make it work in protected mode.
Plus, it wouldn't work on a stock IBM AT.
I looked at the only game for the 8514, Mah Jong -8514-,
and it is also using AI.
I played the game and i noticed its drawings graphics like it would do with vector graphics.
So yeah, not a good example.
So what did programmers do at the time ? And if you don't know, what would you suggest ?
And no please, i don't intend to spit each picture into several parts unless you tell me a good reason why i should do that.
Also, AI only allows you to draw a pixel at best. (Using the rectangle function)
They did fix that later with XGA but that function is not backward compatible with the 8158.
I’ve been waiting for something like this for a long time. I eve considered doing it myself. I do remember a while back that someone was asking about hooking a raspberry pi to that small screen..
« Last post by Juju on March 12, 2018, 08:12:38 pm »
Hm, that's interesting. Sounds like something to TAS.