let it leave me like a long breath

let it dissipate or fade in the background

Entries tagged with programming

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
    • Previous 20
    • |
    • Next 20
  • Feb. 10th, 2018
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • gamedev challenge,
    • programming
    posted @ 01:34 pm

    okay gamedev challenge reportback

    a bit early, because i got tired of rust + hit on a new idea that's really grabbed my attention.

    so, rust. the first thing: rust's library ecosystem is very much not robust. there are some fairly comprehensive geometry/math libs (cgmath, mint), but after that, in terms of "3d rendering engines", it's... very slim pickings.

    at first i looked at piston, which was a gigantic mess of half-finished code and leaky abstractions that was immensely frustrating to try to use. specifically i was like "okay now let's load a texture and place it on a polygon", and they only have examples for loading raw texture bytes.

    (one of my demands here was "i am not going to use any graphics library that expects me to manually serialize texture byte data", which i don't think is an unreasonable demand at all but apparently several graphics library authors disagree with me)

    they had a function that gave you a Texture object from a filename, but it was a texture object that was completely distinct from some other texture object that seemed to be the one that was actually used, and there was a bunch of lower-level gfx code that wasn't really restructured at all, so it seemed like they were expecting you to sometimes use a piston abstraction but sometimes you'd have to talk to the gfx code directly and it was a huge mess. and none of it was really documented, so all of that is basically just guesses i made from looking at the rust doc and trying to line up types, with no real clue if i was looking down the right path. so that alone was nearly enough to get me to just give up there.

    anyway then somebody else recommended three-rs, which was comparably way less busted.

    three-rs basically worked decently? or rather: the version on cargo is version 0.2.0 and has basically no documentation at all, and also has a Window::new function, which you need to get the general assortment of three-rs rendering objects, that expects "a shader path". that's really weird because 1. you need at least two shaders (vertex and fragment) to do anything, so a single shader is useless and 2. they mention precisely nowhere what the shader should actually do (input uniforms, etc). so i looked at the code examples, only to see them using a one-arg version of Window::new that did not appear to actually exist.

    i forget how exactly i figured it out (looking directly at the github source, probably?) but eventually i realized the version hosted on github, which is where the examples linked to, while also called version 0.2.0, was radically different and much further along in development. after cloning the repo and building it locally i also discovered that it had a bunch of actual documentation written. so after that giant hurdle it was decent.

    (btw the newer version has default shaders, hence the lack of that arg for Window, but it still talks about having "a shader" whenever you need to provide one, and it's only through guesswork and asking other people that i discovered that what it wants is a name like "foo" that it will automagically rewrite to "foo_vs.glsl" and "foo_ps.glsl" and load those as the vertex and fragment shaders. this isn't even mentioned anywhere in the docs, they just say "a shader" or "a shader name" as if that's clear.)

    generally most of the time was spent struggling with the library itself as opposed to the language, which, given how fiddly and how little a grasp i have on rust-the-language, wasn't really encouraging either. in like a week of use i found one library panic bug and one place where the library just kinda threw away uv data because apparently literally nobody had ever used this library to draw textures before, which is like... yeah the rust library ecosystem is not robust.

    there was still the issue of there not really being explanations for what anything does -- one of the issues i was having was that textures appeared to be loaded automatically using linear interpolation, which looked like absolute garbage for my lo-fi textures. so i was like, well, what in the world sets that, because it's not mentioned anywhere. i searched for "blend" or "linear" or something like that, and that got me some types (Samplers) that seemed to be talking about texture interpolation functionality, except... the load_texture function didn't take that as an argument, and always forced you to use the 'default' one. there was a separate load_texture_from_memory function that did, but, it didn't take a filename; it required you to manually construct texture bytes. so i griped at somebody to do something about that, and they did, and that turned out to be the thing that fixed it, but until that patch had landed i still wasn't actually certain that the Sampler arg actually did interpolation. it certainly seemed to be the thing in code that did that, but there wasn't any documentation about it anywhere, aside from in data declarations and type signatures.

    i eventually got a super basic demo that draws some textured hexagons and also has a billboarded sprite drawn that switches its sprite index as you rotate the camera around. nothing really worth showing off.

    i guess i might return to rust at some point later? but really because my only other options are haskell (not good at realtime anything) or java (terrible libraries, requires a giant IDE to do anything, requires oodles of boilerplate code everywhere), or like, learning c++ (no). plus it's a language that's doing something interesting, so i feel like i kinda have an obligation to support it over the waves of "let's just write everything in javascript" that are kinda consuming the code scene these days. but so far, uh, not really liking the language or any of its libraries all that much.

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Jan. 30th, 2018
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • gamedev challenge,
    • programming
    posted @ 09:02 pm

    okay gamedev challenge status post time!

    there's technically another like, 36 hours before it's "over", but i think i've pretty solidly hit the limit of what i can add in that time.

    so when i made the last post-mortem post i was all, yes, 3d rendering, that's what i'll be working on next, and if you've been following me on mastodon or on my programming tumblr you have probablyyyy noticed that's not what i've been working on. instead, i started working on the graph generator again! since i mean, i did only work on it for a week last time.

    that's mostly because right after i made that post, i 1. watched some gdc, including a randomized lttp run, and 2. did some bloodborne chalice dungeon crawling. so i was like "hey you know what's neat? random dungeons and random unlock trees", and that got me thinking about getting back into working on a dungeon generator. or, specifically, a dungeon unlock flowchart.

    so when i left off last time, i had a working graph embedding that drew nice hexagonal graphs.

    i initially wrote down a bunch of 'power-ups' which each had a bunch of different powers, because one of the neat things about the lttp randomizer is that it considers alternatives: you need the master sword OR the cape to cross the barrier. you need the book of mudora OR the titan's mitt and the flute to enter the desert palace. i, uh, also basically instantly threw those away, since alternatives are (probably???) a lot trickier to solve for. instead, i mostly focused on 'keys' and 'locks'.

    but this wasn't specifically a key/lock update, it was more just a generalized "improve the graph generator" effort. there were several things i got working additionally, that were maybe just as important: first, differently-sized rooms, and then differently-shaped rooms. (the rendering for those is actually still super ad-hoc, since i wasn't able to generalize it. so literally only hexes of any size + those three shapes (1x2, triangle, diamond) are supported. also, even if i fixed the renderer, only convex shapes work for the placement code i've written. so there are some serious limitations here that i'd like to fix.)

    i also started experimenting with actual generators -- sure, i put together this big graph expansion framework, but that's kind of useless without an equally-intricate set of instructions to build the graphs. everything i'm using right now is really rudimentary, and is still mostly just "randomly pick a room, randomly place it against an existing room", and there's a whole world of more complex operations that i'm totally not using right now.

    but the main thing are the lock-and-key puzzles i got working. there are only a few basic expansions that i'm using:

    • take any two connected rooms, and on one end add a key. on the other end, add one or two locked doors leading to dead-end treasure rooms
    • take any one room. add an open edge to a new room, and move a random percentage of the first room's keys to the new room
    • take any two rooms that have an open (not locked or one-way) edge between them. break that edge, and in its place add a looping circuit: add a new room, and add edges between it and the two initial rooms so that there's some random combination of open, one-way, or one-way unlockable edges between the three rooms, pointed so that you can always run a loop around them (there's also a version of this that runs on a single room, adding two new rooms with a looping circuit)
    • take any two rooms that have an open edge between them. keep that edge, and add one room off each room (with the new connections maybe being one-way or one-way unlockable), with the two new rooms connected through a locked door. one of the two new rooms will have a third new room attached, with the key.

    so those are all comparatively simple expansions, and they all preserve connectivity (if a room was reachable at the start of an expansion, it's still reachable at the end). so i can just run those expansions as much as i want, and it'll slowly complicate the dungeon layout.

    it would actually be possible to add in alternatives fairly easily (like, an expansion that places two side rooms with a key each, and then adds in a room with two locked doors connected to it), and then trust the other expansions to complicate that further, but i haven't actually experimented with that much. something that would be much harder to handle would be 'small keys', as in, one-use key tokens that each unlock any one of a number of small key doors. i think just having random graph expansions would end up with cases where it could be impossible to progress if you use your keys to open the wrong doors.

    (the next major step contentwise would be to make different expansions based on different room types, like e.g., have a forest-with-river zone that generates cosmetic, un-traversable 'across the river' edges, and only allow one-way unlockable exits when they transform a stream edge, and then render that in-game as knocking over a tree. add in more shapes of things, and have certain room types only be certain shapes, to allow for hallways and rooms and the like. stuff like that could add a lot of character to certain zones, since you'd traverse them in different ways. right now the dungeons are pretty uniform in structure, since the same expansions are running across the entire thing.)

    there are a few really big limitations of this system, currently. one, the embedder is kind of dumb and can only place new rooms, not move existing rooms around. this actually has some really major repercussions in terms of generation implications: if any two rooms are generated next to each other, they will always be next to each other, even if we run a million expansion iterations. it's impossible for a new room to push them apart. which means it's impossible to, say, start the generation off with a few locked-circuit iterations, to make a bunch of loops, and then run the rest of the expansions to spread those loops out, so that the entire map (in most cases, statistically) will end up looping back on itself at the ends.

    two, it's impossible to separate edges unless they're specifically matched on: there's no way to say "split this room in two and move half of its edges to the new room", for example. that means it's also impossible to recursively subdivide a room. imagine generating a simple map, but where all the nodes are huge hexes with lots of space inside them. then turn each node into its own dungeon level, making sure to put rooms at the correct places along the edges of the hex so that they end up maintaining proper connectivity. that's a major goal for this project, and it's still totally impossible.

    third, it's basically impossible to propagate information through the dungeon. i had a planned expansion that was "find a room with an open connection and a locked connection, and then in the room of the open connection add a new room, with that connection locked with the same key" (to spread out locked doors so that each key isn't ever only used in one room, basically), and it's not actually possible currently to say "the same key", because that information is only known in the matched subgraph, and only used in the expanded graph, and actually those two things can't share information at all. likewise, i kinda wanted to have a 'difficulty' level, that rose by one with each step, except again the state of the matched node is only known in one place, and it's not where the data for the expanded node is.

    i have a few ideas for fixing those things, or at least some of them, but the code is really not there yet. so i think this is a really neat thing that i've put together, but also there's sooooo much left to do before it's what i would call 'finished'.

    still, on the whole i'm really pleased with this! it's an actual use for this code i've had sitting around for a year, and in these two weeks i've taken it from "here's some hexagons" to something that could be an actual, genuine map layout for a game. it still needs a lot of work, though, even for that: adding start/finish lines, or enemies, or boss rooms, etc, even ignoring all that complicated algorithmic junk above. but pretty good progress for two weeks, and i'm pretty hyped for whatever it is i decide to work on for the next two weeks.



    also i've been doing this by having the embedder output an .svg file of the graph, and so far i've just been taking screenshots and posting pngs. but here are some Original, Authentic svg images:

    a few ~50-room dungeons:

    • dungeon-047.svg
    • dungeon-048.svg
    • dungeon-049.svg
    • dungeon-050.svg

    a few ~100-room dungeons:

    • dungeon-053.svg
    • dungeon-054.svg

    (there are other in-progress shots/blurbs on my code screenshot tumblr if you want to read the whole backlog)

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Nov. 19th, 2017
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • programming
    posted @ 09:04 pm

    ooof after dawdling on it for like two months i finally got the hell's half-acre plant parser to a stable state. not a working stable state, mind you. just one that doesn't crash.

    (okay admittedly about half the delay here was because i got super sick. but. half the delay was just me being lazy.)

    it was also an interesting test of coming back to java after some time away? the language itself still seems fairly painless, it's just that everything built on top of it seems... bad. or maybe "unwieldy"; i'm in this situation where i'm using GSON (the google json library) to decode data files. the default is apparently to use reflection to shotgun all object properties into a json object, and then do the reverse when deserializing. (i didn't actually try running it with any defaults, because it seemed like that would be a huge mess.) but you can also write a custom serializer/deserializer, which lets you shape the files a little bit smoother. so far so good.

    there are a few things that go wrong at this point.

    first, the deserializers take the form of a type instance: class ... implements JsonDeserializer<T>. this means, barring any wacky class Thing<T> implements JsonDeserializer<T> antics (where you'd have to instance them with a deserialize function, i guess?), you're stuck making one class per type you want to deserialize. and because of the way java works, each class needs to be in its own code file. if you have ten data structures to decode, you're gonna need an entire folder devoted to those ten classes that exist only to be instanced once.

    secondly, you have to explicitly register these custom deserializers, on a per-value basic -- the deserialization is all done through a class Gson, which instances of have a fromJson function. if you want it to check for custom deserializers, you need to build a gson value, and call .registerTypeAdapter(Type type, Object typeAdapter) on it. the object is an instance of a thing with the correct deserializing instance, and the type is the type it deserializes. it's entirely possible for them to not match, and i have no clue what happens if they do.

    (an aside: Type here is generally the .class value of the type, but since parametric types (Type<T>) don't have .class values, you have to make a type token for them. this isn't really that annoying, but it's another example of how the basic unit of code in java is assumed to be the class, not the instance.)

    where registering gets really annoying, though, is when you have nested objects to deserialize. because those deserializing instances then need to call fromJson from within their deserialize functions. which appears to mean that i need to have code like

    ThingDeserializer thingDeserializer = new ThingDeserializer();
    Gson gson = new gsonBuilder()
        .registerTypeAdapter (Thing.class, thingDeserializer)
        .create();
    thingDeserializer.setGson (gson);

    so that thingDeserializer has a gson value with all the custom handlers set that it can use in its deserializing function. only since there are like ten of them, it's actually more like

    FooDeserializer fooDeserializer = new FooDeserializer();
    BarDeserializer barDeserializer = new BarDeserializer();
    BazDeserializer bazDeserializer = new BazDeserializer();
    QuuxDeserializer quuxDeserializer = new QuuxDeserializer();
    BlorchDeserializer blorchDeserializer = new BlorchDeserializer();
    Gson gson = new gsonBuilder()
        .registerTypeAdapter (Foo.class, fooDeserializer)
        .registerTypeAdapter (Bar.class, barDeserializer)
        .registerTypeAdapter (Baz.class, bazDeserializer)
        .registerTypeAdapter (Quux.class, quuxDeserializer)
        .registerTypeAdapter (Blorch.class, blorchDeserializer)
        .create();
    fooDeserializer.setGson (gson);
    barDeserializer.setGson (gson);
    bazDeserializer.setGson (gson);
    quuxDeserializer.setGson (gson);
    blorchDeserializer.setGson (gson);

    and it's kind of a gigantic mess of boilerplate.

    third, it seems like manually writing deserializers isn't really encouraged -- it sounds like the suggested workflow is dumping and restoring huge opaque data globs that contain all the perhaps-transitory internal state, and trusting that nothing in the internals of the type has changed between when it got dumped and when it got restored. so what you actually have to write for the deserialize function is like. hey here's a JsonElement that you can call .getAsInt or .getAsJsonArray or .getAsJsonObject or .getAsString on, and i guess from all of those things you need to cobble together some parsing. like, if you have "{"name": "foobar"}", to get to the name value you'd have to have, like, elem.getAsJsonObject.get("name").getAsString(), and if any of those lookups don't correspond to the data, well, i hope you enjoy null reference errors. (i've been wrapping them in Optional, but that can only really do so much, and like,

    Optional.of(elem)
        .map (e -> e.getAsJsonObject())
        .map (e -> e.get("name"))
        .map (e -> e.getAsString())

    ISN'T EXACTLY AN IMPROVEMENT HERE. might as well just wrap a try around everything and hope for the best.)

    anyway it's working but not as flexible as it needs to be, and it's not loading/using the textures correctly or something. still, it's nice making progress on this again.

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Nov. 3rd, 2017
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • programming
    posted @ 03:48 pm

    monad instance frustration

    okay so as i have posted about a bunch before, i have this haskell Enumerable data type, that deals with decks of enumerable values. the fundamental interface is something like

    data Enumerable a = ...
    
    instance Functor Enumerable ...
    instance Applicative Enumerable ...
    instance Alternative Enumerable ...
    instance Monoid Enumerable ...
    
    count :: Enumerable a -> Integer
    select :: Enumerable a -> Integer -> Maybe a
    enumerate :: Enumerable a -> [a]
    
    from :: [a] -> Enumerable a
    range :: Enum a => (a, a) -> Enumerable a

    internally, it's defined like this:

    data Enumerable a
    	= EnumerableNil
    	| Enumerable
    		{ count :: Integer
    		, selector :: Integer -> a
    		}

    and all the instances are function composition / application, so this is a reasonably-efficient way to store and generate values from a deck of values, even when that deck can be ludicrously huge. enumerate can be a dangerous function to use, given that these decks can have billions or trillions of entries, and that's simply because trying to generate billions-to-trillions of values is always a challenge. up until that point, there's no internal code that needs to exhaustively enumerate all values. so where something like
    length $ (,,) <$> [1..1000] <*> [1..1000] <*> [1..1000]
    can take ages since it has to evaluate the spine of the entire 1000000000-long list,
    count $ (,,) <$> from [1..1000] <*> from [1..1000] <*> from [1..1000] evaluates basically instantly, since all it needs to do is evaluate the three individual 1000-length lists. this is, in short, a way to get O(n) performance from combinatorial operations that would usually take O(2n) (or O(n2)?? i am not 100% on my big-os).

    (there's also roll :: RandomGen g => Enumerable a -> Rand g a, for randomly pulling from the deck, and is a fairly simple combination of count and select w/ generating a random number)

    the problem is, Enumerable should also be a monad. or rather, it is absolutely a monad, and writing a join :: Enumerable (Enumerable a) -> Enumerable a function is actually extremely trivial:

    join :: Enumerable (Enumerable a) -> Enumerable a
    join EnumerableNil = EnumerableNil
    join (Enumerable c v) = mconcat $ (\c' -> v c') <$> [0..c-1]

    the problem, though, is that [0..c-1] bit, which requires exhaustively evaluating every value in the deck, and thus completely wrecks the point of the data structure. so like, yeah, it's easy to write a Monad instance, here it is right here:

    instance Monad Enumerable where
    	return = pure
    	EnumerableNil >>= f = EnumerableNil
    	e >>= f = joinEnum $ f <$> e
    
    instance MonadPlus Enumerable where
    	mzero = EnumerableNil
    	mplus = (<|>)

    but that instance has god-awful performance on any non-trivial value, and given the way the value works (we need to know the number of combinations so we can select properly, and with a nested value there's no way to figure out how many combinations the inner Enumerable has without evaluating it) i don't think there's any feasible way to implement a Monad instance.

    this is super annoying, since the npc generator i'm working on now is INCREDIBLY simple if there's a monad instance for enumerable, and much much more complex if there's not. but. i don't think it's feasible to write any monad instance whatsoever. :/

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Oct. 18th, 2017
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • hell game,
    • programming
    posted @ 07:56 am

    or, to make the stuff in that last post more explicit and code-backed:

    (you could also think of it as a sequel to this post from a year and a half ago, because there i was talking about an earlier version of the encounter code. as you can see from this post, it's grown since then.)

    so internally an Encounter is this:

    type Encounter i vars mob = BodyGlob -> mob -> [Event mob i vars]

    where i is the scene-indexing type, vars is the local-variables type, and mob is the encounter type. Event itself looks like this:

    data Event e a l
    	= Narration [String]
    	| Narration2 EventReel
    	| Dialog String String [String]
    	| Dialog2 String String EventReel
    	| Dialog3 String EventReel EventReel
    	| ItemModify [Item]
    	| NodeGive TfNode
    	| Choices [(String, a)]
    	| Choices2 [(String, a, Maybe (Cond l))]
    	| Challenge Opponent [[Item]]
    	| FauxChoice String
    	| Jump a
    	| Shop String [Alchemy]
    	| Close
    	| DeactivateSelf
    	| InlineBranch (Cond l) [Event e a l] [Event e a l]
    	| SetFlag (Scope l) SetMod
    	| UnsetFlag (Scope l)
    	| SetEncounterData e
    	| PushEncounterData (Scope l)
    	| LoadAllSubData (Scope l) [String]
    	| PopToSubData (Scope l) [String]
    	| PeekLoadEncounterData (Scope l)
    	| Branch [(Cond l, a)] a
    	deriving (Show)

    and is basically a list of every possible discrete thing that can happen in a game event -- an event can generate text narration, or text in a dialog frame, or give/remove PC items, or give body nodes, or present a list of choices, etc etc etc onto the more abstruse ones like PeekLoadEncounterData, which i don't even remember what it does.

    the type of Encounter means that, on the server side, when generating the text for an encounter, each encounter has access to precisely two things: the pc body state, and the mob encounter state, both at the precise state they were in when the encounter request was sent (i.e., you can't change an encounter midway through and expect the later scene text to respond to that; all reachable encounter paths are generated once, at request time. when i first started coding the encounter system i made some attempts to change that, but as you might imagine that kind of flow analysis isn't super trivial so there's a certain amount of dead code devoted to path flow analysis.)

    more specifically, each game encounter file provides a few things, most notably an instance to Scene:

    class ToJSON mob => Scene i vars mob | i -> mob vars, vars -> i mob where
    	link :: i -> Encounter i vars mob
    	display :: i -> String
    	vardisplay :: vars -> String
    	varread :: String -> Maybe vars
    	varread _ = Nothing

    which has to do with translating all the random haskell code that makes up an encounter into something that can be serialized (into JSON) and sent back to the client. from the bottom up:

    • varread is dead code ostensibly used for reading the client-side encounter variable table, which is sent with the request but (currently) never used
    • vardisplay is to convert local/global variable references (which exist in code in a form like Local HasMet or Global FarmQuestActive) into plaintext so that the client-side encounter variable table has names to index its variables with
    • display is to render the scene index type, which is used because the actual ~scene api~ responds with a big lookup table of scene data, indexed by the scene index type (hence the name), so that also needs to be renderable as plaintext
    • link, which is the big one, and which determines how we look up scene parts by index on the server side. functions of type a -> b are effectively a total lookup table of the form [(a, b)], so, this is a lookup table with i as the key and Encounter i vars mob as the value.

    so for example, the scene instance for the flame demon encounter looks like this:

    instance Scene Index Vars FlameEncounter where
    	link x = case x of
    		OpeningOptions -> openingOptions
    		TalkOpening -> talkOpening
    		Talk s -> talk s
    		ShopPrompt -> shopPrompt
    		Leave -> leave
    		FuckIntro -> fuckIntro
    		JerkOff from -> jerkOff from
    		BlowTop -> blowTop
    		MouthClimax -> mouthClimax
    		ThroatClimax -> throatClimax
    		Blow from -> blow from
    		SitAnal from -> sitAnal from
    		MountAnal from -> mountAnal from
    		CagePlay -> cagePlay
    		YeahFuck from -> yeahFuck from
    		Fist from -> fist from
    		Reject -> reject
    		Bye -> bye
    		Uncurse -> uncurse
    	display = fmap toLower . show
    	vardisplay = fmap toLower . show

    (this is basically the pattern all the scene instances fall into, btw -- a case statement to link scene indexes with similarly-named functions, and then display/vardisplay being basically show.)

    SO if you read that prior post, here is the technical aspect of what i was calling 'subevents': if you have location X that could potentially store NPCs of type A, B, or C, then what in the world does its scene instance look like? specifically, given that an event is gonna return values of type Event mob i vars, and it's not really feasible to edit the encounter code directly for every possible location that might store an NPC, how do we change those values around to basically turn events on one mob/index/vartable into events on a DIFFERENT mob/index/vartable?

    if you know haskell you might already be having a sinking feeling here

    the answer is, of course, that Event mob i vars forms a TRIFUNCTOR, and can be mapped over to convert values that match one Scene instance into values that match a different one. here:

    triMap :: (e -> f) -> (a -> b) -> (l -> m) -> Event e a l -> Event f b m
    triMap ef af lf e = case e of
    	Choices cs -> Choices $ (\(s, a) -> (s, af a)) <$> cs
    	Choices2 cs -> Choices2 $ (\(s, a, mcl) -> (s, af a, (fmap lf) <$> mcl)) <$> cs
    	Jump a -> Jump $ af a
    	InlineBranch cl e e' -> InlineBranch (lf <$> cl) (triMap ef af lf <$> e) (triMap ef af lf <$> e')
    	SetFlag sl m -> SetFlag (lf <$> sl) m
    	UnsetFlag sl -> UnsetFlag (lf <$> sl)
    	SetEncounterData e -> SetEncounterData $ ef e
    	PushEncounterData sl -> PushEncounterData (lf <$> sl)
    	LoadAllSubData sl s -> LoadAllSubData (lf <$> sl) s
    	PopToSubData sl s -> PopToSubData (lf <$> sl) s
    	PeekLoadEncounterData sl -> PeekLoadEncounterData (lf <$> sl)
    	Branch cs a -> Branch ((\(cl, a') -> (lf <$> cl, af a')) <$> cs) (af a)
    
    	Narration s -> Narration s
    	Narration2 e -> Narration2 e
    	Dialog i n s -> Dialog i n s
    	Dialog2 i n e -> Dialog2 i n e
    	Dialog3 i n e -> Dialog3 i n e
    	ItemModify is -> ItemModify is
    	NodeGive tf -> NodeGive tf
    	Challenge o is -> Challenge o is
    	FauxChoice s -> FauxChoice s
    	Shop s as -> Shop s as
    	Close -> Close
    	DeactivateSelf -> DeactivateSelf

    (to the best of my knowledge there's no major haskell library that provides an actual Trifunctor class that i could instance, so i was just like 'hey whatever' and decided to just write the map function as a standalone.)

    so, while every existing scene instance took a form basically matching that example above, these parametric locations have some very different scene instances:

    data CedarTree = CDryad Dryad | CNature NatureSpirit | CUnicorn Unicorn
    	deriving (Eq, Ord, Show, Read)
    
    data Index = DIndex Dryad.Index | UIndex Unicorn.Index | NIndex Nature.Index
    	deriving (Eq, Ord, Show, Read)
    
    data Var = AsDryad Dryad.Vars | AsUnicorn () | AsNature ()
    	deriving (Eq, Ord, Show, Read)
    
    asDryad :: Event Dryad Dryad.Index Dryad.Vars -> Event CedarTree Index Var
    asDryad = triMap CDryad DIndex AsDryad
    
    asNature :: Event NatureSpirit Nature.Index () -> Event CedarTree Index Var
    asNature = triMap CNature NIndex AsNature
    
    asUni :: Event Unicorn Unicorn.Index () -> Event CedarTree Index Var
    asUni = triMap CUnicorn UIndex AsUnicorn
    
    instance Scene Index Var CedarTree where
    	link f = case f of
    		DIndex di -> \b c -> case c of
    			CDryad d -> asDryad <$> link di b d
    			_ -> error "bad cedartree scene"
    		UIndex ui -> \b c -> case c of
    			CUnicorn u -> asUni <$> link ui b u
    			_ -> error "bad cedartree scene"
    		NIndex ni -> \b c -> case c of
    			CNature n -> asNature <$> link ni b n
    			_ -> error "bad cedartree scene"
    	display = fmap toLower . show
    	vardisplay = fmap toLower . show

    amazing

    (also note the use of () here -- generally that's what i use for encounters that don't have any local variables. you might think that using () multiple times in a scene instance violates the vars -> i mob fundep in the Scene definition, and you would be correct. but because haskell doesn't exhaustively check for overlapping instances, and since (generally) each scene is in a separate compilation unit, and since i never plan on trying to uniquely identify a scene by vardisplay (), i think i'm fine.)

    (anyway this is interesting b/c the dryad code has a local variable named Met, that's used to determine if you're revisiting a dryad. in this encounter, rather than Met being set and read to, a completely different variable, AsDryad Met, is set and referred to. since that's ultimately what being able to (tri)map over events means: that it's possible to rewrite index tables and vartables to break the correspondence with one scene while creating a new correspondence with another scene. this doesn't yet work 100% for reasons that i'm unsure about, and i think that might just be "the events i've written are poorly-structured to handle having a nested entry point", but it's mostly working. for example, in the current code, the npcs in the cedar tree location get their descriptions printed twice: once because i have the cedar tree opening narration describe their contained npc before jumping into the npc's scene, and the other... in the contained npc's scene.)

    (the next issue here is just the structural one of jumping into another encounter -- right now once you jump into an encounter you're basically stuck in the subtree until it ends; there's not really any jumping 'out'. but it's entirely POSSIBLE to explicitly change the link index of a subevent. but generally those aren't surfaced, and there's no functional infrastructure to do things like "oh okay run the subevent's description code, and then instead of the usual choices present "look around the base of the tree" and "talk to the dryad"", or "instead of 'leave' in the subevent exiting the event, have it jump back to the main event's choice prompt". i could handle things like that on a case-by-case basis, but it would involve surfacing more subevent code and generally be arbitrary and tangled, so once this is fully working from a code perspective the next issue is to think about what added structure would help with nesting events)

    ANYWAY THOSE ARE SOME TECHNICAL DETAILS, HOPE YOU LIKED THEM

    • Add Memory
    • Share This Entry
    • Link
    • 1 comment
    • Reply
  • Sep. 2nd, 2017
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • programming
    posted @ 02:51 pm

    so i was looking up how java handles loading xml files, since i was considering using them for data stuff, and uh the answer seems to be "badly".

    so there's this thing called JAXB, the java architecture for xml binding, which is apparently the accepted method, and it just like, automagically dumps out all state in a java object, recursively. what if you don't want your xml files to have a 1:1 correspondence with internal object state? well, then you make an interstitial object that has the correct names and values so that the automagical serialization produces an xml file more to your liking, and then you convert your normal objects into interstitial objects, serialize them, and repeat the process in reverse to deserialize. there are also some custom hooks in the xml parser thing to optionally drop specifically-named fields, or store certain values as attributes instead of sub-elements, but it seems unbelievably crusty.

    for a while i was like "this seems incredibly dense and obnoxious to use, which is weird because i remember using a thing with minecraft that was a lot easier", and after a little digging i remembered that that was when i was using the gson lib to serialize/deserialize json, where you just write a decoder func for type T and that means you can then make a Deserializer<T> which has a default function to parse a file into a value of that type.

    (it's not that i have anything against automated serializers/deserializers, it's just it seems to encourage thinking of data as an opaque glob, and introduces huge issues if the object ever gains or loses internal properties, since that invalidates all your old data.)

    so anyway i'll probably just do all this using json, which is... not ideal (there is no javascript here at all, so, why json) but unless there's a non-awful xml lib i'm just gonna go the path of least resistance here.

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Aug. 28th, 2017
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • programming
    posted @ 06:57 pm

    private Optional<Function<LayeredMap,Consumer<PcState>>> useAction;


    listen, my java code is immaculate.

    (the joke here is that in haskell this would just be Maybe (LayeredMap -> PcState -> ()). tho that's not a useful type signature to have in haskell, so actually it'd be something different)

    actually as a general trend what i've found really useful for writing in javascript or java or whatever is thinking "okay, how would i do this in haskell?", because the answer is generally some slightly elaborate but robust and bugfree code. in this case i'm using a Consumer, but in haskell i'd have some kinda state-change monoid for popping up UI or adding/removing items or changing the map or w/e. and since that's precisely the issue i've been struggling with wrt "how do i communicate the potential effects of item actions", well, just do that and that's the problem solved.

    in theory. in practice things are generally a bit thornier and more elaborate. but it's a good foundation, probably.

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Aug. 17th, 2017
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    • Current Music: Sunset Rubdown - I Know The Weight Of Your Throat
    Tags:
    • programming
    posted @ 10:21 pm

    between starting with java by using forge to write a minecraft mod, and now dipping back in by using libgdx to make HELL FARM (thats not the actual title) my assessment of java is that it's a perfectly serviceable language that its users seem to love tying in absurd knots

    it definitely has some warts (it's impossible to statically initialize, like, anything, which means doing something as simple as \x -> x `elem` [2,3,4,9] becomes an exercise in frustration), and the impression i get is that it's made a bunch of steps from ~1.6 onward to be less of a nightmare language, and that is just absolutely not reflected in the userbase or libraries.

    one of the first things i remember reading about java was actually in eevee's post about stupid classes, where he points out that since java doesn't have first-class functions, the only way to have a mutable function member is to make a default class and then just extend it with a new subclass every time you want a different function. (this is no longer true; java 7 has semi-anonymous function class instances and java 8 has lambdas.) but that approach is endemic in minecraft/forge; i haven't dug into libgdx enough to tell if it's also true for it. the main libgdx issue i've been having is just that it's huge, and that a given feature's functionality is split up between a dozen different classes with no seeming rhyme or reason about it. that's enterprise engineering, i guess. i am absolutely having to make new objects to encapsulate the slurry of a half-dozen libgdx objects interacting under the surface.

    because of my HASKELL BACKGROUND i use a coding style that's let's say callback heavy; i've been using functions and lambdas all over the place, and i have yet to find a problem (save for the static initializing thing) that can't be solved in a fairly simple and straightforward fashion, with only a little wrestling with the language. wrestling with the libraries, though...

    so idk it's been going well. for an ostensible game developer, basically none of the languages i know are super well-suited for actual games. java is definitely more mainstream, but it's still not the besttt for realtime stuff. but wow i don't think i'm ever gonna go back to a c-family, and rust sounds like a half-formed nightmare, so i guess it's java for this thing.

    • Add Memory
    • Share This Entry
    • Link
    • 2 comments
    • Reply
  • Jul. 23rd, 2017
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • programming
    posted @ 06:21 pm

    this is the more recent and streamlined version of the Enumerable code i mentioned back here.

    ( Read more... )

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Sep. 1st, 2016
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • programming
    posted @ 11:05 am

    BUT THEN ACTUALLY:

    okay so the point of that code is that it makes it possible to represent combinatorics equations using only O(n+m) space for the options, instead of O(n*m) that you'd use from exhaustively listing all of them. this gets to be a very big deal when you have some quattuordecillion options or so but your individual components are only a few thousand.

    HOWEVER

    that Applicative instance has to exhaustively list all of the options! due to the way it works, it ends with a big nested list that ultimately contains one of each possible permutation. like, the code works and all it's just got abysmal space/time complexity. even something as simple as enumerating color space (a paltry 3.6 million options in the space i'm using) would make it lag and freeze.

    so i had to think about how my space-saving code actually worked and i ended up writing some variant functions that i'm calling <<$>> and <<*>> which work just like <$> and <*> save that they require the function to have its first argument be a monoid (and thus by implication require all arguments to be monoids), and using some TRICKERY they manage to store function application in slices that are later combined together

    previously i had thought of my monoid constraint on pick as kind of arbitrary but it turns out it's actually super important

    anyway, having done this now i can start to rework the existing parser to actually use this type, so that's neat



    oh yeah and i worked some on hell game! i took a bit of a break b/c i was super burnt out on it, but yesterday i finally dug into the completed code files and started patching them together so they'd actually compile.

    i think all these adventures in parsers has definitely shown me that data entry can be much much simpler when you can control the data format; i had been thinking about making a scene editor script kinda thing, and now i think i'd be halfway competent at putting that together. something to add onto the todo list, i guess.

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Aug. 31st, 2016
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • programming
    posted @ 04:56 pm

    i’m writing some haskell & i’ve put together maybe the first complicated, math-based type i’ve ever written?

    it’s for enumerable combinatorics equations and the deal is you got

    total :: Enumerable a -> Integer
    pick :: Monoid a => Enumerable a -> Integer -> Either EnumerableError a


    where you can say ‘give me anything in [0..total x)’ and you’ll get out that specific permutation of values. so it lets you exhaustively enumerate certain random generators, basically. and also transform the random generation into just "pick a random number in the range and then just calculate that value", since it's possible to generate any given value without looking at its neighbors.

    (the monoid constraint is just b/c otherwise it would have to return [a] which is kind of annoying since that’s basically never what you’d actually want)

    i was testing this with quickcheck (also my first time using quickcheck) to make sure it actually did what i thought it did, and for a while i had a quickcheck Arbitrary instance that just did not terminate, since these are nested data structures that can store an arbitrarily complex expression. even after limiting it a bunch i still can get it to churn out nice compact expressions that have a total count of, for example, 458575485777749657103020327287430331293288631270492527580669116448151889324257271211264327503524251371913200211982149838423029009393779536545995926888936184639883868071199119583128675780396729867476981269731795729169851133510432815404825306221507642742572989858640526486864585952735243739518209812574667794254182198110257592401920000000000000000000000000000000000000000000000000000000000000000000000000000000000

    so it has kind of been illuminating about the nature of combinatorial explosions.

    i’m working on adding weights and constraints and some other stuff but that’s somewhat difficult, since some constraints are basically super easy to codify and others are known NP-complete.

    i spent a lot of time thinking about how it would or would not be an applicative functor, and then it turns out the actual Applicative instance is like ~250 characters

    anyway code follows ( Read more... )

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Jun. 21st, 2016
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    • Current Music: Stereolab - Anonymous Collective
    Tags:
    • programming
    posted @ 07:42 pm

    PRIMORDIAL [
    name: ĉoŝŝaŭp the first
    element: cosmic
    ]
    SPECIES [
    species: naŭujĥteŭll
    raw species: hornet
    name: naŭujĥteŭllmans
    element: water
    description: average-sized humming hornets with an exposed heart with the horns of a frog with a armored ears with a glass cube for a head
    ]
    SPECIES [
    species: kraken
    raw species: kraken
    name: the kraken
    element: life
    description: tiny playful kraken covered with moss and shelf fungus with the fangs of a rat with a two-headed ears that are perpetually bleeding
    ]
    GOD [
    element: water
    domain: fresh water
    description: a rusted dodecahedron surrounded by blue eyes
    ]
    GOD [
    element: light
    domain: colors
    description: a exploded octahedron surrounded by white flames
    ]
    SPECIES [
    species: ogre
    raw species: ogre
    name: the ogres
    element: water
    description: gigantic glowing ogres who live in the ocean with a hollow prism for a head with gaping udders on their chest with a black pyramid within a iridescent cube within a iron octahedron within a ancient dodecahedron for a head
    ]
    


    procedural generation is wild. admittedly the worst of the word salad here is because i haven't really bothered to get species "accents" constrained in any meaningful way. it's all a work in progress.

    i really need to remove that recursive polyhedra definition though.

    • Add Memory
    • Share This Entry
    • Link
    • 2 comments
    • Reply
  • May. 12th, 2016
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • programming
    posted @ 01:38 pm

    COMONADS

    so in the 3d haskell rhombic dodecahedron game / game engine, there's a lot of times when we want to update a given cell by checking its neighbors. currently the way this works is: if i want to do this, i write a function VMap a -> VMap b (for some concrete type a and b) and then i write some code that maps across the vmap (that being the data store i use to store all the rhombic data; it's basically a vector with some extra indexing functions so i can relabel all the coordinates in O(1) time) to look up all the adjacent cells and do whatever i want with the neighbor data that turns up. this is a huge bottleneck in the code, for reasons still unknown to me but probably having to do with laziness, and it's also super awkward to write.

    a few days ago i read this blog post about comonads and cellular automatia and while that's structured more around zippers as comonads the same basic insight made me realize i can absolutely generalize the "lookup adjacent cells" part into something very much like the comonadic =>> / extend. if i have a vmap, then an actual extend function would be extend :: (VMap a -> b) -> VMap a -> VMap b, which isn't useful -- there's a reason that post talks about zippers, and that's because a zipper has a coherent and justifiable value to give to extract :: Comonad m => m a -> a, and that's the currectly-selected value. if you don't have a zipper, you gotta pick something arbitrary, and there's no coherent way to write cojoin / duplicate.

    (btw that blog post uses =>>, coreturn, and cojoin as the comonad class names; the actual Control.Comonad library uses extend, extract, and duplicate.)

    since i'd rather not rework this type into zipper format just so i can technically instance it to comonad, with all the weird zipper-based overhead that would probably entail, i decided to just fake it and write my own extend function: extend :: ([(RD Integer, a)] -> b) -> VMap a -> VMap b (or _extend :: Coordinate i f v => ([(i, a)] -> b) -> VMapI i a -> VMapI i b for the still-unfinished arbitrary-index version)

    this at the very least makes all this code feel less hacky, since the neighbor lookup is all centralized in one function rather than scattered all over the place. it's probably just as slow though. it probably would not hurt to compile all of the vmap/map generation code as strict, just to see what speed improvements i get. but that's for, uh, not right now.

    but what's nice is that now i have kind of an intuition for what a comonad is: it's something where extend :: Comonad m => (m a -> b) -> m a -> m b makes sense, a type that provides a comonad context that can be reduced down to a single value, only to have that reduced value slotted pack into place -- and thus a type where there's some kind of positional or locational data associated with the type itself (and potentially with each value too), among other kinds of "context".

    MONAD TUTORIALS AND COMONAD TUTORIALS. maybe next i'll find a use for contrafunctors.

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Mar. 28th, 2016
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    • Current Music: Panic! At The Disco - Hallelujah
    Tags:
    • programming
    posted @ 03:12 am

    (i wrote the initial happstack webserver very ad-hoc and when i started thinking i might want to support JSON, for map generation initially, i just kinda grabbed some packages off of hackage and ended up with both Data.Aeson and Text.JSON despite how they both do the exact same thing (codify json in haskell). so aeson is... either hooked up into happstack natively, or at least much _easier_ to hook up into happstack, but w/e when i was putting this together i didn't know any of that.)

    (all of this is a very roundabout way of saying that for a very long time my json was double-encoded: when it was sent out as a response body i did Data.Aeson.encode . Text.JSON.encode, and i only noticed i did this -- and that it was weird -- when i started digging into the specific guts of the event api, since... if i output null it would come out as "null", and the more complex events were a mess of backslashes due to being escaped into a string. and i had to manually call JSON.decode on the javascript end.)

    (this all ended with me writing a ToJson (from aeson) instance for JSObject (from text.json), just transliterating one form of json encoding to another one. probably at some point i should go in there and pull out all the text.json bits & get rid of that dependency.)

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Mar. 24th, 2016
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • hell game,
    • process,
    • programming
    posted @ 02:27 am

    today i kind of worked on hell game! marking up sex scenes mostly.

    now i'm gonna talk about some of the code

    ( Read more... )

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Feb. 5th, 2016
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    • Current Music: Tycho - Dive
    Tags:
    • process,
    • programming
    posted @ 01:22 am

    TODAY I: booted into my linux partition for the first time in like three months (well excepting that one other time i did it) and fixed up some of my rhombic game code.

    SPECIFICALLY i made the async chunk loading code a little more functional -- before i had just hardcoded it to try and load up the chunks 0 0 0/1 1 1 inclusive and then stop, which was okay for the kind of testing i was doing but not really, uh, effective for actually walking around. so i hooked it up to keep a list of the chunks within three grid units of the player and add/remove/resort them as the player moved, so it's now at least trying to keep a loaded buffer around the player. it's still pretty arbitrary about which of those chunks it's going to load (i'm just sorting by manhattan distance, so it'll load {anything one unit away}, and then {anything two units away} and then {anything three units away}. maybe i should just go w/ straight-up summing the difference; i don't really want a load volume that's a weird diamond but that's probably preferable to it loading corners) and it will never unload any chunks (though it will give up on trying to load a chunk if you move far enough away) so this is only slightly better.

    also it takes like ~20seconds to load a chunk, and i have no clue why. i'm assuming half of it is my slow geometry code and the other half is, like, haskell copying the entire map data structure over so the loading thread can mutate it or w/e; i have no clue how the async library practically works.

    (i also spent a few hours today trying to assemble a minecraft modpack while the absurdity of wrangling other peoples' code to play a game i didn't really even like when if the actual goal was "assemble a collection of game interactions that expresses this thing in yr head" then that would be much better-served by working on my own game steadily increased)

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Nov. 17th, 2015
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    • Current Music: Richard O'Brien - Over at the Frankenstein Place
    Tags:
    • programming
    posted @ 06:37 pm

    wrote up some really basic texture sheet support but i haven't been able to test it yet b/c trying to generate a map using more than one material type blows the stack (presumably due to some mess of accumulated thunks involving the random monad, which is the thing i have to change to spawn different materials)

    so i guess i should start tuning the code for efficiency. first time/space and then strictness, because i understand the former a lot better than the latter. so coming up: using Vectors with a k -> Int indexing function. a lot of operations go from O(n) or O(log n) time to O(1), which is nice, and important given that the small maps contain ~16k keys and the large maps contain an (unnecessarily large) ~440k keys. actually they only need to store ~23k keys, so uh that is up there on the to-do list also: more efficient sampling of neighboring chunks when generating geometry. the only real downside of using Vectors over Maps is that i'd have to write my own union/intersection code, instead of depending on Data.Maps eight million variants.

    after that... uh trying to fling strictness annotations all over until things stop breaking, i guess?? i really don't know how strictness works.

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Nov. 16th, 2015
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • programming
    posted @ 06:54 pm

    oh and i got the generation code working in production! it fixes my continous geometry issue forever! it's also atrociously slow, in large part because i'm using Data.Map to store, uh, cubic chunks of coordinates, and then doing a whole lot of key updating to move them around. so roughly half the runtime is taken up by me just shifting thousands upon thousands of keys around when that's an operation that i could be doing in O(1) time with a data structure better-suited. like a big fixed-size array with random access and a single displacement value would handle most of my common operations (shift-all-keys, look up one key) in O(1) time.

    next up: generating texture sheets and the code to uv-map them so i can start rendering MORE THAN ONE MATERIAL TYPE. well, that and other things. getting translucent materials working would be real nice too, given that i never got sorting working right with the old code either.

    oh yeah and nanowrimo is still a mess, whoops, wrote like ~700 words yesterday and zero so far today. i should really start digging into it. any time now.

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Nov. 10th, 2015
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    • Current Music: Dee D Jackson - Automatic Lover
    Tags:
    • code,
    • programming
    posted @ 01:04 pm

    ha ha ha ha it works it worksssssss

    {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies,
    OverlappingInstances,
    UndecidableInstances, TypeFamilies,
    TypeOperators, ViewPatterns, FlexibleContexts #-}
    
    module Generation.Layer.Render
      ( Extract(extract)
      , render
      , (?)(..)
      , Term (..)
      , Extend (..)
      ) where
    
    import Prelude hiding (map)
    
    import Data.Map
    
    render :: Extract from (Map k v) => from -> (v -> r) -> Map k r
    render t f = map f . extract $ t
    
    infixr 5 ?
    data a ? b = a :? Maybe b
    
    class Extract from result where
      extract :: from -> result
    
    instance (Term from k v) => Extract from (Map k v) where
        extract = final
    
    -- OVERLAPPING
    instance (Extend from from' j k v, Extract from' (Map j v'), Show k, Ord k) => Extract from (Map k (v' ? v)) where
        extract v = case next v of
            (rs, n) -> n `unify` (rekey v) (extract rs)
    
    class Term t k v | t -> k v where
      final :: t -> Map k v
    
    class Extend e t j k v | e -> t j k v where
      next :: e -> (t, Map k v)
      rekey :: e -> Map j a -> Map k a
    
    unify :: (Ord k, Show k) => Map k a -> Map k b -> Map k (b ? a)
    unify = mergeWithKey
      (\_ a b -> Just (b :? Just a))
      (mapWithKey $ \k b -> error $ "unify: bad generation @ " ++ show k ++ ".")
      (map $ \b -> b :? Nothing)


    (those Show instances aren't really needed except for when i crash on a bad generation, b/c uhhhh it seemed like i wanted at least some vague detail if that situation ever crops up)

    this provides an Extract type class with a single function, extract. this type class is let's say creatively instanced so that i can extract any depth of generator (the Term and Extend typeclasses) into a set of values. this makes it possible to write a render function that takes any extractable value (i.e., any generator stack regardless of depth) and a function that renders based on those values, while still typechecking to make sure the types match. (previously: i needed separate functions for each depth of generator)

    the punchline to all of this is that in nearly all practical situations you really don't need or want to render the entire generation stack; you just want to render the final value, which it's always been trivial to extract. but this lets you make debug overlays and fancy generation visualizations, so of course it's useful!!

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Nov. 9th, 2015
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • programming
    posted @ 01:46 pm

    {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances,
    UndecidableInstances, TypeFamilies #-}
    
    class Appl fun arg t where
      appl :: fun -> arg -> t
    
    instance (a ~ a') => Appl (a -> b) a' b where
      appl = id
    
    instance {-# OVERLAPPING #-} Appl fun arg (b -> t) => Appl fun (b, arg) t where
      appl f (n, rs) = appl f rs n
    
    class Appr fun arg t where
      appr :: fun -> arg -> t
    
    instance (a ~ a') => Appr (a -> b) a' b where
      appr f = f
    
    instance {-# OVERLAPPING #-} (a ~ a', Appr fun arg t) => Appr (a -> fun) (a', arg) t where
      appr f (a, x) = appr (f a) x
    

    s/o to lyxia @ #haskell who wrote the appr half of this, which was enough to finally get me to comprehend all these messed-up recursive instances

    (this generalizes the series of functions i enumerated here. the end goal is as always to unify all the render* functions in this code, which is a more complicated step, but now one i'm a lot more confident that i can figure out.)

    (also as you might discover if you try to use this code, it requires type hints for everything: function, argument, and result. there might be some way for fundeps to fix that, but i haven't figured that out yet either. i don't know how annoying that would be in practice, since in practice i think the types would be pinned down by use elsewhere.)

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
    • Previous 20
    • |
    • Next 20

Syndicate

RSS Atom
Page generated Aug. 21st, 2025 12:18 pm
Powered by Dreamwidth Studios

Style Credit

  • Style: (No Theme) for vertical