The Newest Code Monkey’s Report
Jan 11, 2016
Ever since I formally became part of the Thimbleweed Park team I've been half itching to write a blog post, half scared silly at the prospect and wanting to cower under my keyboard. But here I am. So, no regrets, right?
Maybe before I go on you'd like to know a bit about me so you can get where I'm coming from? I'm just going to assume you're yelling "yes" at your screen right now, ‘cause that's what you're doing, right?
Ok, since you insist... I grew up playing point-and-click adventure games in their "Golden Era". Ok, fine, Ms Picky-person, I grew up watching my siblings play point-and-click adventure games (being the youngest sucks sometimes). I never thought that I could be a game developer, since that wasn't something they told us at school and I was always a super-duper square who handed in my homework on time and actually liked school.
Hopefully, I haven't lost your interest with that, I promise I've improved and I'm now a much more well-rounded individual. Mostly because part-way through finishing my Masters thesis in Artificial Intelligence I realised that I'm a game designer who can code (rather than a pure coder) and my life has been much happier since that moment.
Over my 6+ years in game development, I've dabbled in making small adventure games using AGS and Unity. Working on these small projects gave me an appreciation for the amount of work that must be involved in the old games I loved so much. So I was and still am really excited and proud to be part of the Thimbleweed Park team, even if my official title in the budget is "Code Monkey #2".
Most of the work I'm doing at the moment is understanding the engine and wiring up rooms, I mean sewer tunnels. That's right, my first proper coding tasks have been in the sewers. It's a bit unglamorous. But it's actually perfect for my first tasks. Many of the sewer tunnels are purposely very similar so as to make players feel a bit lost and confused. This makes it much easier for me to implement stuff, because I can just copy-paste all over the place, I can just copy-paste all over the place, I can just copy-paste all over the place, I can just copy-paste all over the place.
As I was doing the copy-paste dance, I realised that there were a lot of valves in the background art for the sewers. Now the vast majority of these couldn't be truly interacted with, but they kinda looked like something that you should be able to interact with (especially because there is at least one valve you can actually turn). The valves are in different rooms and I didn't want to do more copy-paste for what to say for each valve. That is, I didn't want the characters to just say "It's a valve that doesn't move" every single time the player tries to look at it. It's boring for the player and also frustrating because the player wastes time looking at the object and hearing the same line over and over again.
I was reminded of an old GDC talk by Elan Ruskin about dynamic dialog using AI and fuzzy pattern matching. In that, they talked about having running jokes about objects so that you could build on what the player knew previously. I knew I didn't want to set up something quite so complex, but I wanted to do a little something to make looking at valves and using valves a tiny bit more interesting.
To do this I needed to create code that would be run for each of the valve objects that would set up its name, whether this specific valve had been looked at and what to do when looked at. So, to create one of these generic valves, you need to create it within a room using:
{
name = "valve"
enter = function() { sewerValveSetup(this) }
}
The object's enter code gets called every time the player enters this room. We have to name the object with a placeholder name, but we'll allow the setup function to overwrite that value so if we decide to call those things "excessively boring valve thingamabobs" we don't need to go through every room to change their name.
The setup function is defined in a new file I created called SewerHelpers.nut, which contains all of the generic code I used for the sewers (including setting up a sewer light, spinning fans, dripping water and so on).
So here's the code to create a valve:
// Creates a boring valve we can look at
currentValve.name = "valve"
if ( ! currentValve.hasvar(has_looked_at) ) {
currentValve.has_looked_at <- false
}
currentValve.verbLookAt <- function() {
valveLookAt(this)
}
if ( ! currentValve.hasvar(has_used) ) {
currentValve.has_used <- false
}
currentValve.verbUse <- function() {
valveUse(this)
}
...
}
Note that if this is the first time we're running this code, we'll set that this particular valve hasn't been looked at. It will also keep track of whether the player has attempted to use the valve.
So now if the player tries to look at a valve, they'll get something different to say based on how many valves have been looked at globally.
// For if the player looks at a valve that they can't use in the game
if ( currentValve.has_looked_at == false ) {
if (g.look_count_valve == 0) { sayLine("Looks like this valve has been welded into position forever.") }
if (g.look_count_valve == 1) { sayLine("This valve has also been welded into position.") }
if (g.look_count_valve == 2) { sayLine("Why have so many of these valves been welded up?") }
if (g.look_count_valve == 3) { sayLine("Another welded valve.") }
if (g.look_count_valve == 4) { sayLine("Didn't I just see this valve?") }
if (g.look_count_valve == 5) { sayLine("Lots of valves, but no Steam.") }
if (g.look_count_valve == 6) { sayLine("This one's rusted, not welded!") }
if (g.look_count_valve >= 7) { sayLine("Seen one valve in the sewers, seen them all.") }
g.look_count_valve++
// Set that we looked at THIS valve
currentValve. has_looked_at = true
} else {
sayLine("It's still a boring valve.")
}
}
So after looking at 7 different valves we'll just keep saying the same line over and over again, but at least the player got to have a bit of value for all their hard point-and-click work.
Learning how to code in Ron's engine has been lovely and much simpler than I would have assumed after hearing the words "custom engine". It's easy to use and anytime I get stuck, I just post on the Slacks and Ron or David jump right in to help out.
I've been a newbie on many projects and I'm always nervous of those first few days and weeks (yes, and months) as you get to learn the new systems. I hate that feeling of bugging those more experienced with questions that they're probably rolling their eyes about. But it turns out Ron and David are both fantastically friendly and understanding and are really quick to help me out.
As I kept bugging Ron and David I realised one of the major flaws of a custom engine. I can't just google answers and find how everyone else has tackled the problems I'm encountering.
What this custom engine needed was some documentation!! (It also needs a proper name. I suppose technically it's called ThimbleScript, but I don't think that counts, do you? Ron... get on it!)
So I got started on my next, equally unglamorous task. Documentation is one of the tasks most coders hate doing, and I'm no exception. Still, it turns out writing the documentation is really helpful to someone getting to understand the code base (yes, I'm talking about myself, shock!).
I found a list of the command names, then I had to search for them in code, work out what they did and write about it. I'd often try to include example code, yet because I had been warned that this documentation would get released to the public, I tried to find non-spoiler examples (something that was quite tough in some special cases).
I know you're on the edges of your seats waiting to find out whether I survived these tasks, and ... (wait for it)... I did! So now if anyone else is lucky enough to join the team, they'll be able to look through all the documentation and bug me instead of Ron & David!
You can check out the documentation yourselves.
Might make some of that code I just posted above make a bit more sense too!
I'm sure that's enough Jenn for today... or is it?
Someone at my co-working space brought in little pixel stickers and I got a tad overeager and put up my Thimble-Team avatar on the wall. When I'm not at my desk, it looks like Pixel-Jenn is still hard at work putting pixels in the game.
- Jenn
I see there are a methods to stop threads. May I ask how are you stopping Squirrel threads from C++? I don't see a native Squirrel API function to destroy a running thread.
Thank you again!
I'm eager to plow up the source once you release it.
I was taking it for granted, I didn't want to not-so-subtly pressure you into releasing it, provided you're not sure you'll do it.
Looking forward to your second post!!
May I make a suggestion? Switch the lines for two and three looked-at valves around; that is, have "Another welded valve." come before "Why have so many of these valves been welded up?", not after. The lines flow more naturally that way.
As for the engine's name... how about THIMM?
"I might try this valve when I feel like drowning"
"This valve probably drains the local swimming pool"
"Rusty. Like my car, algebra and sex life"
"Oooh shiny. ...Oh my bad! I'll try again. ...Oooh Rusty"
"This valve might turn on if I force it. Ha-ha! Oh, yeah, well I dare you to make a better pun."
Or: "Nah, might cause a nuclear meltdown."
"This valve just might be.....PlaceHolder 239:NOT FOUND!!! ERROR ERROR ERROR LAZY PROGRAMMING!!! PLEASE DIRECT SPAM TO RON AND GARY ASAP"
"This valve looks like it could.....PlaceHolder 627:NOT FOUND!!! ERROR ERROR ERROR!!! FORMAT C: INITIALIZED... JUST BECAUSE..."
"This valve seems... hmm... WAIT!... oh never mind..."
Now I can finally sleep soundly at nights... thank you and apologies to all
"Real Programmers don't do documentation. Documentation is for simps who can't figure out the listing."
- Tom Van Vleck, 10/25/82
Anyway, Jenn, I think you did an enchanting (look at what kind of adjective!) documentation! Thank you for your job!
Seriously though. I find I like to code by writing comments of what I'd like to happen and then I add the real code. Probably not super efficient and I'll never be the whizziest coder that ever did whizz, but it helps me think out what I want to happen and when.
And I answer: "Look at it, think about it, then doc it!" :-P :-P
Jenn, do you want to be a famous novelist, and are you waiting for a big break? (Cit.)
When I know what I want, I can write a longish, mostly correct program without leaving the editor (well, there are often a slew of syntax errors to correct, ultimately,and a few bugs that creep in).
When the picture is not clear, I like to write a pie in the sky description of what I'd like the code to do, it helps tremendously in figuring things out.
Plus:
1) It gets all that documenting straight out of the way - very little need to backtrack your way to long forgotten code.
2) You are doing it while the code is fresh in your mind.
3) It helps you define what the piece of code is supposedly going to do - I'ts a bit (though not quite...) like the difference between learning to do something and learning doing something in order to teach someone else... it sharpens you a bit.
4) You never know what you might do with the code in the future or who might be looking at it in order to contribute.
Minus:
1) Takes a bit more time - although less time if you are going to be documenting anyway.
2) It looks quite "full"\"uglier" and perhaps a bit less readable - but only on first sight and only if you really go overboard with comments.
Welcome Jenn :) and I hope to see more inside posts from you.
Jeopardized Extravaganza Neural Networks :)
P.S.: R.I.P. Dr. David Bowie :-(
But but but (I can not leave without busting your balls):
A masters in AI and all we get is a counter and a bunch of IF statement. You have to do better before the game is finished and blog about it.
No pressure ;-)
Keep up the good work!
Also... There was a reason I'm not doing AI anymore. It's fun, but it's not what I'm passionate about. I love gameplay mechanics and story.
On the other hand it would be extremely difficult to fit something mutable, such as an AI, into a rather programmatic game, such as a point & click game. I think it wouldn't be worth the effort. The monkey on Monkey Island was realistic enough. Furthermore, that wouldn't be 'classic', though I want the game to be classic!
You might be a team "junior" but it feels good to see you helping :)
A lot of the games I've done are free since they were personal projects. My website has more info. http://jennsand.com/index.php
The little adventure games I've done that I'm most proud of were Vet Lucie Disastrous (http://jennsand.com/vetluciedisastrous/vetLucieDisastrous.php) and The Will (http://jennsand.com/gameplayMechanicAMonth/mechanic_11.php).
Nice one! ^^^^
GrumpyWiki is a really nice surprise.
Non-requested possible if-line:
"I wonder if the valves turn the other way in the south hemisphere?"
first of all, I am so jealous of you! I would love to be even "code monkey #99",
but to paraphrase a scene from Jerry McGuire,you lost me at "enter = function() { sewerValveSetup(this) }"
So I am glad you shared the documentation at this point!
What would you suggest for someone with limited programming experience who wants to try and design a small graphic adventure: fool around with AGS or just wait a bit until Thimblescript is released? Undoubtedly, thimblescript is much better, but how do the learning curves compare vs what you can achieve with it?
My daytime job involves coding, but I consider myself more as a creative person trying to find (better/new/elegant) solutions, rather than using some obscure coding constructs no-one else can understand. I like your simple if-statements. Perhaps I would have used a case statement, but that's because I am scared to introduce a bug when the variable name changes for one reason or another
;)
So I'd definitely say don't wait, start making games right away.
In terms of comparing AGS and ThimbleScript... Well... It's a long time since I used AGS so I can't entirely remember and maybe AGS has changed. I remember it being pretty easy to get started in, but if you wanted to do something a bit out of the ordinary it was hard to make it happen. I felt like AGS was goof for people who aren't good coders. So if you're already coding for your day job... I'd probably say try out Unity and the 2D tools. Unity isn't great and you'd have to do a decent amount of work before you could have simple rooms set up. But what you get from Unity is cross-platform, which is really important these days.
Still... It depends what you want to do with your game. Are you just mucking around with ideas? Or do you want to sell your game? If you're just looking to make something for yourself or close friends, then the tool you choose might be different. Sorry I can't help you out more. Good luck!
I would just create a small adventure game for my children, called "the great adventure of cleaning up your room" :) So nothing cross-platform or intended to be sold. I'll give AGS a go.
Thanks for your reply!
...Close door
It's really educational ;)
... Including lots of Sierra-like deaths each time you step on a toy that is lingering on the floor! :D
If you forgot the TV on, the faucet on, the fridge door open, Zak didn't leave the dinning room, saying:
"I forgot to turn off the TV" -> automatically walked towards the TV and turned it off
"I forgot to turn off the faucet" -> automatically walked towards the faucet and turned it off
"I forgot to close the fridge" -> automatically... etc.. etc...
Then, after leaving the room, if you entered the dinning room again and then leave it, but this time everything was OK, before leaving the room Zak looked behind him, and said:
"It looks good to me."
I Love This Game!!
RATPOOP
Really Awesome Thimbleweed Park Object Ordering Program
And with a blink eye. ;O)
Thank you for sharing this, wonderful written, freshful and i can read the joy in your words, also that the job makes you fun.
Hope realy to read more entries from your side, and maybe a secret here or there from Gary and Ron. ;OP
I noticed that "It's still a boring valve." doesn't contain the information that the valve is fixed. So I suggest to make it clear. For instance: "It's still a boring valve which I can't turn."
Now I wont be able to sleep tonight.
The pixels I used didn't have all the colours I would have liked. The pixel I think you're talking about (just below Pixel-Jenn's nose on her right) is not the same blue as the painted wall. It was a grey, but it does look blue.
It's hard to tell in the original pixel images that were posted in the team post (http://blog.thimbleweedpark.com/teamthimbleweed), but I was copying straight from that.
function valveLookAt(currentValve) {
if (!currentValve.has_looked_at)
sayLine(g.sewer_valve_line[g.look_count_valve++]);
else
sayLine(g.sewer_valve_line.len() - 1); // The last entry handles the "already seen" case.
}
This is a convention I'd adopt for the whole project.
Based on snippets posted on the blog and the documentation, there are a bunch of other changes I'd make in order to improve the robustness and flexibility of the code, presuming it wouldn't require *too* much refactoring. But I will stop here because feedback of this kind is often perceived as being in bad taste (for reasons that go beyond my head).
I actually pay people good money to tell me I'm wrong... Praise is just empty words. I obviously appreciate their work on the game, otherwise I wouldn't be here. On the other hand, negative feedback can actually be constructive.
If you are interested in my actual work (not a GUI engineer, mind you), we can discuss it elsewhere. Obviously, you know how to reach me.
But you shouldn't take a suggestion as a personal insult. If the advised code is inappropriate you should rise an OBJECTION! indeed.
Regarding your other remark, I only partly agree. Even though there clearly exist differences between coders, in most respects we are more similar than we are different so common ground can be found. Sometimes it's obvious (e.g., no one will argue that indentation is a bad idea) and other times it's not but can be measured (on average, by how much did this style increase or reduce development costs by, say, leading to or avoiding bugs?).
But I mostly mentioned that because it'd help with the internationalization process, not because of aesthetics.
So, to answer the questions as to why we do it the way we do…
Code like you posted is exactly the kind of code you will find in the engine. The very optimized for speed, memory, reliability and testing, etc. I like code like that.
But for the game scripts, I like very readable and clear code, not clever code. A game scripter should be able to look at a section of code in isolation and completely understand that it does. Jenn’s code accomplish that. You look at it and you know exactly what it is doing, including seeing all the text. We code like that on purpose. While the code you posted is good “computer science” code, it’s not immediately obvious what it does, especially since the text is divorced from the code.
Embedded text in the game code doesn’t make translations any harder, all the text is automatically extracted from the source and put into databases, any changes to the text in the code is automatically tracked and updated. I’ve been doing translation for 20 years, it a system that works well for me and keeps the text tight with the code that uses it, which greatly increases productivity and creativity.
Then, once the text is in a DB, you can do all what you want, export to a spreadsheet, a text file, etc...
Maybe it's easier than I thought...
Regarding the coding efficiency, I always say "Sometimes good enough is GOOD ENOUGH". I write business code, and if I'm writing a simple function that'll be used a couple times per day, it don't need to spend 5 minutes tweaking it...speed to market is more important to my business than efficiency. I do agree that there are times where tweaking milliseconds off a routine is worthwhile when it's called countless times in a loop, but those are exceptions.
Apart from being kind of a hack, the regex solution won't work well for certain scenarios. Although I understand that you're purposely adopting a style that tries to avoid the non-obvious, sometimes it might be necessary to play with the string before calling sayLine. In particular, a string may sometimes need to be manipulated in some way based on something that has happened in the game. (Sure, maybe it doesn't apply for TP.)
For the record, the code snippet I wrote above isn't what I would have used either. I only suggested it because it seemed simple enough to adopt in an existing code base. Coincidentally and incidentally, I had to solve a similar (but not identical) problem once and what I came up with was a simple abstract universal language with generators dedicated to each translation. In the case of adventure games, the generators would probably have to be replaced by something else, where (human) translators are more involved (I have some ideas), since good dialog is such an important part of the experience. A generator that produces natural-sounding, interesting lines tailored to each character's personality is possible but requires an unjustified amount of work. For example, the language might be used like this: *<complement <person jimmy
As for the other issue, telling someone they're wrong is not an attack, it's the exact opposite. If you're wrong and I don't tell you, that's when I'm causing you the most harm. If you're right, then oh well. My philosophy here is to be blunt, even if it makes me less likeable. When it comes to solving problems, I don't really see the point of pampering and I don't enjoy when people do it to me. My observation is that doing things this way usually saves time and energy, cuts costs, etc. Being blunt doesn't make you right, of course, but it can really help. I don't have a problem with being proved wrong either, since being right about something is not the point at all. When it comes to other aspects of life, sure, I'll be just as considerate as anyone else. That's not very interesting.
You don't take anything on board.
Why not save everyone time and just stop commenting for a while?
And you are doing it again, for a reason beyond my imagination. If you had read Rons answer carefully enough AND had ACCEPTED its meaning you would have replied nothing at all.
All your points are valid, from your guessed and assumed point of view.
Phoenix would say: "Your Honor! That statement contradicts this evidence!"
Bogdan: " [..] Sure, maybe it doesn't apply for TP. [..] "
Mr. Barbu, perhaps the hardest thing to understand sometimes is that there may not be a single, One True Way of solving a problem; and that your solution is just as valid, efficient, measured, elegant, or practical, as someone else's. In such cases, the only difference between the solutions is that only one came from your own initiative.
Accepting this fact as part of human nature -- or just life in general -- is a very hard lesson to learn, one that apparently has eluded you. I know you will probably disagree with my comments above (just as strongly as you have disagreed with Mr. Gilbert's position), but here's my point: that's quite alright. :)
I will leave you with a quote from Shakespeare, which seems rather a propos to aid in this lesson:
"There are more things in Heaven and Earth, Horatio, that are dreamt of in your philosophy."
-- Hamlet
Best regards,
-dZ.
The only thing that I did was to add a comment above with the original text. :(
Looking forward to see how you actually handle this, because I guess that must mean you do some sort of preprocessing before compiling to actually replace those strings with the actual calls!
Thank you!
Ace Attorney is the second most beautiful game I've ever played!
Feeling a little bit envious :) (well... a LOT actually!) ;)
Sorry!
(a bit late... sorry)
Welcome Jenn :)
I wish you a warm stay with your new team and hope you write more and feel welcomed by all. I think posting here could be a really nice place to rest, vent and express yourself in a non-formal and friendly forum about things that are on your mind.
Hope to read more of your views... no pressure :)
If you can't find it - consider adding\dropping people with suitable letters just to make it so... lawyer\water-boy\janitor and the likes... heck, why not call it JANITOR (Jenn-And-Notable-Important-TeamMembers-Of-Ron or Just-Another-Novel-InterNational-Team-Of-Revolutionaries)... hmmm I just kind of like the name JANITOR for an engine... especially for an old-school engine... if you don't use it I will :)
It is so great to see you working on classic point'n'click games again. The art style looks more Maniac Mansion than Monkey Island. And the interface alone is very nostalgic to see. Great post and great progress so far! Can't wait to play it for the next 25 years!
"This thing looks like it was welded by a Krogoth"
cutscene(
@()
{ // Do main cut scene commands here, like regular code.
playObjectSound(soundHandDryer, QuickiePalBathroom.quickieDryer)
actorPlayAnimation(currentActor, animReachMed)
breaktime(2)
...
},
@()
{ // This is called if player presses ESC
fadeOutSound(soundHandDryer, 0.5)
...
} )
Is there some trick? I thought it looked cool, and wanted to try it myself, but Squirrel is complaining about a missing "=" during compilation...