Tuesday, November 25, 2008

Thoughts on Advertising

This is actually a topic that I've been thinking about for a while now, going back to a conversation I had at with Corvus Elrod at his "moving to the West Coast" party. We were discussing advertising in games, and generated a lot of interesting points. As a storyteller, it is anathema for him to see anything that pulls a player out of the game experience, and have them thinking about products in the real world. I agree with this, to a point. When one thinks of the rise of casual games, and the free product business model, it has to be conceded that advertising is one of the main revenue streams (and therefore driver of) the games industry. Armor Games was a company that I though had nothing but cheesy, vapid fare, until I was given a disk by the people of the Indie Games Showcase. Some of their offerings, particularly in the puzzle category (which I usually avoid), blew my mind with their innovations. This company, which is producing quality (though flash) games, is completely supported by the ads on their site (as far as I can tell). 


However, I just bought a new cell phone (a Google G1, in fact, and I love it), and got my first telemarketing call on it a few days afterward. Rather than encouraging me to buy whatever product they were hocking, all that call did was remind me to add my number to the "Do Not Call" list. So, the real question here is, why don't I mind unsolicited advertising next to games I play online, or even in my email, but there's a national response to telemarketing and spamming?

One word: Interference.

If advertising interferes with what you're trying to do, then it's a problem. Calling my phone, or sending me email, wastes time that I would otherwise spend doing other things. Interrupt me, and you're not hooking me as a customer. If I'm already on a website (or watching TV, or at some sort of sporting event), I'm an audience member already. If I happen to look at an ad that is up while I'm waiting for the next play, that's not interfering with what I'm trying to do, it's not breeding any resentment in me. In fact, if it's entertaining enough, I might even be glad it's there. Just look at how many funny/amazing commercials have been posted, by random people, up on YouTube. I readily admit that I would not buy Brawndo if they hadn't gone with an over-the-top, hilarious ad campaign.

Personally, I have no problem with advertising. In its most basic form, it's just trying to get you to buy something. That's fine, if we didn't buy things, we wouldn't really have an economy. Buying things drives innovation, and makes our lives easier. I even don't have a problem with targeted advertising. Some people say it's an invasion of privacy, and it is slightly, but personally, I'd rather not be pitched Depends while I'm checking my email. I see nothing wrong with companies collecting information about buying habits, customer satisfaction and whatnot. It's all supposed to make things more efficient and convenient.

However, if advertising gets too pervasive, too omnipotent, then we have a problem.

Wednesday, November 19, 2008

Dealing with Null or "All" Parameters in MS SQL Reporting Services

I finally had something happen to me at work that I can add to this blog. Oh happy day, I feel as if I might faint.

Either way, I've recently become the SSRS "rock star" at my office, where even my bosses are stopping by and asking me questions about how to get their queries/formatting to work. It's not as glamorous as I thought it would be, and the groupies haunt my nightmares, but it's fun...ish.

Anyways, the person who was running the Reporting Services before me had an interesting way of dealing with using parameters to filter results in the SQL Query. I know there's a way to filter just using the wizard, but I'm of the opinion that it is probably faster (or at least more efficient) to do filtering in the query. For our filters, if the user doesn't want to use that filter, the first option says "All", but actually has a value of null. The way my predecessor would handle this would be to have a single query for every combination of null/not null parameters. So, if there was one parameter that could be null, there would be two queries, and IF statements to select which one. If there were two nullable parameters, that number raised to four. Three meant nine separate queries...et cetera

This wasn't really acceptable to me (mostly because I didn't want to have to add five more queries), so I looked to see if it was possible to use the IF statements right in the middle of the WHERE clause. No dice. So, what I did instead was use the following style of WHERE filter:

WHERE/AND (@param is null OR object.RelevantValue = @param)

This way, if the parameter the expression would still evaluate to true, and the filter statement would only matter if the parameter was actually specified. Now, the report query is 1/4 of its previous size, and 1/9th the size it would've been if I had done it the old way. Rock star? Maybe, maybe not, I don't care. This is just what I do.

Monday, November 17, 2008

Tables for Layout?

I love this

http://shouldiusetablesforlayout.com/

It was a response to a page that claimed that the average time that a web developer took before abandoning CSS for web layout was 47 minutes. Anyone who has worked with me (or watched me work on a web project) will vouch that I have toiled untold hours of my life away, simply trying to get a CSS layout to work. It warms my heart to see that other people are willing to be as fanatical about standards and best practices as I am.

Also, I'll be at the Philadelphia Game Expo this weekend. If you're there, say hi!

Sorry about the lack of posts, I've been working quite dilligently in what little free time I've had, but hopefully some more posts will be forthcoming soon.

Wednesday, October 22, 2008

Lock's Quest Recap

This is sort of a game review, but also a discussion about interface design, and the nature of fun in games. I recently purchased Lock's Quest, which was the best Strategy game at E3, which for a DS title, is pretty impressive. It's developers also put out Drawn to Life, a game that I wanted, but never got around to purchasing. Basically, you're a special boy in a war...blah blah blah, the story isn't really that important, but the game itself is really involving. 


You have so build walls and turrets to fend off increasingly large and tough armies of clockwork robots. That in and of itself sold me, but the DS's touchscreen is really well implemented in both combat and repairing damaged structures. Depending on what you're trying to do, there's an associated task (pulling a lever, spinning a gear, etc) that makes the action you're attempting go faster, or more effective. While this sounds kind of hackneyed and repetitive, they're mixed up nicely, and you don't just sit around for 10 minutes doing the same thing over and over again.

I'm also really impressed with how they set the flow of the game up. After a few rounds with your defenses, you have an impenetrable death-fortress, and you're able to handle even the most vicious onslaughts. Then, the game tells you that you have to go grab an objective all the way on the other side of the map. It's frustrating, yes, but it breaks the whole build-repair-fight cycle, which is nice. It's also worth noting that there's few things more awesome than watching wave after waves of bad guys crash on the rocky cliffs of your defenses.

One thing is infuriating though. The screen doesn't zoom out, and there's a fixed speed to the camera, so panning from one wall to another can sometimes take precious seconds. This in and of itself is not too bad, since (if you're like me) you can set up relatively smaller, close defenses. However, when you pan to a location, and then tap (which tells your character to move there), the AI that keeps the main character from running aimlessly into a tree is a little bit lacking. So far, I have lost a solid number of turrets, and even a level or two, just because there was a rock or tree or bit of something in the way, and the flax-haired hero did nothing except struggle against the properties of matter until I realized and gave him an easier location to get to (after a while, you get used to tapping out smaller, shorter paths).

With that behind me, I do have a few more good things to say about this game. Despite the "ehh" story, the game has a good length to it. I thoroughly enjoy the game mechanics that are in place, and was worried that with all the cool stuff in it, that the levels would be cut tragically short, leaving me wanting more, much like when you when you visit home, and only get a single strip of bacon, because that's "healthier." Don't worry, this game has platters of bacon. I've been playing it for about two solid weeks now, mostly during my commute and at home and I think I'm only 2/3 of the way through it. 

Also, the difficulty is really well done. While I didn't like that there wasn't a standard dial-a-difficulty, which would be pretty easy to do with this game, I was able to clear most of the levels on the first try, though a good number of those were nail-biters, and a couple of them required a do-over. The increases in challenge over time are well-paced. I never felt too bored, and when I was right on the edge, something new popped up. Also, there's a fun little seige mini-game which would be right at home as a flash game.

Monday, October 13, 2008

Using A LIKE Operator With A String Parameter In SSRS

Hey-o,


Just something quick that I figured out today, that I felt warranted sharing. I'm doing some work with SQL Server Reporting Services (SSRS) and the Business Intelligence Development Studio (BIDS), creating reports for work. Today, I was asked to create one that used a LIKE statement in a WHERE clause where the argument passed to the LIKE statement is a string parameter. The challenge here is that concatenating "%"s to the parameter didn't work in the query, nor did the same process in the Dataset -> Filter By tab. The only possible answer I could find was on experts exchange,  which requires a subscription, so I had to figure it out myself. Here's what I did:

  1. Create the String parameter you actually want to search with. Make sure that it can be blank, but don't let it be null. For the sake of the example, I'm calling it Filter.
  2. Create another, hidden parameter that is also a string. Make sure it is hidden! Then, set the default value to be non-queried ="%" & Parameters!Filter.Value & "%". I gave this parameter the name FilterFormatted. Now, hit okay and close out your Report Parameters.
  3. Now, in the query, all you need to do is write "WHERE columntofilter LIKE @FilterFormatted". If you're testing the report, make sure you put the "%" before and after your search string, but going to the "Preview" tab will let you test it the way it'll be deployed (though you should probably know that already).
That's it! Interesting problem, simple fix, solution posted.

Wednesday, September 24, 2008

OpenSocial Version 0.8 App Development From Scratch


With the newest version of OpenSocial out, I felt compelled to revisit it, despite the fact that my new job couldn't have me farther from it. A labor of love, some would say, to stay up-to-date on a constantly evolving platform, like trying to hold onto pudding with your bare hands. Anyways, where was I?

I'm going to develop an OpenSocial 0.8 app from scratch. Let's get started. First, I need to set up the initial scaffold.


<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Pandaface">
    <Require feature="opensocial-0.8"/>
  </ModulePrefs>
  <Content type="html">
    <![CDATA[
         <div id="gadgetdiv">
             Panda!
         </div>
    ]]>
  </Content>
</Module>

This app is going to be named "Pandaface," as you can see from the line (  ModulePrefs title="Pandaface"). It's also important to point out the gadgets.util.registerOnLoadHandler line. This sets the Javascript function that will execute when the gadget loads. Usually, one would define this in the body tag of an html page, but declaring it in that manner makes sure that the callback function (in this case gogoGadget, I prefer that over "main") won't execute until all the gadget code and its dependencies are loaded.

Step 2 is setting up the first data request. In my first request, I like to get the OWNER and VIEWER objects. These will allow me to know if the viewer is looking at their own version of the App or someone else's. To do this, gogoGadget is changed to look like this.

function gogoGadget()
{
var request = opensocial.newDataRequest();
request.add(request.newFetchPersonRequest("OWNER"), "get_owner");
request.add(request.newFetchPersonRequest("VIEWER"), "get_viewer");
request.send(response);
};
This creates a request object, adds two FetchPersonRequests, and sends the request out, saying that the function 'response' will deal with the data response. It is important to note that the callback will not execute until it has all the data. Let's lake a took at the response function.

function response(responseData) { owner = responseData.get('get_owner').getData(); viewer = responseData.get('get_viewer').getData(); };

Not terribly interesting at the moment, but it is important to note a few things. In gogoGadget, the last paramter that is given is a name for the specific response. MAKE SURE THESE MATCH UP. In my other OpenSocial adventures, I got that mixed up at one point, and couldn't for the life of me figure out what was going wrong.

Now, let's pull down some persistent data. I'm just going to have a single variable, face_data, and pull it from the orkut sandbox server. To do this, I have to declare another datarequest, and add a newFetchPersonAppDataRequest. Back in .7, you could just pass the user's id, and the name of the app data you wanted to pull. Now, however, you have to create an IdSpec object for it. This is new, and fairly unintuitive (especially to people who've been working with it as long as I have).  Here's the full data request.

var ownerspec = opensocial.newIdSpec({ "userId" : "OWNER" , "groupId" : "SELF"});
var datarequest = opensocial.newDataRequest();
datarequest.add(datarequest.newFetchPersonAppDataRequest(ownerspec,"face_data"),"face_data");
datarequest.add(datarequest.newFetchPersonAppDataRequest(ownerspec,"face_url"),"face_url");
datarequest.send(loadUI);

Make sure to pay attention to what you name the variable, like with the personrequest. You also have to make sure your idspec is constructed correctly. At this point, you can only have "OWNER" and "VIEWER" for "userId", and "SELF" or "FRIENDS" for "groupId." There's also an important thing to note at the 3rd line. The first "face_data" is actually the name of the variable the request is supposed to retrieve. Along with named variables (which return as "null" if they haven't been set yet), you can also just put a *, for all variables associated with your app. On the response side, there is another main difference from the person requests.

function loadUI(responseData)
        {
          var facedata = responseData.get('face_data').getData()[owner.getId()];
          var faceurl = responseData.get('face_url').getData()[owner.getId()];
          var to_output = owner.getDisplayName() + " is a ";
          
          if(facedata == null)
          {
            to_output += "lazy";
          }else{
            to_output += facedata.face_data;
          }
          to_output += " panda.";
          if(faceurl == null)
          {
            faceurl = "http://www.cs.drexel.edu/~asc38/Google/meh.png";
          }else{
            faceurl = faceurl.face_url;
          }
It is important to note how to retrieve the data from the response object. Not only do you have to call get() with the field name, and getData(), but you must also grab the data from the index of the owner's id, and then call the name of the variable off THAT. 

What this does is set default values for facedata and faceurl, and then combines them to make the entire gadget rendering. Now, if you look at my profile, either as me, or as someone else, you see the following:













Not terribly exciting, but you can see what it's trying to do. Now, to add a bit of user functionality. For this app, the only real interactivity is going to be when the owner changes their emotion or the panda's face. To do that, we first have to see if the person viewing the app is the owner. That's pretty intuitive:

if(owner.isViewer())
          {

See? Now, what I do is build an admin panel/form that has a text box for the user to input their emotion, and then radio buttons.

owner_output = '


New Panda Emotion?
';
            owner_output += '
New Panda Face?
';
            owner_output += '
';
            owner_output += '
';
            owner_output += '
';
            owner_output += '
';
            document.getElementById("gadgetdiv").innerHTML += owner_output;
          }

For the sake of brevity, I'm not going to detail both changing functions, but just the important bits of changeFace. You get the value of the new emotion, and then create an updateAppDataRequest and submit it. You still need to specify a callback though. This lets you do error checking to make sure that the request made it through alright.

var updaterequest = opensocial.newDataRequest();
 updaterequest.add(updaterequest.newUpdatePersonAppDataRequest("VIEWER", "face_data", newemotion), "update1");
updaterequest.send(finishUpdate);

For finishUpdate, I just have it mirror the initial data pull, and loop back to loadUI, thus completing a beautiful life cycle.

        function finishUpdate(responseData)
        {
          var ownerspec = opensocial.newIdSpec({ "userId" : "OWNER" , "groupId" : "SELF"});
          var datarequest = opensocial.newDataRequest();
          datarequest.add(datarequest.newFetchPersonAppDataRequest(ownerspec,"face_data"),"face_data");
          datarequest.add(datarequest.newFetchPersonAppDataRequest(ownerspec,"face_url"),"face_url");
          datarequest.send(loadUI);
        }

Obviously this example could be fleshed out a LOT more. By adding posting to the activity stream (which I may still do) and a much prettier, this app could be a lot more solid. All in all, it's not bad for a few hours of work. The full gadget xml is at:

Monday, September 8, 2008

Browser Inconsistencies: Part 2

Hey-o

Today was my first day at my new job. Exciting, but nothing notable yet. Hence, this is a relatively shorter entry.

Anyways, I was playing around with HTML and CSS again, putting together a chess/checkers-style board with HTML and CSS, and found another browser inconsistency.

I have a 4x4 grid of alternating white and black tiles. The tiles are arranged in rows, and there are tokens that are placed absolutely on top of them. The rows of tiles and tokens are all in a master div. The rows are each divs that have a clear: both property to make them cascade properly. At first, I didn't define the horizontal position of the tokens. In firefox/opera/chrome, you see the following:
















The absolutely positioned tokens are removed from where they "should" be, and are placed in the upper-left corner of their containing div (the white token has been defined to be that low). However, in IE8, this happens:















Apparently, if a horizontal position is not specified, the absolutely positioned elements stay where they would, even though they are removed from the box model. You can tell they have still been removed, because if they hadn't, then the border would be surrounding them as well. Setting the left: 0px; property makes IE8 render the same as all the others, but it is still interesting.