Pages

Wednesday, March 27, 2013

Coco/R and parsing strings

Writing a parser for Stareater with Coco/R as parser generator was easy. Despite requiring slightly different way of thinking (all terminals "return void" so output is done through method parameters with keyword out) overhead imposed by Coco/R was minimal. And then I wanted to test the parser. I figured unit tests would be appropriate and while writing first unit test I got stuck at scanner constructor. The scanner is the part that converts input to tokens (lexeme) and default Coco/R scanner can be constructed either by providing a file name (or relative path) string or the Stream object. The problem was my parser was intended for in-memory strings.

Quick googling showed that string can written to memory stream with few lines[1]. If you want to be quick and don't mind bloat you can stop reading, here your solution:

var stream = new MemoryStream(Encoding.UTF8.GetBytes(text));

Parser parser = new Parser(new Scanner(stream);

Why do I call this bloated? Because:

Under the hood default scanner implementation wraps stream with buffer. The buffer does what the name says, buffering, plus conversion of bytes to characters. I'm not going to discuss their design decisions like why didn't they use BufferedStream. When implementing IKON reader for .Net I opted for TextReader as a base class for input because conversion form input to characters is guarantied by interface and there StreamReader and StringReader classes that implement TextReader and can read from any stream or from in-memory string. Maybe they had some reason to insist on the Stream but that is not the point of this post. What is the point is that I was talking about default Coco/R scanner.

Both Coco/R parser and scanner are based upon a frame file. Those files are sort of blueprints, a C# code interleaved with placeholders for generated code. When generating parser and scanner, command line tool must be supplied with those files along with grammar specification file though they don't have to be explicitly named if they are in the same folder as grammar file. By customizing scanner's frame file parsing bloat can be reduced. Below are steps for making a frame for a scanner that only accepts string as input.



Buffers can be ditched altogether since in-memory string is already buffered and converted to collection of characters. Feel free to completle delete Buffer and UTF8Buffer classes from scanner's frame but keep in mind that generated scanner's code depends on Buffer.EOF constant. I prefer to hide it as static private nested class inside Scanner class even but it's valid to just leave original Buffer as is.

static class Buffer
{
  public const int EOF = char.MaxValue + 1;
}



Next, make an reference to input string and initialize it in constructor.


public string input; // scanner input
public Scanner (string input) {
    this.input = input;
    Init();
}



Than clean up Init method. Aside form initialization, that method detect whether the stream is encoded in ASCII or UTF-8. Since new scanner works directly with characters, encoding detection can be omitted.


void Init() {
    pos = -1; line = 1; col = 0; charPos = -1;
    oldEols = 0;
    NextCh();
    pt = tokens = new Token();  // first token is a dummy
}



And finally modify NextCh method to raise "end of file" when end of string is reached.


    void NextCh() {
        if (oldEols > 0) { ch = EOL; oldEols--; }
        else {
            pos = charPos;
            charPos++;

            if (charPos >= input.Length)
                ch = Buffer.EOF;
            else
            {
                ch = input[charPos]; col++;
                // replace isolated '\r' by '\n' in order to make
                // eol handling uniform across Windows, Unix and Mac
                if (ch == '\r' && input.Length > charPos + 1 && input[charPos + 1] != '\n') ch = EOL;
                if (ch == EOL) { line++; col = 0; }
            }
        }
-->casing1
    }



That's it! In case you want to support both strings and streams you could do similar modifications with TextReader instead of string. I haven't tried that yet since the idea occurred to me while writing this post.

Saturday, March 9, 2013

Binary Domain and C-evo

It was almost to months since I wanted to write about this and there was always something in the way. I've seen first impressions of Binary Domain video game on Cynical Brit's YouTube channel and although the guy on the channel was not impressed by the game, a friend told me it was good and I was intrigued by the plot. I downloaded the demo version and what should have been fun was an agony. The controls were disaster! You'd understand my shock if I told you that I do not and never had owned a console and the game was ported from console meaning, among the other things, it was designed for a game pad instead of mouse and keyboard. On top of that I played quite a number of first person shooter on desktop computer back in '00 when they were blossoming genre there. My expectations were WASD movement, space for jump, shift for sprint, C or control for crouch, E for interaction, R for reload and mouse for aiming and shooting. Binary Domain being console port did this it's own way, WASD was there but space was kind of everything else button, sprint was some random letter (F or something like that), shift just turned character's facing direction to the left. That looked silly :). To rebind the keys you had to exit the game and use separate application. All of that would be bearable if there wasn't a lot bigger issue, aiming with mouse. When I moved mouse slowly aim kind of jumped few degrees at the time making it hard to "aim for a head", when I tried to make sharp turn, fast mouse movement was dampened to slow turn. Shooters are known to have an issue with mouse acceleration but this was opposite, mouse deceleration. Why??? Was it just on my machine or are there more players that think the game was never tested with mouse? I tried to find a quick workaround but except "use gamepad instead mouse" there was nothing. That's preposterous measure that I'm never going to take to just play a game.

After I stepped back from computer to calm down and eat lunch I figured that only reason why I wanted to play that game was a story and what is natural medium for story telling? In this case it's video and there are a few "let's plays" on the Internet for this game. "Let's plays" are something that big companies don't like and they may even be legal issue if they are monetized. But why people demand such content if video games are there to provide fun in the first place? Some games fail to deliver a fun, some games are broken, some cost more than they are worth and some people are plain lazy so they want somebody to play a game for them :). Binary Domain is broken and probably kind of game that is one time experience, once you play it through that's it, no more new stuff to do. Basically a 25€ movie, thereby it more fun to watch it than play it. What games aren't one time experience? I'd say most strategies (especially grand strategies such as Civilization series) and every game with healthy multiplayer. Out side the world of video games, chess and poker are great examples. It basically boils down to having big enough uncertainty factor and allowing player(s) to influence the outcome.

Watching a video didn't exactly fulfilled my need for interactive fun so I browsed through my good old library and decided to play a game of C-evo. C-evo is polar opposite of Binary Domain, it's PC native, can't be played without the mouse, it's turn based strategy and as most grand strategies has high replay value. I had that game for a few years and played it through about couple dozen times. It occurred to me that I never experimented much with the map size in C-evo. In grand strategies large maps tend to prolong the gameplay while not really adding to the experience, small maps on the other hand tend to be more aggressive. Peaceful expansion is concluded much sooner so wars start sooner. I opted for the smallest map, about half the size (1/4 of area) of normal map with a little bit more land. The game turned out to be easy, there were only two tiles I had to fortify in order to prevent other player from peeking and colonizing my territory and during the second session I had enough technological advantage to steam roll opponents. But the game didn't end there, oh no, it was the time form something more. It took me at least five more session to complete my plan. I could have achieved victory condition with very little effort but instead I chose to something else, something I've tried to do but never managed to pull off before, to cultivate whole planet. Since the map was small and I had almost hundred engineers (special units that can cultivate land) due to disbanding poorly placed AI's cities, it was doable. A little tedious but not to much.


That's what I expect from a game, to be a medium that can surprise and please more than once, to be significantly different experience over multiple playthroughs and to allow experimentation.