let it leave me like a long breath

let it dissipate or fade in the background

(no subject)

Profile

xax: purple-orange {11/3 knotwork star, pointed down (Default)
howling howling howling

Nav

  • Recent Entries
  • Archive
  • Reading
  • Tags
  • Memories
  • Profile

Tags

  • art - 2 uses
  • asteroid garden - 4 uses
  • code - 19 uses
  • demos - 1 use
  • dreams - 5 uses
  • ff7 fangame - 23 uses
  • fic prompts - 13 uses
  • gamedev challenge - 82 uses
  • hell game - 76 uses
  • nanowrimo - 11 uses
  • plants - 9 uses
  • process - 52 uses
  • programming - 51 uses
  • screenshots - 5 uses
  • writing log - 83 uses

May 2025

S M T W T F S
    123
45678 910
1112131415 1617
18192021222324
25262728293031
  • Oct. 15th, 2018
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    [personal profile] xax
    Tags:
    • gamedev challenge,
    • programming
    posted @ 10:49 pm

    okay so two week projects

    i didn't write a writeup for the last one so this is two-in-one.

    so for the latter half of september i tried out procedural magic systems, and ultimately i wrote down a lot of notes and concepts but i didn't get a lot specifically done. part of the reason why is that i tried using my possibility space code to handle it, and actually this is something that it's remarkably ill-suited for

    the possibility space code really works the best when there's a really large possibility space and you just want a handful of mostly-unique items. with a procedural magic system, there are a lot of complex constraints. there's lots of things like "for each rank in the magic system, it should usually generate one of each magic type (healing, control, summoning, attack, etc) available in the system, and it should usually not generate two spells of the same type", or "when generating magic effects, it should usually either reuse the same adjective often or never (e.g., holy healing vs. holy bolt), but not sometimes" that the possibility space code really couldn't handle very well. it's definitely possible for the possibility space code to have some part in generating systems, but it definitely can't handle the main generation logic.

    so basically in working on that, i got to the point of realizing that and stopped, because i didn't want to totally start over w/ some bespoke haskell code. but this is something i'd want to revisit at some point, b/c, well, hey procedural magical systems. it'd be neat.

    here are some outputs i generated:

    • 001
    • 002
    • 003
    • 004

    (one of the things that i realized pretty quick was that what i wanted was... each magic school as having a messy cluster of 'domains'. i started with 'light' and 'dark' and very rapidly realized the words and concepts i were using were actually from several distinct subclusters. so i broke them apart into a set of relations: 'light' as in actual light, associated with 'sun' and 'sacredness' and 'purity'; 'dark' as in shadow, associated with 'moon' and 'occult' and 'decay'. and it'll first pick one domain and either a random associated domain or the opposing domain, and during generation it can generate 'mystery' events that would pick a new associated/opposing domain and start to generate spells of that domain.

    this has an issue where it doesn't really handle comprehensive science-type magic very well -- there would be no magic schools about converting earth air water and fire magic into each other. it's always got a specific elemental flavor. that's something i'd have to think about how it would work.)


    the other thing i worked on, for the first half of october, was input stuff again. kind of a continuation of this thing. i'm using the reactive-banana FRP framework still, which, is okay. as mentioned previously, there's a bit of an issue with the rendering -- my understanding is that the event network (which is its own monad thing) expects to effectively be the 'main loop' of the program, and handle all input (via handlers firing events) and all output (via an event stream of IO ()). but that's not feasible for real-time rendering, and gpipe does all its render stuff inside a different rendering monad that can't really be converted to an IO () action. so i'm not entirely sure how i'm gonna get everything to work together yet.

    that being said, this two-week section went a lot better than the prior two-week section. the goal was to figure out automatic synthesis of input handlers, so that i could just make some inert data type like a Menu a or Form a or whatever, and populate that as desired, and then hand that over to get an Event a out of it.

    let's talk about input handlers for a bit

    on the lowest level, GLFW provides callbacks for raw input events. these are things like 'key pressed' or 'cursor moved' or 'mouse button clicked'. there is only minimal state recorded (only the control keys -- shift, alt, ctrl, etc), so even if you want to know where the cursor was clicked then you need to maintain your own state, because as far as GLFW is concerned 'mouse click' is wholly separate from 'cursor position'.

    so last time i put together some slightly more structured events, most notably click events that tell where they were clicked at. but. ultimately you don't want to be thinking at the level of individual input events at all. in my previous attempts at handling input, i generally had one gigantic handler that took the raw events and did stuff with them according to game state. stuff like 'did we get an up/down arrow press?' 'are we in a menu?' 'if we are, and we got an up arrow, and the index of the menu isn't already the top, then move the index up one' 'otherwise if we got a down arrow, and the index of the menu isn't already the bottom, then move the index down one' and so forth and so on for every possible action. if the game is in a certain state -- menu, world movement, paused, dialog box, shop screen, config screen -- then that state needed to be recorded and shoved into the scope of the input handler, and then the input handler would look at its available state and do whatever it needed to do, given the state and the event.

    i would also generally have to manually construct all the menu layouts. if it was something easy like a list of options, that was pretty easy to automate. but something like an options screen, with lots of different labels and toggles and selection fields, i would have to literally got, for every single thing, "okay this is at coordinates x,y and it extends w,h and when it's clicked it should set this state", over and over and over again, and test a bunch to make sure i hadn't messed up and rendered overlapping items. it was a giant hassle.

    for this, in haskell, i really wanted to avoid that entire nightmare. i wanted the program to do all that for me.

    think about filling out a web form: you might click on some checkboxes, click on a select box, mouse down through the drop-down box, click again to select an option, click on a text field to focus it, type some text in, click on a separate text field to focus it, type different text, click on a radio button, click up through a number field, mousewheel scroll to get to the right number, and finally click on a submit button. there are a lot of low-level input events happening there, and none of them really escape the context of "making a form". so instead of thinking about each raw input event as it comes why not restructure things so that you're expecting an input stream of the form itself? when the form is done (or cancelled) it'll emit an event, and until then all the low-level input will be automatically handled by its own thing, and i as the programmer writing more code wouldn't have to ever really touch any of the input events or think about what they're doing.

    what that actually means is writing code that would 1. auto-generate a layout for a given ui thing, maybe with some hinting 2. manage internal state like 'is this checkbox checked', and suitably generate render update actions when anything is selected or deselected or w/e, and 3. synthesize a new event stream from the raw input event stream. what this accomplishes in haskell is that it takes user input and treats it like any other kind of value -- a Form a is a Functor and can be mapped over and changed and composed and ultimately extracted into an event stream, which means that it's possible to treat these values as 'haskelly' values that you can reason about, instead of ephemeral things that are piecemeal assembed inside a handwritten event handler.

    this kind of thing is basically entry-level for actual gamedev toolkits, but uh i've never actually gotten around to doing it. mostly because i haven't really been using haskell for realtime stuff until very recently.

    anyway i did it, is the short version. i vaguely remembered using reform as part of happstack for my web server, and they have a form type that is an applicative. i also remembered they mentioned that reform was based off of digestive-functors, which was a simpler implementation (reform has a whole proof types thing that i don't need, at least yet) so i decided to check out how they did it. this lead to some interesting hacks.

    digestive-functors defines a FormTree type that does a wild GADT hack.

    so a big part of the usability of this is that we want to have an Applicative instance for these types, right? so we can say (+) <$> number 0 10 <*> number 0 10 or whatever, and have a Form that runs that code when it's evaluated, without the programmer having to really care about which numbers were selected or where exactly every pixel was clicked. and that presents a problem, since to work as a form element, it needs to cache its original constructor. this is what i tend to think of as the heterogenous infinite type problem: if we have a type Foo b a that stores a value of b and can convert it to type a, then anything that stores a Foo b a needs to include the type variables a and b in its type signature, even if we only ever pull an a out of it. this means it's impossible to make that type do anything useful, because the types will never line up (e.g., can't instance Functor b/c it's not Foo b a -> Foo b c, it would have to be like Foo b a -> Foo (Foo b a) c, which is not... robust. consequently any type that has to cache its transformations is pretty impossible.

    except you can use existential quantification to avoid that, by just hiding the type variable:

        data Deferred a = forall b. Deferred b (b -> a)
    
        run :: Deferred a -> a
        run (Deferred b f) = f b
    
        instance Functor Deferred where
            fmap f (Deferred b g) = Deferred b (f . g)

    which is actually the first time i've actually seen a use for existential quantification.

    this gets subsumed under GADTs:

        data Deferred a where
            Deferred :: b -> (b -> a) -> Deferred a

    and that also solved a problem i was having with checkboxes. so a checkbox constructor should always return a [a] value, right? like that's how checkboxes work. but without GADTs there's no way to force a constructor like that.

    so my type ended up looking like

        data Form a where
            Checkboxes :: Eq a => [(String, a)] -> (a -> Bool) -> Form [a]
            Radios :: [(String, a)] -> Int -> Form a
            Textbox :: (String -> Either String a) -> Int -> String -> a -> Form a
            EnumSet :: (Enum a, Show a, Read a) => a -> a -> a -> a -> Form a

    to represent your basic ui elements. the thing with that is, well, that's not a Functor, right? imagine running fmap head on Checkboxes [("foo", 1), ("bar", 2), ("baz", 3)] (const False). Checkboxes can only construct a Form [a], so you can't reconstruct the fmapped value with Checkboxes in the same way that you could with Radios.

    this is where we cheat:

            Pure :: a -> Form a
            App :: Form (x -> a) -> Form x -> Form a

    now you can implement Functor (and Applicative) by just shoving everything into the App constructor:

        instance Functor Form where
            fmap f v = App (pure f) x
    
        instance Applicative Form where
            pure x = Pure x
            (<*>) f v = App f v

    and then when you run it, you can extract those functions and evaluate them.

    but wait, i hear you say. that's not a valid Applicative instance! it breaks the Applicative laws! pure id <*> v === v no longer holds! but. doesn't it? after all in every possible evaluation path the same value will be generated. sure, internally it's an App (pure id) v constructor instead of a v constructor, but like, internally in haskell id . f would be a different thunk than f, so...

    (actually that's maybe not true; i am not really that familiar with how haskell thunks work. but you get the idea.)

    anyway the actual function is pretty messy due to the way rendering interacts with events -- the full type is buildForm :: MonadMoment m => RenderState os -> Event FauxGLFW -> Form a -> ([RenderUpdate os], m (Event a, Event [RenderUpdate os])), which returns 1. a list of initial render actions, which need to be rendered when the form is first displayed, and 2. a tuple of event streams in the FRP monad, one corresponding to form submissions and the other corresponding to intermediate render updates (e.g., text being typed or checkboxes/radios being selected or deselected). in actuality it's still super unfinished, since i only wrote rendering/behavior handlers for the checkbox constructor. text input is a mess.

    anyway i'm feeling tentatively positive about this. i still need to figure out how to actually use that Event a value to do things; right now i'm just debug printing it, but presumably the game would be in a state where it's prepared to do something with the a events it recieves. and i still have no clue how to selectively enable/disable forms, in practice. so opening and closing ui boxes. still a very primitive thing.

    anyway i'm sure i'll figure it out at some point.


    • Previous Entry
    • Add Memory
    • Share This Entry
    • Next Entry
    • Reply
Page generated Jan. 3rd, 2026 01:09 am
Powered by Dreamwidth Studios

Style Credit

  • Style: (No Theme) for vertical