2018 icon indicating copy to clipboard operation
2018 copied to clipboard

The League of Extraordinarily Dull Gentlemen

Open cpressey opened this issue 5 years ago • 55 comments

The League of Extraordinarily Dull Gentlemen

Goals

  • To tell a single story over the course of 50,000 words.

(There were other goals but this turned out to be the main one.)

Code

Of particular interest are the world-description in 930 lines of Samovar and the 363-line Python 3 script that renders the generated events into sentences.

The version of Samovar used was 0.2. For more information on Samovar, see its entry at Cat's Eye Technologies or its repository on GitHub.

Novel

In celebration of GitHub's recent acquisition by Microsoft, I have provided this document for you in Microsoft Word format. It is 85 pages long and consists of 53,129 words.

(If you cannot (or prefer not to) view files in Microsoft Word format, there is also a Markdown version which you can view directly on GitHub.)

Preview

Because it tells a story over the course of 50,000 words, I feel that a single excerpt would not do it justice, so here are a handful of them.

Chapter 2

[...] Moonlight flooded in through the French window and illuminated the suit of armor. The shadowy figure rubbed his chin. An owl hooted outside and the shadowy figure froze. The shadowy figure coughed and was now sure no one else was about. An owl hooted outside and the shadowy figure froze. The shadowy figure cast a furtive glance around the room and coughed and was now sure no one else was about and examined the leather couch closely and leaned back in the leather couch and looked out the French window and examined the leather couch closely and looked out the French window and leaned back in the leather couch and got up and stretched and coughed and rubbed his chin and coughed and

Chapter 4

[...] Pranehurst put down the encyclopedia. Scurthorpe looked at Pranehurst. Throgmorton nodded to Pranehurst. Furze-Platt looked at Pranehurst. Pranehurst nodded to Throgmorton and nodded to Furze-Platt. Scurthorpe picked up the quill pen and got up and stretched and walked around the library and coughed. Throgmorton looked at Scurthorpe. "I shall write to Old Grisbourne. He will know just what to do," said Throgmorton. Throgmorton looked at Pranehurst. Scurthorpe walked around the library. Furze-Platt examined the bookshelf closely. Throgmorton brushed some dust off his coat sleeve. Pranehurst nodded to Scurthorpe and

Chapter 9

[...] Nearby there was a grandfather clock. Furze-Platt walked over to the fireplace. Throgmorton sat down on the leather chair and leaned back in the leather chair. Furze-Platt rubbed his chin. Throgmorton brushed some dust off his coat sleeve. Furze-Platt rubbed his chin and picked up the whiskey. Throgmorton looked at Furze-Platt. "I think YOU stole the silver statuette of Artemis, Furze-Platt!" shouted Throgmorton. "WHAT?" bellowed Furze-Platt. Throgmorton rubbed his chin and put down the newspaper. Furze-Platt walked around the sitting room. Throgmorton rubbed his chin and coughed and nodded to Furze-Platt. "I think YOU stole the silver statuette of Artemis, Throgmorton!" shouted Furze-Platt. "Well I never!" bellowed Furze-Platt. Furze-Platt spluttered and looked out the window.

Chapter 12

[...] Furze-Platt looked out the grimy kitchen window and put down the empty teapot and put down the empty kettle and picked up the tea infuser and picked up the empty teapot and put down the empty teapot and looked out the grimy kitchen window and rubbed his chin and picked up the cannister of tea and rubbed his chin and picked up the empty teacup and picked up the empty kettle and examined the grimy kitchen window closely and put down the empty teacup and picked up the empty teapot and put down the empty kettle and picked up the empty kettle and coughed and rubbed his chin and rubbed his wrist and coughed and looked out the grimy kitchen window and walked away from the grimy kitchen window and walked over to the oven and walked away from the oven and

(Original content of this post is retained below)


For past NaNoGenMos I've alternated between "experimental works" and generating "proper novels". Last year I did some "experimental works" so this year I guess I better generate a proper novel, hey? Not that I have the time for this.

Looking at my previous generators, The Swallows was essentially simulation-based and MARYSUE was more-or-less grammar-based. For this one, I'd like to combine the two approaches using techniques that could be called railroading (TVTropes link).

Also, both of those generators modelled the world as discrete objects, in the manner of, say, a typical text adventure game. In this one, in contrast, I'd like to model the world as a set of propositions, similar to the "database" in Prolog. (I don't think I'll actually use Prolog - I mean logic is great and all but I've never been convinced it's very good for programming in. I actually sketched a DSL for this approach a while back, but I don't think I'll use that either. With the right set of abstractions, doing it in a "mainstream" language should be fine, and Python is what I'm most used to these days.)

My hope is that those two things will work well together and will allow some more sophisticated narrative development, stuff that was kind of awkward in the previous generators, to fall out fairly naturally.

There are certainly other things I'd like to tackle, but finding the time to do what I've already described is already a stretch. But at least one deserves mentioning, which is the actual construction of sentences. The output of a simulation is a sequence of events, and yes you can write one sentence per event, but it's horrendous, even if you use a lot of templates. What would be ideal is if the actual "writing" part of the generator could construct sentences more "from scratch", based on a grammar (obviously) but not just expanding that grammar randomly (obviously) but rather reflecting the content of the events. This is obviously incredibly difficult and I'm not going to get very far in this area, but I'd regard even a tiny bit of progress here a success.

cpressey avatar Oct 12 '18 12:10 cpressey

(The NaNoGenMo rules say I can use my issue as a "dev diary", so here are some notes.)

(1) The article on Railroading on TVTropes talks primarily about tabletop RPGs, but mentions another context where it is seen (console RPGs). Generated narrative is yet another context, and there is a common method for railroading simulations which has been mentioned in passing in past NaNoGenMos but, to spell it out:

Keep re-running your simulation until you obtain a run that has the properties you want.

Here "the properties you want" would be: meeting all the chosen plot points. This is a simple and effective algorithm, with all the efficiency of bogosort. I think it will probably be sufficient for my purposes.

(2) I did a little research and apparently there has been only one finished NaNoGenMo entry written in Prolog (though there have been at least 2 other attempteds). Prolog would give a nice syntax and an efficient implementation for writing rules, and I probably "should" use it, but I'm more interested in picking particular answers (randomly++) instead of enumerating all answers to queries. The latter is certainly more what Prolog is known for; if there is support for the former in some Prologs, I'd have to research what it is, and I suspect that whatever it is would end up being "doing programming" in Prolog and I'd rather avoid that as (as I mentioned) I find that rather unappealing.

The reason I'm hopeful about it is that, while the object-oriented approach is good at modelling the state of the physical world (the table is in a room, a box is on the table, the key is in the box), there is more state here than just that:

  • the state of the conversation (during dialogue)
  • the characters' mental states
  • the state of the story itself (e.g. what characters have already been introduced?)

In an object-oriented approach the temptation is to create separate classes for all of those. But then, when you need to make something conditional on more than one, you end up writing fairly complex code to check all the different classes of states. If, however, they're all modelled as propositions, stored in a single "database", the playing field is levelled, and you can write single, straightforward rules to check all these things at once.

That's the theory, anyway. The reality is probably that the rules become large and unwieldy and are difficult to make modular. The fact is, I'm not sure, but finding out would be one reason to try it.

(3) For construction of sentences, I guess the basic idea here would be to avoid templates and have each event associated with an independent clause tree instead; and have an unparser that renders the sentences. In between, there can be some code that rewrites trees to combine individual IPs into complex sentences, elaborating on them in the process.

cpressey avatar Oct 15 '18 10:10 cpressey

NaNoGenMo Dev Diary, Day -14:

Having just compiled Language survey 2017 I'm having serious second thoughts about using Python.

I mean it's not bad to use a programming language that you use every single day (almost) and that is by far the most-used language in NaNoGenMo, but...

I could do at least part of this in some other language; the problem lends itself to a pipeline where each phase reads data produced by the previous phase (in some intermediary format like JSON) and writes data for the next phase. Each phase could be in a different language.

Looking over the language surveys, I note that the eager functional languages seem a bit under-represented: Emacs Lisp and SML and Clojure have each been used on one entry, but no one has ever yet used Scheme or Racket or Erlang or F#.

cpressey avatar Oct 18 '18 16:10 cpressey

file.readlines() currently uses an iterator (in other words, it's equivalent to the old xreadlines()), so if you can fit your whole per-pass logic in a 'for x in y.readlines()' block, you can do streaming for a single pass easily -- it's sufficiently lazy.

On Thu, Oct 18, 2018 at 12:01 PM Chris Pressey [email protected] wrote:

NaNoGenMo Dev Diary, Day -14:

Having just compiled Language survey 2017 https://github.com/NaNoGenMo/2017/issues/135 I'm having serious second thoughts about using Python.

I mean it's not bad to use a programming language that you use every single day (almost) and that is by far the most-used language in NaNoGenMo, but...

I could do at least part of this in some other language; the problem lends itself to a pipeline where each phase reads data produced by the previous phase (in some intermediary format like JSON) and writes data for the next phase. Each phase could be in a different language.

Looking over the language surveys, I note that the eager functional languages seem a bit under-represented: Emacs Lisp and SML and Clojure have each been used on one entry, but no one has ever yet used Scheme or Racket or Erlang or F#.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/NaNoGenMo/2018/issues/6#issuecomment-431066006, or mute the thread https://github.com/notifications/unsubscribe-auth/AAd6GTy9eP8AoXhxkopWWBkfHWlksxz1ks5umKXcgaJpZM4XZYGE .

enkiv2 avatar Oct 18 '18 16:10 enkiv2

file.readlines() currently uses an iterator (in other words, it's equivalent to the old xreadlines()), so if you can fit your whole per-pass logic in a 'for x in y.readlines()' block, you can do streaming for a single pass easily -- it's sufficiently lazy.

Oh! So, what you're saying is, I should write it in Rust? I agree, that's a grand idea. No-one's used Rust yet, either, apparently.

cpressey avatar Oct 18 '18 16:10 cpressey

NaNoGenMo Dev Diary, Day -12:

It has surely not escaped your notice, dear reader, that all the above description of this entry has been technical in nature. But what's the novel gonna be about, you ask.

Well, before signing up this year, my idea was to set it in a Victorian-era gentlemen's club and, because it would contain a certain (large) amount of The Swallows-esque Brownian motion ("Pranehurst looked out the window and coughed. Scurthorpe rubbed his chin and walked over to the fireplace. Pranehurst looked at Scurthorpe" etc., etc.), I was going to call it "The League of Extraordinarily Dull Gentlemen".

Now, I'm not saying I won't still do that, but the fact is @enkiv2 kind of scooped me (by 9 hours!) on the Victorian-men-of-learning-and-leisure milieu with #5, and I don't have any really good ideas for how to move the plot along in this sort of setting (and it does need a plot, because the kernel of this project is to have a simulation railroaded by a plot), and to top it off, I now have another idea, which is not as interesting technically but which will probably make a much better story, and so I will probably let it take priority over this project.

cpressey avatar Oct 20 '18 17:10 cpressey

That's the theory, anyway. The reality is probably that the rules become large and unwieldy and are difficult to make modular.

If that becomes a problem, you could look up Aspect-Oriented Programming. I never used it myself, but the methodology tries to deal with "cross-cutting concerns" by making the 'concerns' more modular (so you group rulesets under specific modules rather than having them "entangled" with each other). The archetypal example of AOP is "logging", and isn't novel generation nothing more than glorified logging of a person's thoughts?

tra38 avatar Oct 21 '18 02:10 tra38

If that becomes a problem, you could look up Aspect-Oriented Programming.

As it turns out, the instructor for the compilers course I took as an undergrad was one of the Big Names in AOP circles. So of course, a lot of the project work we had to do in that course, involved AOP, for what I was assured were very good didactic and vocational reasons, nothing at all to do with self-promotion.

In my professional life, I've never once used AOP, and in general, I've spent more time removing ill-considered "metaprogramming" from code bases than adding it in.

But that's neither here nor there. This ain't professional life, this is NaNoGenMo! Anything's worth trying!

cpressey avatar Oct 22 '18 21:10 cpressey

NaNoGenMo Dev Diary, Day -10:

Just a note relating to my earlier seemingly-agonizing-over-using-or-not-using-Prolog. There are two languages that are related to Prolog, that are both simpler, and could be interesting to look into in such a simulation as I was thinking of.

One is, if you place certain restrictions on Prolog, you get Datalog, which, if you like CS theory, has the neat property of being P-complete. You may have heard of the "does P=NP?" problem, and you may have heard of NP-complete problems, and you may well wonder, well, are there also P-complete problems? Yes there are, and answering a Datalog query is one of them. For a simulation where there are lots of rules but none of the rules is particular complex by itself, Datalog might be sufficient.

The other is miniKanren, which I've been peripherally aware of for a long time but I've never quite figured out. Like Prolog, it lets you write logic programs, but it's much simpler. It's also traditionally embedded in some other language (traditionally, Scheme) and presumably interfaces well with it - i.e. you could probably do the "programming" in the outer language and just call miniKanren when you need to do a logic query (which would probably ease my concerns about "programming in logic".) So that would be something that would be nice to learn. It's always seemed like a really opaque topic, though, which is why I haven't yet. One gets the impression you're supposed to learn it by implementing it in your favourite programming language. There's an interactive tutorial but when the first example is not a legal program and the second example evaluates to "the reified value of an unbound query" it really makes me wonder what the word "tutorial" means to the author.

Anyway I now have no clear idea what I'm doing this November. So there.

cpressey avatar Oct 22 '18 21:10 cpressey

NaNoGenMo Dev Diary, Day -6:

I now realize fully that you don't need anything as powerful as Prolog, or even Datalog, to work with the "propositions world" I was envisioning, because the "propositions world" consists entirely of facts, no rules, and most of the complexity in logic programming comes from evaluating rules. If you only have facts, you only need a kind of pattern-matching. For example,

character(alice).
character(bob).
weapon(gun).
drink(gin).
holding(alice, gun).
holding(bob, gin).

Then if we want to run a query like

character(C), weapon(W), holding(C, W).

to find all the characters who are holding a weapon, we simply look through all the propositions for one that matches character(C). We find character(alice), which matches with C=alice, so we replace C with alice in the rest of the query to get

weapon(W), holding(alice, W).

and we recursively run that sub-query, where we'll find a match for weapon(W) with W=gun, and our second sub-query will be

holding(alice, gun).

which we will find amongst the propositions when we look, so there are no more sub-queries to run, so yay, we got a match (C=alice, W=gun) and we record it somehow (append it to a list or whatever.)

Since we're running the sub-queries recursively, all we have to do is return from a sub-query, and the parent query will continue running where it left off; in this way it will return all matches. (You have to be careful to forget about variable bindings from previous match attempts, but that's straightforward if you allocate new data structures each time, instead of mutating them.)

This is like only 20% of the way to actual logic programming, but all the same, it sure beats writing conventional imperative (or even functional) code to manually look for all characters who are carrying a weapon.

cpressey avatar Oct 26 '18 15:10 cpressey

we recursively run that sub-query

By doing this, you have implemented rules (modulo the problem of re-ordering parameter lists).

If you've got well-defined finite domains (like, for instance, word lists) most of the difficulty in writing a dumb constraint solver disappears (and the dumbness of the constraint solver ceases to matter in most cases -- or if it starts to, you can memoize).

On Fri, Oct 26, 2018 at 11:23 AM Chris Pressey [email protected] wrote:

NaNoGenMo Dev Diary, Day -6:

I now realize fully that you don't need anything as powerful as Prolog, or even Datalog, to work with the "propositions world" I was envisioning, because the "propositions world" consists entirely of facts, no rules, and most of the complexity in logic programming comes from evaluating rules. If you only have facts, you only need a kind of pattern-matching. For example,

character(alice). character(bob). weapon(gun). drink(gin). holding(alice, gun). holding(bob, gin).

Then if we want to run a query like

character(C), weapon(W), holding(C, W).

to find all the characters who are holding a weapon, we simply look through all the propositions for one that matches character(C). We find character(alice), which matches with C=alice, so we replace C with alice in the rest of the query to get

weapon(W), holding(alice, W).

and we recursively run that sub-query, where we'll find a match for weapon(W) with W=gun, and our second sub-query will be

holding(alice, gun).

which we will find amongst the propositions when we look, so there are no more sub-queries to run, so yay, we got a match (C=alice, W=gun) and we record it somehow (append it to a list or whatever.)

Since we're running the sub-queries recursively, all we have to do is return from a sub-query, and the parent query will continue running where it left off; in this way it will return all matches. (You have to be careful to forget about variable bindings from previous match attempts, but that's straightforward if you allocate new data structures each time, instead of mutating them.)

This is like only 20% of the way to actual logic programming, but all the same, it sure beats writing conventional imperative (or even functional) code to manually look for all characters who are carrying a weapon.

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/NaNoGenMo/2018/issues/6#issuecomment-433445689, or mute the thread https://github.com/notifications/unsubscribe-auth/AAd6GTKKBGSkRAUMB8UTCcFEDQ8SmEQ8ks5uoyjlgaJpZM4XZYGE .

enkiv2 avatar Oct 26 '18 15:10 enkiv2

we recursively run that sub-query

By doing this, you have implemented rules (modulo the problem of re-ordering parameter lists).

No, by doing this, I have implemented search. A rule, as the term is understood in logic programming, is a different thing.

And I honestly have no idea what you mean by "re-ordering parameter lists", because even if I did want to implement rules, I'm sure I wouldn't have to "re-order" anything. Did you mean "renaming variables"?

cpressey avatar Oct 26 '18 16:10 cpressey

Yeah, renaming variables. (Sorry -- when I implemented this, rather than keeping a symbol list, I made a map of positions in parameter lists versus a local stack, since symbols don't persist beyond a pred definition, & that's still how I think of the problem.)

My point was just that, since you're not traversing the whole herbrand universe for this problem, you're substantially closer to a trivial constraint solver than you're making out to be. (You've already got backtracking since you're railroading, even if you don't have actual choice points.)

On Fri, Oct 26, 2018 at 12:03 PM Chris Pressey [email protected] wrote:

we recursively run that sub-query

By doing this, you have implemented rules (modulo the problem of re-ordering parameter lists).

No, by doing this, I have implemented search. A rule, as the term is understood in logic programming, is a different thing.

And I honestly have no idea what you mean by "re-ordering parameter lists", because even if I did want to implement rules, I'm sure I wouldn't have to "re-order" anything. Did you mean "renaming variables"?

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/NaNoGenMo/2018/issues/6#issuecomment-433458548, or mute the thread https://github.com/notifications/unsubscribe-auth/AAd6GVRnfYwP4UbVpr6YwBPK463NtOn8ks5uozJDgaJpZM4XZYGE .

enkiv2 avatar Oct 26 '18 16:10 enkiv2

substantially closer to a trivial constraint solver than you're making out to be

I have no idea how close it is to a "trivial constraint solver" because what does that even mean? Maybe it is a "trivial constraint solver". I could call it that and how could anyone say I was wrong?

The comparison I actually made was to "actual logic programming". Maybe the estimate of "20% of the way" is a bit low, but I stand by the idea that there is quite a bit more work you have to put into this to make it able to handle rules as well as facts (full unification and variable renaming, mainly, neither of which is particularly simple to get right, at least not for those of us who don't casually describe problems in terms of Herbrand universes.)

cpressey avatar Oct 26 '18 16:10 cpressey

Real unification is hard & making real unification fast is a lot harder. It seems like real unification is neither necessary nor desirable here, though.

If you're solving constraints in a greedy random way -- i.e., if you resolve rules like

person(P),weapon(W),holds(P,W)

by picking a random person, a random weapon, and then failing & retrying from scratch if holds(P,W) can't be fulfilled (a kind of bogo-unification, by analogy with bogosort: destroy the universe whenever anything fails) then it should eventually resolve so long as the constraints aren't too specific.

If you can express

person(jim).

and you can express

?- person(P),weapon(W), holds(P,W).

then you can express

attacks(P, Q, W) :- holds(P, W).

if only by treating the predicate body as a query. (You can also decompose an arbitrarily complex rule into a tree of rules with exactly two terms & one AND or OR operator, so long as they can share symbols & you have true and false as built-ins. This can be useful if you want to memoize the intermediate results of rules -- say, into facts.)

That's what I mean by trivial constraint solver: arbitrary constraints can be expressed, and constraints that can be solved will eventually be solved if you let it run forever, but the programmer is expected to enumerate all the atoms in any domain (keeping it finite & presumably small) & the planner doesn't need to have optimizations for pruning possibility space or minimizing retries.

On Fri, Oct 26, 2018 at 12:56 PM Chris Pressey [email protected] wrote:

substantially closer to a trivial constraint solver than you're making out to be

I have no idea how close it is to a "trivial constraint solver" because what does that even mean? Maybe it is a "trivial constraint solver". I could call it that and how could anyone say I was wrong?

The comparison I actually made was to "actual logic programming". Maybe the estimate of "20% of the way" is a bit low, but I stand by the idea that there is quite a bit more work you have to put into this to make it able to handle rules as well as facts (full unification and variable renaming, mainly, neither of which is particularly simple to get right, at least not for those of us who don't casually describe problems in terms of Herbrand universes.)

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/NaNoGenMo/2018/issues/6#issuecomment-433474540, or mute the thread https://github.com/notifications/unsubscribe-auth/AAd6GRPOQBqh_TGHHnl2GFvhXrbhf7h_ks5uoz6fgaJpZM4XZYGE .

enkiv2 avatar Oct 26 '18 17:10 enkiv2

Sounds like you're doing a more sophisticated version of what I'm trying in issue 16. I will probably be writing in Go. I've already coded a unification algorithm (which I admit is trivial progress). We'll see if I come up with anything, and I'll probably be following your progress with great interest.

One of the previous attempts that inspired me is likely your Swallows. I probably misremember it. Many of my original ideas arise from misremembering other peoples' work.

hendrikboom3 avatar Oct 26 '18 19:10 hendrikboom3

@enkiv2

It seems like real unification is neither necessary nor desirable here, though.

I agree; didn't I already say basically that when I introduced my post yesterday with

I now realize fully that you don't need anything as powerful as Prolog, or even Datalog, to work with the "propositions world" I was envisioning

?

And the rest of that post was exclusively about that aspect (making queries in a propositions world, in contrast to logic programming); in order to focus on it, I completely ignored the other aspects of the project. Then you said,

we recursively run that sub-query

By doing this, you have implemented rules (modulo the problem of re-ordering parameter lists).

By "this" I thought you were referring to the aspect I was talking about in the post (making queries in a propositions world, and in particular, recursively running a subquery).

But I guess, now, that you were actually thinking about the entire project when you said that, including event simulation and railroading?

Whichever way it is, figuring out what you're getting at is, to be honest, kind of exhausting, and at the same time, you don't seem to be putting in a lot of effort to comprehend what I've written.

Please forgive me if I choose not to respond to your comments on my dev issue in future, as this is not my idea of a good time.

cpressey avatar Oct 27 '18 07:10 cpressey

Sounds like you're doing a more sophisticated version of what I'm trying in issue 16.

Not sure if this is more or less sophisticated than that, but, in the general sense of trying to combine simulation with structured plot, yes, these seem like similar ideas.

There was no plot at all in The Swallows, if your definition of "plot" means it has to have a resolution. It was only a collection of intrigue tropes. By the end, the model of the characters was somewhat sophisticated (Alice could know that Bob thought an item was in a certain place when Alice had actually moved it, etc,) but that was all the result of hacking over the course of the month, than of any planning.

cpressey avatar Oct 27 '18 09:10 cpressey

Sounds like you're doing a more sophisticated version of what I'm trying in issue 16.

Not sure if this is more or less sophisticated than that, but, in the general sense of trying to combine simulation with structured plot, yes, these seem like similar ideas.

There was no plot at all in The Swallows, if your definition of "plot" means it has to have a resolution. It was only a collection of intrigue tropes. By the end, the model of the characters was somewhat sophisticated (Alice could know that Bob thought an item was in a certain place when Alice had actually moved it, etc,) but that was all the result of hacking over the course of the month, than of any planning.

I was quite impressed by Swallows, however you deprecate your own work as "hacking". It was a real attempt at gaining some kind of consistency in the story, even though it didn't seem to have any kind of direction. The dead body in the bathroom was an inspiration. It added tension to the story just by being there.

Being impressed is probably why I'm trying to mimic or improve upon those ideas.

But hacking over the course of the month is my method, lacking any kind of a clear, implementable spec of what constitutes a good novel. I may get around to implementing some sort of planning, but a month is short.

It's only if I see some kind of pattern in the hacks that I'll consider I've made real progress.

hendrikboom3 avatar Oct 27 '18 20:10 hendrikboom3

I was quite impressed by Swallows, however you deprecate your own work as "hacking"

Okay, a better idiom than "hacking" in this context might be writing [code] by the seat of your pants. :)

cpressey avatar Oct 29 '18 09:10 cpressey

NaNoGenMo Dev Diary, Day -2:

OK, first, at the risk of repeating myself: the technical ideas in this project are interesting (to me), but I don't have any really good ideas for plot or characters or setting to go along with them, so please don't expect the resulting novel to be any good at all.

That said. A lot of the ideas in this project are continuations of the ideas in Samovar. I looked at its code last night and cringed (well, it was a prototype). It would directly benefit from using the propositions-world query I described previously. I think it would be not hard to add negation now as well.

Now, as I said, I don't really want to use Python this year, and I'm nonplussed by the name "Samovar" and I don't remember why I gave it that name -- but those are, I suppose, minor considerations. I've only got a month. I can always translate it to Julia and rename it "Boxlozyte" later.

What I'm getting at is, it might play out like this: I might use NaNoGenMo as an excuse/opportunity to improve Samovar and make another release of it. Then (maybe) use that to generate a novel, one that tries to meet some of the technical goals I've mentioned, regardless of how poorly it works as a novel.

cpressey avatar Oct 30 '18 08:10 cpressey

OK, first, at the risk of repeating myself: the technical ideas in this project are interesting (to me), but I don't have any really good ideas for plot or characters or setting to go along with them, so please don't expect the resulting novel to be any good at all.

Here's a possible idea for you to look at.

When I saw Samovar, I thought about using it to "simulate" corporate warfare within a Cyberpunk city using some factions from an abandoned cyberpunk game I tried to build. You can have multiple corporations, several "industrial sectors" (Electronics, Energy, IT, etc.) and MacGuffins to fight over, and then rules to justify when a corporation declare war, when they declare peace, when they win/lose battles, etc. You can also have internal events as well - such as a corporation reforming themselves, focusing on research, etc. And then you might have other entities as well - hedge fund owners trying to manipulate the stock prices of the megacorps, police officers trying to enforce the law, etc.

tra38 avatar Oct 31 '18 02:10 tra38

That could be interesting; it ties in with the goal of wanting to extend the scope of state that's tracked (state of the economy? geopolitical state?)

Under the current plans, though, the simulation will have a lot of random motion between plot points -- a good way to reach the 50,000 word quota, I think, but perhaps too silly for a gritty cyberpunk setting.

Factions and allegiances are a good way to generate conflict in a story, though.

cpressey avatar Oct 31 '18 21:10 cpressey

NaNoGenMo Dev Diary, Day 1:

Good progress. I didn't imagine I would start off NaNoGenMo by writing unit tests, but Samovar is not nearly as much of an embarrassment now, and that's good.

Just don't ask where Day 0 went.

cpressey avatar Nov 01 '18 20:11 cpressey

Care to provide a link to Samovar? It looks interesting. The link https://github.com/catseye/Samovar gives me a 404 not found.

hendrikboom3 avatar Nov 02 '18 12:11 hendrikboom3

@hendrikboom3

The link https://github.com/catseye/Samovar gives me a 404 not found.

That's really weird, because that's definitely where it is (and I don't even have the means to make it private by accident, free account holder me.) Maybe a temporary problem on GitHub's end?

There are lots of changes (like, you don't have to use Greek letters for variables anymore) on the https://github.com/catseye/Samovar/tree/develop-0.2 branch, so that's worth checking out, if you do manage to get the URL to work.

cpressey avatar Nov 02 '18 14:11 cpressey

NaNoGenMo Dev Diary, Day 3:

Not much progress today, so here's an unnecessarily verbose report on the current status.

1/3 Simulation

I've implemented the "propositions world" query in Samovar, and it works fine. I'm very pleased that it is not a full inference engine, yet it suffices for my purposes.

Samovar has a NOT operator, so I had to add a "failure as negation" step (search for term in database, and only succeed if you can't find it.)

I will also need to add some kind of binding-uniqueness condition since I don't really want to keep seeing "Alice looked at Alice."

2/3 Story

Several participants this year have mentioned readability. I dealt with the issue of readability in A Time for Destiny in 2015, and readabillity is not my goal here. Instead, my goal is to tell a story over the course of 50,000 words. (A Time for Destiny arguably did that, if you look at the "She Gets the Guy" subplot, but in this case I mean, a single story, not an arc over many episodes.)

In the absence of any better ideas I'm settling on "The League of Extraordinarily Dull Gentlemen". Synopsis:

The golden falcon that normally resides on the mantel in the Great Hall, much beloved of all members of the Widgets Club, has been stolen. Accusations are made, improprieties surface, diaries are burnt. Also, Pranehurst has misplaced his umbrella, and asks people he meets if they've seen it, a lot. It is eventually discovered that [SPOILERS REDACTED]. In the end, Pranehurst buys a new umbrella.

It's unlikely that that alone will fill 50,000 words so there will almost certainly be a large amount of padding, of the Deep Hurting variety, around the middle.

3/3 Diction

I have decided that Samovar will not do any stylistic processing of the text. It will produce a straight-up "caveman narrator" series of sentences which bluntly describe the events.

Instead, I'm planning to write a separate tool that I'll use to post-process those gormless utterances into something nominally less grating to read.

In theory this other tool could be used on other texts, but I'm sure that's just a theory; the goal will only be for it to work for my use case (if I even get this far.)

cpressey avatar Nov 03 '18 18:11 cpressey

NaNoGenMo Dev Diary, Day 4:

This is more on the "meta" side of things, but, I just wanted you all to know that I've just muted issue #2 because, seriously, c'mon.

cpressey avatar Nov 04 '18 21:11 cpressey

NaNoGenMo Dev Diary, Day 6:

I've written out a sequence of plot points for the story and coded them in Samovar. Not every scene is fleshed-out, but they all have, like, a setting and some characters.

My generator script currently produces 20K words from this, and takes 2 minutes 47 seconds to run.

I'm backing away from the idea of having a diction filter that works on arbitrary text, mainly because of the unnecessary resources it will use on this novel (lots of sentences have the same structure and it will be parsing them over and over, and making it efficient just to handle this case doesn't seem worth the effort). Trying to come up with a more "semi-automatic" solution for that, now.

cpressey avatar Nov 06 '18 13:11 cpressey

NaNoGenMo Dev Diary, Day 7:

One step forward, two steps back: After some refinement of the scenarios and the diction filter, the generator started producing only 15K words. This was not unexpected, as "better" sequences of events, and their descriptions, are often shorter.

After more refinement, the number started to climb up again.

And then, I found a fairly major bug in my base rules: characters weren't actually ever dropping items. Fixing that means that all of the scenarios that run until the goal "X is holding Y" is met, are now harder to satisfy, so the generator tends to take longer to run.

Which I guess is good, because it generates more words, but generation is starting to become a bit of a wait. Eight minutes!

And now I'm seeing a "maximum recursion depth exceeded" error which I haven't seen before. Will have to investigate, later on, when I can get a few minutes.

cpressey avatar Nov 07 '18 18:11 cpressey

NaNoGenMo Dev Diary, Day 8:

The "maximum recursion depth exceeded" error was because it built a sentence tree that was too deep for Python to process recursively. Even repr(tree) would crash on it. So I put in a depth-check when creating them, so that they never get quite that big now.

Since I've set the bar at "Tell a story over the course of 50,000 words," I tried today to at least flesh out the plot points and the scenes so that the whole thing is, identifiably, a story. Nothing says it has to be a good story, of course. The point is, I have to remind myself to reach this bar first, before working on improving the little things that bug me but that don't stop it being a story.

The number of words it produces can vary a lot now, but last run it got 37K words after 4.5 minutes.

cpressey avatar Nov 08 '18 15:11 cpressey