How do the frontends work?

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • Gwarl
    Administrator
    • Jan 2017
    • 1025

    How do the frontends work?

    So I feel like a good way to expand the game's reach might be to implement tiles/graphics for online play. I am just brainstorming about how to do this.

    Obviously DCSS manages it somehow. I'm not quite sure how it works, I asked one of the devs many years ago who said it 'spits out a bunch of JSON'.

    There was this attempt by takkaria at writing a native web frontend for angband: https://github.com/takkaria/angband-webterm but I'm not completely sure what it is or how it works.

    Angband.live currently uses pseudoterminals forked from the main process to plug the variants in. This is suitable since being able to play in a terminal is more or less the common thread which unites most variants. When I first created the site it used the pty.js library, now unmaintained, since forked by microsoft as node-pty and incompatible with the two most recent nodejs stable versions, but I digress. I don't think this is what DCSS does for webtiles.

    I can theorise one might be able to spawn a process on the server not as a terminal and then perhaps pass messages in real time to stdIn and pull them from stdOut and then one could write a client program eg a webserver to work with the interface. I'm not sure.

    If someone could help me with the angband side of things I could probaby write the client/UI part of it. Or even if anyone knows how the main-xxx.c files work and could give me an explanation it might be food for thought.
  • backwardsEric
    Knight
    • Aug 2019
    • 527

    #2
    It's probably easiest to look at main-xxx.c (a stub frontend), main-gcu.c (a frontend that you're familiar with; does go through main.c), and main-win.c (a front end that can render tiles, does not use main.c, and is present in many variants). The comments and declarations in z-term.h (renamed to something else in some variants) are also very useful since the bulk of a frontend's interaction with the game are related to what's in z-term.h. When something is not clear from the comments in z-term.h (or those comments are not in agreement with observed behavior), look at z-term.c (renamed to something else in some variants) to see what's actually being done.

    There's two types of frontends, those that let main.c do some of the initialization and those that bypass main.c entirely and have to do everything themselves.

    For those that use main.c, they have to be tied into main.c. In general, that means specifying which string passed to the -m option will select the front end, telling main.c how to call the initialization function for the front end, and how main.c should describe the front end in a usage message. The details of how to do those depend on the variant. in Hengband 1.6.2, each frontend has two blocks of code that are conditionally compiled into main.c: one to print the help message and another to call the frontend's initialization function if the string passed to -m is right. In Angband 4, there's one element of an array initialization that is conditionally compiled into main.c. That array entry specifies the string to use with -m, a string to use as the help message, and a function to call to initialize the frontend. Angband 4 also has the prototype for frontend-initialization function and the external declaration for the help string in main.h.

    For a frontend that uses main.c, the initialization function for the frontend processes the command-line arguments specific to the frontend (all command-line arguments handled by main.c have already been processed and removed from argc and argv) and does the initialization of the terminals and the other hook functions into Angband's internals. The initialization of the terminals is broadly the same between variants: call a function which initializes the terminal's data structure and sets its size and depth of the input key buffer, fill in the hook functions and some customizable data fields for the terminal that the frontend wants to override, and then activate the terminal. Where the terminal functions are declared and the details of the prototypes for them and the hook functions do vary. Some variants have largely kept the original interface (declarations in z-term.h, initialization of a terminal with term_init(), and activation with Term_activate()) while others have deviated from that (Angband 4.2 has the declarations in ui-term.h while keeping the term_init() and Term_activate() names; Hengband 3.0.0 has moved everything to C++ rather than C, put the declarations in term/z-term.h, kept term_init(), but renamed Term_activate() to term_activate()). The naming of the hook functions and their roles is fairly consistent, but the argument types vary as the variants do different things to handle larger sizes for terminals, encoding of color and other attributes, and handling of text encodings that can take more than one byte per character. The common hook functions are:

    init_hook: Initialize the data specific to the frontend, the data field in the terminal's structure, for a given terminal. Called when activating the terminal if its active_flag field was not false.
    nuke_hook: Clean up the data specific to the frontend for a terminal. Called by Term_nuke().
    xtra_hook: Handle a variety of miscellaneous actions for a terminal. Common actions are: TERM_XTRA_EVENT to wait for an event, TERM_XTRA_FLUSH to flush all pending events,
    TERM_XTRA_CLEAR to clear the entire terminal, TERM_XTRA_SHAPE to change the shape/appearance of the cursor, TERM_XTRA_FROSH to signal that the terminal has completed updating a row (with the associated calls to text_hook, pict_hook, or wipe_hook if something changed), TERM_XTRA_FRESH that the terminal has completed updating the entire terminal's display, TERM_XTRA_NOISE to make a noise, TERM_XTRA_BORED to do something when the game is waiting for input or is otherwise not busy, TERM_XTRA_REACT to respond to global changes that could invalidate what's displayed (changing between tiles and ASCII, changing the color table, changing visual preferences, ...), TERM_XTRA_ALIVE to suspend the terminal or release a previous suspension, and TERM_XTRA_LEVEL to respond when Term_activate() deactivates the previously active terminal and activates a new one. The TERM_XTRA_EVENT case is especially important since that is the normal place where input keystrokes and mouse events are passed to the game from the frontend.
    curs_hook: Draw or move the cursor.
    bigcurs_hook: Draw or move the cursor when it is not the size of a single character. Typically used when the variant supports displaying a tile in m columns x n rows or in something like Hengband when the cursor points at a Kanji character that spans two columns rather than just one.
    wipe_hook: Clear n columns from one row.
    text_hook: Display n characters on one row using the same color/text attribute.
    pict_hook: Display n pictures on one row. For a frontend that supports tiles, this handles the rendering of a tile.

    The other fields in the terminal structure that the initialization function might change are:

    icky_corner: Set to true by frontends using traditional terminal emulators where one corner requires special treatment. Defaults to be false.
    soft_cursor: Internally, the game has two ways of handling the cursor: one in which it manually erases the old cursor position and then redraws it at the new one (a "software cursor") and another where it only tells the frontend what the new cursor position is. Frontends that want the first behavior set soft_cursor to true. Those that want the second behavior (typically those that use a traditional terminal emulator where erasing the cursor is implicitly done when the cursor is moved) use the default value for soft_cursor, false.
    always_pict: If set to true by a frontend, all rendering for a row goes through pict_hook. text_hook and wipe_hook are not used and can be NULL. If false, the default, the rendering for a row can use pict_hook, text_hook, or wipe_hook depending on how always_text or higher_pict are set.
    always_text: If set to true by a frontend, wipe_hook will not be used and can be NULL: clearing is done with pict_hook (if always_pict is true) or text_hook. If false, the default, wipe_hook will be used for clearing unless always_pict is true.
    higher_pict: If set to true by a frontend, the rendering for a grid goes through pict_hook if the high bit of the text attribute for that grid is set and goes through text_hook (or possibly wipe_hook if always_text is false) if the high bit of the text attribute is not set. If false, the default, and always_pict is false, all rendering for a grid goes through text_hook or wipe_hook and pict_hook can be NULL. The typical frontend that supports tiles has higher_pict set to true and always_pict set to false.
    char_blank: Is the character to use for grid with nothing there. Defaults to be space. Angband 4.2.5 has removed this field.
    attr_blank: Is the text attribute (usually the color table index) to use for grid with nothing there. Defaults to be zero. Angband 4.2.5 has removed this field.
    never_frosh: If set to true, the frontend is not interested in TERM_XTRA_FROSH events, and they will not be sent through xtra_hook. The default is false.
    never_bored: If set to true, the frontend is not interested in TERM_XTRA_BORED events, and they will not be sent through xtra_hook. The default is false.
    complex_input: Present in Angband 4. If true, the frontend converts entered newlines, backspaces, tabs, and escapes to the KC_ENTER, KC_BACKSPACE, KC_TAB, and ESCAPE codes, respectively, before putting them on Angband's input key queue. If false, the detection and conversion of those keys is done internally by the game.

    In the initialization function for the frontend, you would also override game behavior that is not tied to a terminal. Most variants have quit_aux so the frontend can do something when the game quits and plog_aux so the frontend can respond when plog() is called. Frontends using main.c will already have quit_aux overridden even if they do nothing since main.c sets quit_aux before calling the frontend initialization function. If your frontend supports tiles, you would also likely set up what tile sets are available. Angband 4 has the functions in grafmode.h to do that for the tiles packaged with the game; other variants handle that differently.

    The frontend will have to provide implementations for the hook functions it overrode. Look at main-gcu.c and main-win.c to see how they implement the hooks they use. Frontends that support tiles and do not fix the tile settings at startup, have to provide a mechanism for the player to change the tile settings while playing. Many frontends tie the size of a displayed grid to the natural size of a character in the fixed-width font being used (the Cocoa frontend for macOS allows a font that is not fixed-width but then does things to render that font to a fixed size grid). The Windows frontend has an option ("Enable nice graphics") so the size of a grid is chosen to best match up with the native size of a tile; that can cause characters to be rendered with extra padding so that they fit in the chosen grid size. Some variants support rendering a tile to a space larger than a single grid. In some that is simply 2 columns x 1 row ("bigtile mode" which allows square tiles to better match with a typical fixed width font whose native width is roughly one half of its native height). Others allow the tile to be rendered to m columns by n rows where m is an integer greater than or equal to one and n is an integer greater than or equal to one.

    If the frontend does not use main.c, it has to do the initializations that main.c would do:

    1) Set up the paths with init_file_paths(); for the paths which may not already be there when the game starts (user's .angband/... directory for instance) the frontend may have to create them (some variants like Angband 4 have create_needed_dirs() to help with that)
    2) Set ANGBAND_SYS to the short string for the frontend; used when conditionally pulling in parts of preference files
    3) Set the savefile name; in Unix environments likely need to set a player ID (typically this is Angband's player_uid global) and perhaps other user-related IDs
    4) In something like Angband 4 where there's a UI layer that's been pulled out from the rest of the game, the frontend will have to initialize that
    5) Call init_angband() to read in the data files and do other initializations of the game
    6) Call play_game() to start playing

    If the variant supports extra sounds or music and you want the frontend to support that as well, you may have additional work to do which is likely variant-specific. Angband 4's sounds go through a layer that is separate from the rest of the user interface and the core of the game. main.c handles initialization of the sound layer. Frontends which do not use main.c would have to initialize the sound layer themselves. Look at Angband 4's main-win.c for an example of that where the frontend has its own platform-specific sound implementation. In Hengband, sounds are handled by calling terminal's xtra_hook with TERM_XTRA_SOUND as the code for the action to take. Hengband 3.0.0's music also uses the xtra_hook but with one of the TERM_XTRA_MUSIC_* codes as the action. For sound or music in Hengband, the parsing of related configuration files is tightly integrated into the Windows frontend so another frontend would have to do its own parsing and its own mapping of the second argument to xtra_hook to the file to play.

    For your specific case, I suspect that one main-angbandlive.c that works well with all the variants you support would lead to something that is an ugly mess of conditionally compiled code. I'd guess you would end up with multiple versions of main-angbandlive.c, perhaps one per variant (though the variants that have left z-term.h in its original form could likely be the same; especially if you let the variant's main.c do the game initialization). Those main-angbandlive.c files would be a thin layer to hide most of the variant-specific quirks and would call into common code for handling the input and display that's compatible with a web browser.

    Comment

    • Pete Mack
      Prophet
      • Apr 2007
      • 6883

      #3
      You might also as Diego how he did it for Android. Someone is likely able to PM his email, as he rarely checks in.

      Comment

      • Gwarl
        Administrator
        • Jan 2017
        • 1025

        #4
        Thanks for the extensive reply.

        So as I understand it, writing a a frontend means more or less writing functions to replace those hooks, so that the frontend can draw an angband 'term', the innards of the engine will still handle the specifics of which character to put in which box and the frontend simply provides a method for putting those characters in the place where the game tells them to? How are inputs done?

        I think ideally I would want a sort of 'headless' frontend where essentially calling these hooks would just be a matter of passing the hook and its parameters to stdout to be implemented outside of angband itself and I would also need a way of passing commands to stdin that angband would recognise

        Comment

        • backwardsEric
          Knight
          • Aug 2019
          • 527

          #5
          Originally posted by Gwarl
          So as I understand it, writing a a frontend means more or less writing functions to replace those hooks, so that the frontend can draw an angband 'term', the innards of the engine will still handle the specifics of which character to put in which box and the frontend simply provides a method for putting those characters in the place where the game tells them to?
          Yes, that's the essence of how it works, especially for the hooks that do the drawing (text_hook, pict_hook, and wipe_hook). I should note that those drawing hooks are only called to update grids that have changed since the last time the game pushed out an update to what's displayed. The frontend will have to have some sort of memory for what's been displayed in the past or query the game (Term_what() is the traditional name for that function) about what's at particular location for a terminal.

          Originally posted by Gwarl
          How are inputs done?
          The frontend queues a keystroke with Term_keypress() (to put it at the end of the queue) or Term_key_push() (to put it at the front). Variants that support mouse input or other types of events may have additional functions to pass along those events; in Angband 4, Term_mousepress() passes along a mouse click. The typical place that a frontend pushes input events to the game is in the xtra_hook when it is called with TERM_XTRA_EVENT.

          Originally posted by Gwarl
          I think ideally I would want a sort of 'headless' frontend where essentially calling these hooks would just be a matter of passing the hook and its parameters to stdout to be implemented outside of angband itself and I would also need a way of passing commands to stdin that angband would recognise
          The first part of that could be done. The core of the game isn't looking at stdin so your headless frontend would have to implement the protocol by which it turns stuff read from stdin into function calls or changes to global variables to get the game to respond.

          Comment

          • Nick
            Vanilla maintainer
            • Apr 2007
            • 9637

            #6
            Originally posted by backwardsEric

            The first part of that could be done. The core of the game isn't looking at stdin so your headless frontend would have to implement the protocol by which it turns stuff read from stdin into function calls or changes to global variables to get the game to respond.
            See https://github.com/angband/angband/b...s/game/basic.c too for a test which involves pushing commands directly to the game core rather than going through the UI.
            One for the Dark Lord on his dark throne
            In the Land of Mordor where the Shadows lie.

            Comment

            • Gwarl
              Administrator
              • Jan 2017
              • 1025

              #7
              Okay so still at the thought experiment stage..

              Let's say for now we just use JSON for information interchange it might not be the most efficient way but it's probably among the easiest.

              Term_keypress() and Term_key_push() would accept arguments? Strings? Something that strings can be easily mapped to?

              So my client program could send things to stdIn like {'function':'keypress', 'args':['m']}\n and angband-side there would just be a function which switches based on the function parameter and calls it with the supplied args. And on the other side to receive from angband similarly it could print things like {'function':'wipe_hook','args':['3','6']}\n and there would be another switch to deal with the different hooks. That seems like it might not be too much work to make possible? One could call the file main-json.c

              I think that would give me everything I would need to write a client application for a webserver (or an electron frontend with most of the same code)

              Comment

              • Gwarl
                Administrator
                • Jan 2017
                • 1025

                #8
                Or would it be better to go through term_xtra_hook somehow? maybe term_xtra_hook would be that switch?

                Comment

                • backwardsEric
                  Knight
                  • Aug 2019
                  • 527

                  #9
                  The traditional declarations for Term_keypress () and Term_key_push() are:

                  Code:
                  errr Term_keypress(int k);
                  errr Term_key_push(int k);
                  k is an integer code for a single keystroke. The expected character encoding will depend on how the game was compiled but would usually be ASCII or a superset of ASCII (and the specific characters the game tests for would be in ASCII's set).

                  In something like Angband 4, the prototype for Term_keypress() was changed (but not Term_key_push(), looks to be an oversight):

                  Code:
                  extern errr Term_keypress(keycode_t k, uint8_t mods);
                  Where keycode_t is a typedef for a 32-bit unsigned int, k is the code for a single character encoded in UTF-32, and mods specifies which modifier keys were pressed. mods is expected to be a bitwise-or of zero or more of the KC_MOD_* constants that Angband 4 declares in ui-event.h. In practice, mods only matters when testing if a keymap will be triggered, and there's the expectation that some cases have the modifiers folded into k and left out of mods (see MODS_INCLUDE_CONTROL() and MODS_INCLUDE_SHIFT() in Angband 4's ui-event.h; something like 'A' is expected to be sent with k equal to 0x41 (UTF-32 or ASCII for 'A') and mods equal to zero and not as k equal to 0x61 (UTF-32 or ASCII for 'a') and mods equal to KC_MOD_SHIT; similarly, 'control-a' or 'control-A' would be sent as k equal to 1 (0x1F bitwise-anded with the code for 'a' or 'A') and mods would either be zero or, for 'control-A' if the frontend distinguished 'control-a' from 'control-A', KC_MOD_SHIFT).

                  Comment

                  • Gwarl
                    Administrator
                    • Jan 2017
                    • 1025

                    #10
                    Thanks again.

                    I think if I actually give this a try I'll probably be starting with 2.9.3.

                    Comment

                    Working...
                    😀
                    😂
                    🥰
                    😘
                    🤢
                    😎
                    😞
                    😡
                    👍
                    👎