Thursday, 24 July 2014

Custom AIs and whatnot: Parsnip Theory Alpha 11 released!

I've done another update to Parsnip Theory, which you can get... at, well, the link in this sentence.

Aside from the change in the table graphics so you don't accidentally mistake them for vents (yeah, they don't sit well with me, perhaps I could do some palette changes), I've given the AI system an overhaul.

By that I mean I've made it possible to write your own AIs in Lua.

Currently there's a crouching-aware version of the AI that's been there since I put AI in.

If you'd rather revert back to the old AI, just make the Lua side throw an error and it will jump back to that. Be very aware of this when writing your own AIs, because it can catch you out.

There are two functions that are added:
  • game.astar_layer(layer_index, x1, y1, x2, y2) - returns a list of directions (order is: 0=south, 1=east, 2=north, 3=west) to get from point 1 to point 2, or nil if you can't get there. WARNING: While point 1 can contain a player, point 2 cannot! So you'll want to find a neighbouring cell for point 2.
  • game.line_layer(layer_index, x1, y1, x2, y2) - returns a tuple (success, xt, yt) where (xt, yt) is where something thrown would actually land, and success is a boolean indicating if you actually managed to hit point 2.
The AI is called from time to time via a function called hook_tick. It takes one argument, conventionally "gstate". This is a table containing:
  • tid: The ID number of your team, useful for identifying who in the object table is actually in your team.
  • ocount: Number of objects in the object table.
  • layers: A table containing tables of the form:
    • x, y: Top-left corner of the layer. Do not assume this is 0, 0.
    • w, h: Width and height of the layer. Do not assume this is 40 x 40.
    • data: A 2D table in order [y][x] (not relative to x,y, so [0][3] really does mean (3, 0) in space!) containing cell data of the form:
      • ctyp: Type of cell. This is a string. Valid types are: "oob", "floor", "solid", "backwall", "table", and "layer" (which is unused and intended for transitions between layers.
      • tset, tidx: Tileset and tile index respectively - what the tile actually looks like.
      • p1: A parameter for specific kinds of tiles. Currently unused.
      • has_ob: A boolean indicating if there is an object on that tile or not.
  • objects: A table containing tables of the form:
    • otyp: What type of object this is. Valid types are: "player", "food_tomato". Note, you will probably never see food_tomato as while that object exists, the AI tends to not be called. Still, don't assume everything is a player.
    • layer: Which layer this object belongs to.
    • cx, cy: Which cell this object belongs to on its respective layer.
    • ox, oy: The pixel offset of this object. Note, this probably shouldn't be here, so don't rely on it, and expect me to remove it.
    • steps_left: How many steps this object can take - undefined for things that aren't players, but it's there.
    • health: How much health this thing has - once again, undefined for nonplayers.
    • flags: A table of certain things:
      • flags.crouch: True if this object is crouching.
    • fd: A table containing things specific to a given object. The stuff this contains for a player is:
      • team: Which team this player belongs to.
      • face: Which direction this object is facing (0=south, 1=east, 2=north, 3=west). Mostly irrelevant in terms of strategy.
gstate is generated every single time the function is called, and it's a copy of the current state. Modifying it will not allow you to cheat, as it won't affect the actual state of the game.

hook_tick must return one of these things:
  • "idle": Do nothing.
  • "newturn": Move onto the next player. You may need to return this several times, due to the turn change delay (which is there to prevent desyncs)
  • "move", x1, y1, x2, y2: Moves player at point 1 to point 2. You MUST ensure that the player at point 1 belongs to you, and it's YOUR responsibility to ensure there is a valid path. Use game.astar_layer to get the exact path.
  • "attack", x1, y1, x2, y2: Uses player at point 1 to attack point 2. You MUST ensure that the player at point 1 belongs to you, and it's YOUR responsibility to ensure there is a valid line of sight. Use game.line_layer to check this.
  • "crouch", x1, y1: Crouches player at point 1. You MUST ensure that the player at point 1 belongs to you and it isn't already crouched.
  • "stand", x1, y1: Stands player at point 1. You MUST ensure that the player at point 1 belongs to you and it is currently crouched.
That's the API, anyway.

So whether or not you read or understood any of that, go ahead and give Parsnip Theory a whirl today!
http://fanzyflani.itch.io/parsnip-theory

If you have any issues, there's always the comment section and my twitter @fanzyflani, and there's also a Facebook page for Parsnip Theory.

Have fun!

Friday, 11 July 2014

How to write a game in assembly/machine code in a small number of hours

Coding games in assembly ("machine code" isn't quite accurate but it gives an idea) is viewed in many different lights. On one hand, there are people who will view it as useless and outdated and point you to the nearest compiler game development system that they've heard of. In other words, they'll tell you to use Unity, Game Maker, Construct 2, or whatever other buzzwords they've heard of lately.

However, you may be constrained by certain limits. Yes, I can make a game for Windows which fits on a floppy disk and has music and sound and stuff (if you strip away the non-Windows stuff from Parsnip Theory, I suspect it will actually fit, although you may need to remove the server), but it sure won't use Unity, and it sure won't use Game Maker either (barring maybe Game Maker 4.2a if I decide to take the piss).

Either way, it's more fun writing it in 80186 assembly. Pure 8086/8088 assembly is a bit limited for my tastes, and the oldest x86 CPU I have is 80186-compatible, so the 80186 is actually a really good target if you're taking the x86 route.

Or you may be doing it for the challenge. Which brings us to the next group: There are people who are genuinely shocked that it's actually possible to write software in assembly machine code techno whizz-kid language and will revere you as a god. The upside is that you get praise. The downside is that they'll refuse to listen to you when you try to explain it.

It's not as hard as people think it is. It's just something you have to try.

But it is a little bit harder than what you're used to. Yet if you program it well, get a bit of practice, and follow these principles, you'll be very surprised at how quickly things will come together.

It will also make you a better programmer.

... well, it'll teach you how to comment properly, at the very least.

Use a modern assembler and a modern emulator on a modern computer.
That way, you won't be wasting time rebooting, burning media, trying to remember what address "draw_box" is, trying to remember the format of the ModR/M byte, or any of that crap.

Of course, this part is pretty easy. Your whole toolchain is, at the time of writing, smaller than any Unity game ever produced... barring maybe your text editor, shell, terminal emulator, IDE, and all that crap - but let's just assume you have those already. Either way, it'll be smaller than Titanfall. That game has no right to be 15GB. Heck, it's even bigger than the Unity IDE itself!

But enough ranting about how everything is complete and utter bloatware. Let's get on with the other tips.

Comment your code properly.
Make a comment at every logical step ("Calculate video pointer", "Draw pixels", "Return" for example). You will completely and utterly lose your sanity otherwise.

Also, put a comment before every subroutine call in your code to point out the input registers/flags, output registers/flags, and which registers get trashed. Always assume that, when you call your code, the flags will be trashed - and thus, you don't have to note that the flags will be trashed.

If you've done this before, use your old code as much as necessary.
Another way to word this is "I really, really do not want to have to waste another 24 hours getting scrolling working properly on a Sega game console". If you have a good framework, keep it.

Know a very high-level language and use that to assist with tables and calculations and whatnot.
Python is my weapon of choice, but as a VIM user, I also tend to use some of its nicer features to generate at least some of the tables.

Still, sometimes you need to make a sine table.

Define named constants instead of using magic numbers everywhere.
You're using a modern assembler. If you need to change something like, for example, the maximum number of objects you can have (you probably will), or the address where something is stored (assuming you aren't letting your assembler handle the addresses or if your assembler sucks), it will be far easier if you have a constant you can change.

Don't prematurely optimise your code.
1MHz is a lot faster than you think.

Either way, good code is more important than fast code. You can make it go faster later.

If you do need to optimise, the general rule is to only optimise the innermost loop. Oh, and comments will help you make sense of it later.

Make lots of things into subroutines.
Not only does this make your code easier to manage, it also tends to make it smaller, too! Are you doing something twice in two different places? See if you can rewrite it as a subroutine.

Make structs, arrays, and function pointers.
Those of you who don't realise that C doesn't always have a ++ or a # after it may not know what I mean, so I'll explain them.

A struct (short for "structure") is, well, a structure. It's basically a definition for a block of memory which has certain values in certain places. I'll give you an example in C:

struct obj {
  short x, y;
  char vx, vy;
  char sprite_slot, sprite_tile, sprite_count;
  char flags;
  char timer_stun;
  char timer_shot;
  void (*f_tick)(struct obj *);
}


And a similar example with the WLA-DX assembler:

.struct obj
  x  dw
  y  dw
  vx db
  vy db

  sprite_slot  db
  sprite_tile  db
  sprite_count db

  flags db

  timer_stun db
  timer_shot db

  f_tick dw
.endst


An array (and if you haven't heard of this, you do not know how to code well, so put your "but I know C#" shades down and listen because this is fundamental) is a usually-fixed-size list of... something. It can be a list of numbers. But it can also be a list of structs.

See that f_tick thing? If that confuses you, then I guess it would be new information for me to remind you that it's called a function pointer. Now, if you're going to be programming in assembly, chances are you already know what a pointer is. So, of course, this is a pointer which points to a bit of code. Yep.

Here, have some code. This one's for 68000, sorry, because the Z80 equivalent is a bit ugly... and the x86 equivalent is even worse.
(Mind you, the 68000 version is pretty ugly, too.)

tick_object:
  ; Push registers to stack
  movem.l d0-d7\a0-a6, -(a7)

  ; Call function
  movea.l OBJ_f_tick(a0), a1
  jsr (a1)

  ; Pop registers off stack and return
  movem.l (a7)+, d0-d7\a0-a6
  rts


... actually, that's pretty ugly. Here's the 16-bit x86 version.

tick_object:
  ; Push registers to stack
  pusha

  ; Call function
  mov ax, .fromcall
  push ax
  jmp [bp + obj.f_tick]

  .fromcall:

  ; Pop registers off stack and return
  popa
  ret


...ok, that was actually much less ugly than I thought it would be.

Yeah. That's one of the rare cases where x86 looks nicer than 68000.

Anyway, using an array of structs which have function pointers in them will make your life much, much easier.

Speaking of arrays...

Don't write malloc(). Just use arrays, and a subroutine to look for a free slot.
I've done this plenty of times.

Of course, you have to find some way to denote if a slot is unallocated, but once you've done that, it'll save you a lot of pain!

Using instructions which write immediate values to RAM makes it much easier.
I've done this several times before. I'll point out the ones for several CPUs:
  • x86: mov word [address], value ;;| you can use byte, word, dword, qword, although some aren't available in certain CPU modes
  • 68000: move.w #value, address ;;| .b, .w, .l can be used, also did I mention this syntax is backwards? Because it's backwards.
  • Z80: ld (ix + offset), value ;;| OK, there are limits to doing this. offset is a signed byte, and IX is a 16-bit pointer to somewhere. But if you're modifying a structure, this helps a lot.
For some CPUs, however, you'll need at least two instructions - one to load the value, and one to store it in RAM. 6502, MIPS, and ARM are all like this.

If you have local labels, use them.
NASM (x86) has the .label syntax. I'll give an example here:

  ; Input:
  ; ES:DI = pointer to video memory for top-left corner
  ; CX = vertical length of line
  ; AL = color of the line (of course it's actually spelt "colour" but by convention we have to spell it wrong)
  ;
draw_vline:
  push di
  push cx

  .lp_y:
    stosb
    add di, 319
    loop .lp_y

  pop cx
  pop di
  ret


In this case, the .lp_y label is actually draw_vline.lp_y, but doing stuff this way really saves you pain.

The WLA-DX multi-CPU assembler (highly recommended!) has the + and - series of labels. These are amazing, especially when you're doing stuff like this (Z80 given as an example):

    ld de, SCREEN_WIDTH_BYTES - WIDTH_BYTES
    ld c, HEIGHT


--: ld b, WIDTH_BYTES
 -: ld a, (ix+0)
    ld (hl), a
    inc ix
    inc hl
    djnz -

    add hl, de
    dec c
    jp nz, --


By the way, when I say "don't prematurely optimise"... I kinda did a little. The improvement is minimal, but simple enough that we can get away with it. See that jp opcode? It takes up 10 cycles regardless of whether it is taken or not, and a jr takes up 12 cycles when taken, but only 7 when it isn't. Seeing as the branch is taken more than 40% of the time, I'm using jp here.

Which leads us to another tip.

Knowing your cycle counts and other timings can be helpful for when you do need to optimise.
The cycle times for Z80 are kinda hard to memorise, but the main points are:
  • 4 cycles for M1 access (first op OR the byte after a DD/FD, ED, or CB)
  • 3 cycles for regular memory access
  • 4 cycles for I/O access
  • 5 cycles for (IX+dd), (IY+dd) or JR/DJNZ PC+dd calculation (the calculation for JR only takes place if the condition succeeds)
The cycle times for 6502 are easier, but there are still some snags. Main points:
  • 2 cycles for first byte of an opcode
  • 1 cycle for memory access
  • 1 cycle to calculate a 16-bit carry if need be
Often there are other cycles used, though, so be wary!

But this isn't a tutorial on how to calculate cycle timings. Look elsewhere if you want the timings for your CPU. As for x86 cycle timings... I don't even try.

-

Anyway, there's a lot of things that can be talked about, but that should be enough to give get you somewhere.

Tuesday, 8 July 2014

Adventures in voice synthesis, part 1

Drake M. @DMODP:
@fanzyflani Ugh. I hope you're writing this effort down and plan to make a tutorial on it. Super-curious.
I think that would be a good idea.

I'm working on a new game with a working title of "HeroGraze". The name isn't exactly the best, but at the time I came up with it, putting it in quotes yielded no results on Google, so there we go!

The idea: You're a girl who wants to be a superhero, and a song plays in the background with words saying what you're doing, what you need to do (worded as what you're going to do), and how awesome and brave and heroic you are.

Basically, I'm making a real "feel good" game.

The catch is I have to write a voice synthesiser. At the moment, what I have is 6 vowels being sung at about C4, fed through AutoTalent, and clipped into small loops.

And then I have these terrible consonant samples. These are played as one-shot samples.

I then use a custom mixer for sackit (my .it playroutine) which detects when a particular sample is played, sets the sample volume to 0, and steals the pitch and note-on status and stuff so it can mix the voice synth over the top.

Steps required

There's a few steps that need to be done to make this work. This paragraph is pretty useless, but I really feel like there should be some text here before I drop in a subheading.

Music

This is created with SchismTracker, and the samples mostly created with SunVox. These fly out of my arse like a hot curry. I put annotations on the lyric line to point out which notes have to rhyme with which notes, at what point a phrase starts, and when to reset the rhyming pattern. These annotations aren't read yet, sadly.

Dictionaries

Lists of verbs and nouns and adjectives and whatnot. There's a bit of overlap with these. There are a lot of words to add,

Facts

This step is still being worked on. Facts will be stored in a tagged tree sort of structure.

Bullshitter

This step hasn't been started on yet. The idea is that it can take facts and exaggerate things and can even conjure up complete untruths.

Phrase generator

This step is still being worked on. Basically, you give it facts and it spits out valid phrases in different forms.

Pronouncer

But of course, you need to take these sentences and work out their pronuncations. Firstly, so the speech synth can actually do something. Secondly, so you can tell what rhymes with what.

This uses a dictionary to accept English words and spit out Lojban-esque syllables. No, Lojban isn't going to be used for the reasoning - we're trying to make the song not sound Lojbanic here!

Poeticiser

This step hasn't been started on yet. It gets stuff from the phrase generator and works out pronunciations, and then matches it up with the annotations and gets it to fit and rhyme.

I'll need a synonym dictionary for maximum effect.

Synthesiser

It accepts syllables starting with optional consonants, having one or two vowels in the middle (I'll need to raise this limit), and ending with optional consonants. For example, several possible pronunciations of "heroically":
  • 'iro,ykyl,i
  • 'iro,yk,li
  • 'yro,ykal,i
 The quote is an "h" sound, and the "y" is the vowel you get when you let your mouth rest (kinda like an "uh" sound).

Issues

The code for the phrase generator is a complete and utter mess and I'm struggling to get my head around it, so I'll need to do some rewriting of it. But I'm happy with the form the facts are being stored as.

I really should be treating "n" and "m" as vowels. This would mean that I would have to be able to handle more than two vowels in a row.

And of course, the best way to fix that is to move the vox synth control to the Lua side.

For the other consonants, while stuff like k/p/t would be best done as one-shots, stuff like c/f/s/'/x (note, "c" is a "sh" sound, and "x" is just plain weird!) would be best done with the vowel engine.

And of course, let's not forget that there are voiced and unvoiced consonants.  j/c, f/v, s/z for the vowel engine, and k/g, p/b, t/d for the consonant engine.

I could possibly change the samples and make a Karplus-Strong synth.

My main foci at this stage, however, are the sentence generation step, and, well, the game itself.

At least I'm using Lua this time. Makes it much easier.

Tuesday, 1 July 2014

Alpha 9 is out! Sorry about the radio silence.

I've mostly been saying everything on my twitter account @fanzyflani, and so I got a little carried away and forgot about this blog. Oops.

Anyway, Parsnip Theory alpha 9 is out. Most of what happened during 5 through 8 has been cosmetic or bug-related, and as you can tell from the screenshot I've done some huge improvements to the graphics since then.

Oh yeah, I also added in an AI. And an 8-player map which I tend to show off all the time these days.

However, alpha 9 adds in a new gameplay mechanic: Crouching. The idea is that you can crouch behind a table for cover. Suddenly, those white things are no longer just obstacles.

I'm looking for people to test this mechanic (mostly so it's not stupidly lopsided), so contact me on twitter if you're interested in a netgame. Because I'm up for it.

Download: https://fanzyflani.itch.io/parsnip-theory
Twitter: @fanzyflani

Thursday, 19 June 2014

Parsnip Theory Alpha 4 released - usability + sound

I've pushed a new update for Parsnip Theory. It makes it less annoying to play, although currently you can't turn the music off so it might be more annoying. Oh well, the setup screen stuff can happen in Alpha 5 - I'm mostly just trying to test usability at the moment.

No fancy picture, but here's the music rendered to an .ogg: https://dl.dropboxusercontent.com/u/32094129/parsnip/trk1-dem1.ogg

Download: http://fanzyflani.itch.io/parsnip-theory

Monday, 16 June 2014

Parsnip Theory Alpha 3: This Time You Can Actually Play It Networked

Since Alpha 2, it's possible to play Parsnip Theory over a network.

But with Alpha 3 out the door, it's now feasible.

I've added a "game setup" menu to the game. This allows each player to pick which players they want to play as, and also allows for a higher playercount (I've capped it at 16, although the editor only goes up to 8 at the moment).

No more "only stuck with two players".
No more "stop ending my turns for me".
No more "stop making my moves for me".
No more "don't move until I've joined".
No more "make sure you have the right version of the map".

Although this did require a couple of #ifdef WIN32 lines simply because Windows. But of course, there's no such special code required to get this to behave on both BSD and Linux. I could rant about this all day but whatever.

As usual, the HTML5 version is updated (although I haven't added in the new map): https://dl.dropboxusercontent.com/u/32094129/parsnip/index.html

It also runs notably worse than it used to. If you find it's driving you nuts, it would be a good idea to download the real version.

Anyway, enjoy! And if you have any issues, please prod me on Twitter: https://twitter.com/fanzyflani

Download away: http://fanzyflani.itch.io/parsnip-theory

Thursday, 12 June 2014

Parsnip Theory Alpha 2 released!

Featuring a Linux build, flimsy but playable 2-player network support, and it's a bit easier to run stuff now.

Not convinced this is the game for you? Here's a gameplay video.


Download for free here! http://fanzyflani.itch.io/parsnip-theory