2013/06/11

PA8 - Prediction

To review, my original prediction scheme involved:
  1. Sending a packet locally with a change in state.
  2. Continuing that local action until the key is released (for movement).
  3. Changing that action and sending out a packet when the key is released.
Though this works for the sender, it is absolutely horrible for the receiver because the receiver does not know how to repeat the remote action.

PA7 - Moving Ships

First, I began moving ships by adding NetworkStateData to the Queuing system which broadcasts the network state and the game's winner. I also modified GameScene so that it only adds ships for players in the lobby. After doing this, I noticed the following anomalies:
If I start a lobby with only one player, I can move the blue ship correctly, I can shoot lasers and place mines, and the fences work. Note that this is still entirely networked.
However, when a player joins:
Input from both machines binds to the green player, the ship cannot move (only turn), and if by some chance I get velocity on a ship, it gets stuck in the fences until I drive the other way.
The inappropriate binding was caused by assigning the machine-specific GamerIndex every time the Gamer Joined event fired, so I used e.Gamer.IsLocal. The ship unable to move is due to my early version of prediction that works as follows:

  1. If the ship is not moving, send one packet.
  2. When the packet is received, start moving the ship according to that packet and repeat that movement sender-side as long as a key is down (unless it's a laser or a mine).
  3. When a key is released pertaining to that type of movement, send a packet updating the other players.
The problem is that though the prediction works for the sender, it does not loop like this for the receiver. When two players are in the game, the packets deadlock each of them.

I am now making it so that the sender only sends packets for his own events, and I'll have to figure out some way of automating movement on the receiver-side as well.

Strange bug, the blue ship fires green lasers but places blue mines. Upon looking into this, I discovered that I had removed the index parameter from the laser respawn method (and the mine method, but that magically works). I understand why the original developer uses a queue to reduce coupling, but I would have made two queues per player because each laser and mine should intrinsically belong to one player each.

2013/06/10

PA6 - Lobby

My greatest debate in this assignment was whether to base my lobby code off of the Network State Management (Windows) example, or to base it off of the Queue Demo (which creates a session or searches for a session). As someone in class mentioned, the State Management example was a monolith (or leviathan, choose your analogy). At first, I had a juggling act between MainMenuScene, LobbyScene, and OmegaRaceXGame. I kept trying to figure out where to put the lobby methods since, based on encapsulation, they should be in the MainMenuScene instead of OmegaRaceXGame (LobbyScene ending up getting only a Draw method). The methods include UpdateMenuScreen, UpdateNetworkSession, CreateSession, JoinSession, HookSessionEvents, DrawMessage, GamerJoinedEventHandler, and SessionEndedEventHandler.

A simple bug I came across was that creating a lobby always required the Enter key to be pressed twice. I found out that there was an extra DrawMessage call in CreateSession that I had to get rid of and that it would not accept the first keypress of Enter.

MainMenuScene ending up getting a single method called HandleSessionInput that handles requests to create a lobby or find a lobby. One of the worst bugs in my lobby from HandleSessionInput was the absence of !Guide.IsVisible, which caused input from the guide to go into the game and cause a NullReferenceException because the gamer was not logged in yet.

2013/05/03

PA5 - Game Data Driven

I am using the Input Sequence Sample on Windows to rewrite the Input Manager of Omega Race X. I already deleted all of the redundant methods for the keyboard, now I am implementing the Move class and removing the redundancy on the Xbox buttons. Another Java/C# comparison: I love Java's "Buttons... sequence" for variable-argument lists over C#'s "params Buttons[] sequence". I first read the C# syntax as a variable-argument list of arrays. Also, you cannot use namespaces with "using OmegaRaceX.*" whereas Java can import packages by wildcard, "import java.util.*" (though this import would slow compilation). A final annoyance is that you cannot define an object in a case of a switch statement, and then define (not redefine, because the cases are separate paths) that same object in another case. Another plus to C# over Java is an exception-less catch clause such as "try { statement; } catch { }".

Anything that implements IDictionary cannot be serialized, which makes creating a config file a pain. After the Week 10 lecture I jumped to implementing the fifth example, which includes networking, so my game will be halfway networked when it is finally data driven. I added the standard OutputQueue, InputQueue, QueueHeader, and QueueType. At first, I had several class-specific structs such as ShipData, LaserData, and MineData as well, but since they all had the same variables I reduced them to a single ObjectStruct and placed it in the IO namespace. Laser and Mine were the first to successfully send across the queues. Then, I added ship turning to the queue but accidentally set the value of the OutputQueue to Position, not Velocity (a bit of confusion can arise from the usage of Laser and Mine, which set X and Y to Position and not Velocity). Once I fixed this, turning worked.

Now boost, that was a different story altogether. My first addition to the OutputQueue did not have drag (though I knew that I needed to add it) so the ships barely moved forward. And then I noticed onCollide()... I had been manually adding the ObjectData code to each Velocity call, but when I saw multiple sets to Velocity.Zero, I added velocityZero() and sendVelocity() methods to handle these. Finally, I realized that ObjectData could not be used for everything Ship, and then resorted to using ShipPosition, ShipVelocity, and ShipRotation.

On a final note, I cut down InputManager as much as possible (dat copypasta was disgusting). The keyboard only has three methods and the Gamepad only has 5 methods now.

04/27-05/03 Assignment Log

With Java as my most fluent language, I began writing Data Driven through the lens of a Java developer. In Java, switches on enums do not require each case to be prefixed by the name of that enum, for instance this:

switch (data.type) {
case FSM_Type.FSM_ADV:
Advance(data.input);
break;
case FSM_Type.FSM_SET:
Set(data.state);
break;
}

Instead of this in Java:

switch (data.type) {
case FSM_ADV:
Advance(data.input);
break;
case FSM_SET:
Set(data.state);
}

Also in Java switches: the last case does not need a break, it automatically knows that being the last case, it does not need a break. This is especially annoying in C# default cases:
switch (enum) {
...
default:
...
break;
}
Actually, now that I think about it, I hated leaving break statements off of default cases when I first learned Java. Maybe C# and I were meant to be.

Speaking of enums, you do not end the last element in an enum with a semicolon in C# like in Java (it can be ended with a comma, but that is optional). You also cannot have private enum constructors, or enum methods.

I keep forgetting to write string instead of String, Java habit. There does not seem to be a difference between primitives and objects in C# (update: there isn't). For instance, in Java if I wanted to change a byte to a String for a parameter passing, it would be "new Byte(b).toString()", whereas I can simply type b.toString() in C#. The same goes for ints. Also, every primitive is a struct in C#.

In Java you can place semicolons after method blocks, in C# you cannot. I also learned that a field with the get; set; blocks defined is actually called a property, and is treated differently by the CLR than a field.

I kind of miss my @Deprecated annotation from Java, whereas C# uses [Obsolete]. In addition, C# has no easy equivalent to @SuppressWarnings("unchecked"), you have to use #pragma in the actual method, and then turn it off later.

2013/04/27

04/20-04/26 Assignment Log

So I wrote out about half of this log four hours ago, only for my internet to go down and blank this page, and then stay down for the next two hours.

One trivial mistake I kept making was after passing from the listening socket to the worker socket, I would still call the listening socket. This was the last bug I had before my TCP server started working. I kept trying to delete the buffer like so:

delete [] & buffer

Honestly, no idea why I ever tried that. Eventually I had to common sense to comment it out. And it also got working again. I tried to copy directly from &rNodeList.size() to a buffer with memcpy, couldn't get that working either.

The UDP server was surprisingly harder than the TCP server. I was receiving Winsock Error 10014 because I was using sizeof() wrong. Then I got Error 10013, which is a permissions issue so I had to manually add the debug executables to Windows Firewall. Turns out the Firewall had nothing to do with it. Fixed a couple things and it looped me back to Error 10049, which I had received before.

Alright, my internet's back after losing my internet over 9000 times tonight. Here are the mistakes I made converting from TCP to UDP:

Server
1. local.sin_addr.s_addr set to inet_addr(HOST) instead of INET_ANY.
2. Forgot to remove the listen() method.
3. Kept worker socket and waited with the accept method.

Client
1. Used 0 instead of  IPPROTO_UDP
2. Tried using *((unsigned long*) host->h_addr) from the tutorial for the hostname instead of remote.sin_addr.S_un.S_addr, which worked.
3. Forgot to remove the connect block and that's where all those errors came from (I should have printed the block I was receiving the errors in).

2013/04/19

04/10-04/18 Assignment Log

So I started with Bird, straight into alphabetical order and I had no idea it was supposed to be second-hardest, not that it mattered because I instinctively make everything harder for myself, so I started with bitwise operators! I wanted to try manipulating bits instead of memcpy to see if it would work, serialize:

buffer[0] = (char) (getX() >> 24);
buffer[1] = (char) (getX() >> 16);
buffer[2] = (char) (getX() >> 8);
buffer[3] = (char) getX();

And deserialize:

this->x = 0;
this->x |= buffer[0] << 24;
this->x |= buffer[1] << 16;
this->x |= buffer[2] << 8;
this->x |= buffer[3];

Never got it to work in C++. The same algorithm worked perfect in Java, so maybe some of my surrounding code is broken. I stored the size of the string in a single byte because the size can never change, and then I used a for loop to iterate through both the buffer and the string at the same time. This was broken for a while because I was missing the + 5 on the loops to reach the end of the string, without which it was grabbing random bytes of data.

I tried to get memcpy to save to the class member:

memcpy(this->x, buffer, sizeof(x));

Didn't work because it passes the value, not the reference.

I did Cat and Dog next, very simple, copying from the sample class for the most part.

Bird actually took me an hour or two, mainly because of dumb mistakes I kept making. I knew I couldn't save from the buffer straight into the apple and orange pointers to a null location, did it anyway. This was my absolute worst mistake that held me up for an hour:

memcpy(buffer, &pApple, sizeof(pApple));
pointer += sizeof(pApple);

Obviously, the size of a pointer is always 4 bytes. Even after I printed the sizeof I had to think about it for a few minutes. Also, the I changed the source parameter for memcpy to this->pApple; don't know if there's any significant difference between the two methods. I'm used to structs being capitalized (like classes in Java), so I kept trying to say:

apple = new apple();

Then I finally remembered how to declare it (generally, I name my objects as lowercase version of the class in Java and mostly all object-oriented languages). Oh, and then I forgot that structs need to be pointers, so I had tried to do this:

apple app = new apple();

It's been far too long since I've worked with structs, even though they are virtually the same thing as classes.

I also kept questioning whether I was offsetting the buffer correctly while this problem with structs was going on in the background. Simply adding to the buffer pointer seemed to easy, so I tried to do an array look-up and then a reference of buffer until I remembered that it was a pointer and that I shouldn't have second-guessed myself because I was doing it right to begin with.



Oh hey, remember that memcpy to a class member from up above?

memcpy(&this->x, buffer, sizeof(x));

Figured it out while I was writing this blog post, so I went over my code again and removed the temp variables I was using to memcpy to.