Page 1 of 3

Random Nikos questions

Posted: Tue Mar 15, 2016 2:54 pm
by Ice Cream Jonsey
So, I was thinking the other day that Hugo doesn't support fading the screen to black.

Would it be possible to construct code in a Hugo game that Hugor would understand, but would otherwise be ignored by other Hugo interpreters? Without having to recompile or extend the Hugo language, I mean.

For instance, if I were to kick off some opcodes in a Hugo game and Hugor picked them up, could Hugor then do a thing. (Like fade the screen to black.) This is all theoretical at this point, just curious if it's even possible.

Posted: Tue Mar 15, 2016 4:08 pm
by The Happiness Engine
embed a way to interpret comments?

Posted: Wed Mar 16, 2016 1:18 am
by RealNC
Yeah, it's possible. We'd need to come up with a secret handshake. Something the game initiates, doesn't have ill effects on other interpreters, but Hugor recognizes it as a handshake and behaves in a way that signals to the game that it detected it.

For example, the game might try to read a file that doesn't exist, but has a specific name, like "hugor-interpreter-secret-hello-handshake/?", a file that's very unlikely to exist. Other interpreters will indicate an error. Hugor can indicate success. That way, the game knows it's running in Hugor, and Hugor knows the game wants to know if it's running in Hugor.

Hugor might even allow reading from this non-existent file, by feeding the game with metadata it might want to know when it actually reads from the file (like Hugor version, operating system, whatever else.)

After the fact the game is running is Hugor is established, the game could then open a file for writing, again with a special name, like "hugor-control" (this doesn't need to be an unlikely file name, since we already know the terp is Hugor). The game can then write commands to that file. (Obviously, nothing will ever be actually written to such a file.)

Example:

Code: Select all

writefile "hugor-control"
{
    writeval "fade 250 255 0"
}
Which would do a fade, lasting 250ms, starting from an alpha value of 255 and an end value of 0.

Basically, since you just write commands, you can come with any command structure you want using this mechanism. Error reporting would also be possible if we want, by having the game reading from the same file. So writing to "hugor-control" executes commands, reading from it gets you the result of the last executed command.

Posted: Wed Mar 16, 2016 1:18 am
by RealNC
The Happiness Engine wrote:embed a way to interpret comments?
The compiler strips all comments.

Posted: Wed Mar 16, 2016 1:39 am
by Roody_Yogurt
RealNC wrote: Example:

Code: Select all

writefile "hugor-control"
{
    writeval "fade 250 255 0"
}
writeval in that instance, stores the value of the string "fade 250 255 0", not the string itself, so I guess the hacked Hugor terp would just be checking for a certain value (if you already know what the value of the string will be)?

Posted: Wed Mar 16, 2016 1:47 am
by RealNC
Roody_Yogurt wrote:writeval in that instance, stores the value of the string "fade 250 255 0", not the string itself
Oh, oops. Yeah. I'm not actually familiar with the Hugo language itself.

The game should write the actual text to the control file, just like it would write text to a normal text file.

Posted: Wed Mar 16, 2016 2:24 am
by RealNC
Hm, actually I think we can just write dictionary entries. That would be simpler from the game's side. We'd basically have our own Hugor-specific assembly-like language. The commands would be numeric opcodes, and the parameters would be also numeric, and string parameters would be dictionary entries:

Code: Select all

writeval fade_opcode, 250, 255, 0
If you'd want opcodes that open a website in the player's web browser and alter the volume of the currently playing music:

Code: Select all

writeval openurl_opcode, "http://example.com"
writeval music_vol_opcode, 40
The opcodes would be defined somewhere in the game as numbers. When an opcode takes a string parameter, the value of the string is passed (the interpreter will then look it up in the game's dictionary table.)

I have tested ANY of that, but I think this would work just fine.

Posted: Wed Mar 16, 2016 1:07 pm
by Roody_Yogurt
I was also thinking that if the intent of the fade to black is to present cool transitions, you could make a movie (ala the Thundercats logo) transition that plays nearly fullscreen. Probably would take some clever design to make something that looks cool and fits the game but is definitely doable (and wouldn't require an interpreter hack).

Posted: Wed Mar 16, 2016 1:18 pm
by Roody_Yogurt
Also, both of those opcode suggestions sound cool. I recently added mouse-click support to newmenu.h so I can imagine how I could also add clickable web links to menu pages.

And the ability to fade out music is something we've all wanted for a while.

Posted: Wed Mar 16, 2016 2:28 pm
by RealNC
The core issue here is, how far can we push this? Stuff that is optional seems fine; audio fade in/out can be mapped to just music 0 / sound 0 when not running in Hugor, visual fades are just eye candy, and opening URLs doesn't affect the playability of the game itself in any way.

We could do other stuff, which doesn't have a compatible fallback. Like playing any amount of music and/or sound tracks at the same time. We can pretty much do just about anything. Heck, we can put 2D or even 3D rendering in there if we want. And since these all go through the control file API, we wouldn't be modifying the engine or compiler. But we'd end up with something that call itself a Hugo interpreter, but really isn't :-/

So I think for now, the best thing to do is just pick a few things that would be nice to have (and I assume that would be mostly Robb needing some helpful opcodes for CG) and go from there.

Posted: Wed Mar 16, 2016 2:33 pm
by Roody_Yogurt
In the very least, it'd be nice to have that Hugor-check just so I can have Roodylib figure out if Hugor is being used and act accordingly (for things like hiding cursors where I can just put the cursor in a window and pause with Hugor but requiring a while loop for the regular interpreter).

Posted: Wed Mar 16, 2016 3:24 pm
by Ice Cream Jonsey
Yeah, I am thinking, if we had a Hugor check as one, and two opcodes for fade out and fade in of graphics*, that would be an excellent starting point. (Not sure how difficult fading out audio would be.)

So, I am envisioning a full-screen fade. I don't have a copy of compiled Cyberganked at the moment, but it opens by putting a company logo up for a second, then a color splash screen that takes up the entire display, and then the same splash screen in CGA colors. Something that effectively lets me:

- Fade in, print logo, fade out
- Fade in, print color splash screen, wait for spacebar
- Immediately display CGA splash screen, wait for spacebar then fade out after spacebar is hit, then it continues with the game

Would be awesome and one of those little things that makes the game look a bit more modern. Ditto with fading out the previous song before starting a new one.

Posted: Fri Mar 18, 2016 5:14 am
by RealNC
I started work on this, and here's the design so far:

Virtual Control File
The virtual control file is named "HrCtlAPI". The Hugo engine does not allow more than 8 characters in a file name, and enforces alphanumeric letters. That's unfortunate, but "HrCtlAPI", with *exactly* that capitalization (it's case-sensitive) is safe and very unlikely to be ever used in any other game.

Handshake
For detecting Hugor, the game must read exactly one value from the control file before writing anything in it. The magic value is 12121. So:

Code: Select all

readfile "HrCtlAPI"
{
    hugor_test = readval
}
if hugor_test = 12121
{
    print "We are running in Hugor."
}
The magic value is an additional safety measure. In the very unlikely event that a file named "HrCtlAPI" actually exists on disk, and the game is not running in Hugor, when you don't get 12121 as the only value from that file, then you know you're not running in Hugor. If you want to be completely paranoid, you might also test whether 12121 is actually the *only* value in the file. If you're able to read more values from the file, even if the first value was 12121 by some crazy chance, then you're not running in Hugor.

When trying to read more values, the read will fail (just like when trying to read past the end of a real file.)

This handshake is only possible when reading from the control file for the first time, and without having previously written to it. So the first access to the control file is special and should probably be done at game startup.

However, from Hugor's perspective, this is not mandatory. You can write opcodes to the control file before doing this handshake and just assume you're running in Hugor. But that's obviously not portable; other interpreters (but also older versions of Hugor) will actually create a file named "HrCtlAPI" on disk and your opcodes will be written there and obviously have no effect.

Long story short: do the handshake *first* if you care about other interpreters or older versions of Hugor.

Executing Opcodes
To execute a Hugor opcode, you write the value of the opcode, plus parameter (if any) to the control file, in *one* write operation. So each opcode should be in its own writefile { } block. The opcode is *only* executed when the block exits. You cannot write multiple opcodes in the same writefile block.

This works:

Code: Select all

writefile "HrCtlAPI"
{
    writeval opcode, param1, param2, param3
    ! The opcode is NOT executed yet.
&#125;!<-- The opcode is executed here.
This also works:

Code: Select all

writefile "HrCtlAPI"
&#123;
    writeval opcode, param1, param2
    ! Do something else
    ! ...
    writeval param3
&#125;
However, this does not work:

Code: Select all

writefile "HrCtlAPI"
&#123;
    writeval opcode, param1, param2
    writeval some_other_opcode
&#125;
Or at least, you should not expect it to work. It might, or it might not. Let's call it "undefined behavior."

So every writefile block should write exactly one opcode and the parameters (if any) for that one opcode.

The reason for this is two-fold:

First, Hugo does not allow you to read values inside a writefile block, so there's no way to get the return value of an opcode from inside the writefile block. To get the return value (if the opcode has a return value), you need to a readfile.

Second, if the opcode was executed immediately, you wouldn't get the chance to calculate parameters and write them later on inside the writefile block. This way, you can writeval the opcode, do something else, and then writeval parameters. It is guaranteed that the opcode will only be executed when the writefile block closes ("}").

Getting Opcode Return Values
Since you cannot read values inside a writefile block, to get the return values of an opcode, you need a readfile block and read the results:

Code: Select all

writefile "HrCtlAPI"
&#123;
    writeval opcode, param1, param2, param3
&#125;

readfile "HrCtlAPI"
&#123;
    readval result
&#125;
If an opcode has multiple return values, then you readval multiple times.

Posted: Fri Mar 18, 2016 5:19 am
by RealNC
And here's the first version that implements the above design:

http://83.212.107.13/~realnc/hugor/test ... upport.zip

A couple of opcodes are implemented. Fading is not in yet. This only implements the core mechanism and just a few very easy opcodes.

You can test the above build with this Hugo program:

Code: Select all

#include "hugolib.h"

constant HGR_CONTROL_FILE "HrCtlAPI"

constant HGR_OP_GET_VERSION 100
constant HGR_OP_GET_OS 200
constant HGR_OP_ABORT 300

constant HGR_OS_UNKNOWN 0
constant HGR_OS_WINDOWS 1
constant HGR_OS_MACOSX 2
constant HGR_OS_LINUX 3

routine Main
&#123;
    local hugor_test, major, minor, patch, os_type

    readfile HGR_CONTROL_FILE
    &#123;
        hugor_test = readval
    &#125;
    if hugor_test = 12121
    &#123;
        print "Running in Hugor."
    &#125;
    else
    &#123;
        print "Not running in Hugor."
        pause
        quit
    &#125;

    writefile HGR_CONTROL_FILE
    &#123;
        writeval HGR_OP_GET_OS
    &#125;
    readfile HGR_CONTROL_FILE
    &#123;
        os_type = readval
    &#125;
    print "Operating system&#58; ";
    select os_type
        case HGR_OS_UNKNOWN
            print "unknown"
        case HGR_OS_WINDOWS
            print "Windows"
        case HGR_OS_MACOSX
            print "Mac OS X"
        case HGR_OS_LINUX
            print "Linux"

    writefile HGR_CONTROL_FILE
    &#123;
        writeval HGR_OP_GET_VERSION
    &#125;
    readfile HGR_CONTROL_FILE
    &#123;
        major = readval
        minor = readval
        patch = readval
    &#125;
    print "Hugor version&#58; "; number major; "."; number minor; "."; number patch

    pause
    quit
&#125;

Posted: Fri Mar 18, 2016 5:40 am
by RealNC
RealNC wrote:Getting Opcode Return Values
Since you cannot read values inside a writefile block, to get the return values of an opcode, you need a readfile block and read the results:

Code: Select all

readfile "HrCtlAPI"
&#123;
    readval result
&#125;
Obviously that was meant to be "result = readval". Where's my edit button?

Posted: Sat Mar 19, 2016 2:48 pm
by RealNC
Screen fade support and opening of URLs is now in. (No audio fade yet.)

The design changed too. The opcodes now always return at least one value that indicates whether it succeeded or failed. I'll document the new behavior later. For now, you can grab this:

http://83.212.107.13/~realnc/hugor/test ... upport.zip

A test Hugo program with source is included.

Note: The URL opcode opens pretty much anything, not just http URLs. Local files, email addresses, whatever.

Posted: Sat Mar 19, 2016 3:18 pm
by Roody_Yogurt
Cool, I just played with this. Already started on adding some of it to Roodylib.

A couple of things, though. Trying to get the OS on Windows is resulting on a 0 readval for me.

Initially, I thought that I'd have to call HGR_OP_ABORT whenever I wanted to reset the ops if I wanted to check for Hugor again. I see in Test.hug that you've set up another file just for Hugor-checking which is nice so my code will switch over to that, but just the same, calling HGR_OP_ABORT caused Hugor to crash for me.

Posted: Sat Mar 19, 2016 3:35 pm
by RealNC
ABORT is for killing the process. No graceful engine shutdown, or even application shutdown. It's mainly there for debugging.

All opcodes now return one value that indicates whether there was an error or not. That means that GET_OS will return two values. The first is a HGR_RESULT_* code, the second is the actual value you want (one of the HGR_OS_* values.)

Posted: Sat Mar 19, 2016 3:38 pm
by Roody_Yogurt
Ah, cool, that fixes it, thanks!

Posted: Sat Mar 19, 2016 3:53 pm
by RealNC
Roody_Yogurt wrote:I see in Test.hug that you've set up another file just for Hugor-checking
This was needed, since the game needs to check for this not only during game startup, but also after restoring from a saved game. I'm not sure if this is possible with Hugo though. Is there a way to run code right after restore?

If not, then things will break if you save the game in Hugor, then restore it in another interpreter, as the game will be assuming it's running in Hugor.