So... yeah. The title says it all. I'm frustrated and on the verge of giving up, so forgive me if my tone is off on this one.
Several weeks ago, I submitted my latest game, made in Cocos2D-x, to Apple for approval. I was rejected with the following message, citing Apple 10.6 (dealing with UI):
"Your app takes the user out to Safari when they opt-in to signing in with Google"
I explained the situation: I didn't build that part, it's build in to the GPGS code. All I call is [[GPPSignIn sharedInstance] authenticate]; . Google even says in the documentation, for iOS they try to use the Google Plus app if it's available, then try Safari. There's no way to do it in-app as provided by Google.
I was still rejected, and lost my appeal, even though I've seen and used several apps that use the same implementation. I've also seen the same style used for GREE logins (i.e., the official 1 vs 100 app). It's not an unknown thing, and it's not new. Yes, I'm aware that's not a reason to approve it. But as it stands Apple is (or "should be") rejecting ALL apps that use Google Play Games Services. Unless there's some way of signing in in without leaving the app that I simply can't get working. I'd think something that large would warrant a message and/or a fix from Apple or Google addressing the situation.
After doing a ton of digging and only coming up with a handful of people who've had similar issues (and no solutions), I decided that perhaps my issue was the reviewer and not the actual policy. Again, rejected, same thing: though it did reference my previous rejection, so if it's related to that I'm not sure.
Then, just this week Google announced an update to Google Play Game Services, and I was hopeful that there might be a solution in there. Especially after this post came out about a Cocos2D-X implementation being available. Unfortunately, using the code from the sample the relevant call GPGSManager::BeginUserInitiatedSignIn(); still opens up Safari.
So, I'm done. I've spent weeks researching and submitting and getting rejected in the name of using a unified system for achievements and leaderboards. If someone has advice or a tip, feel free to chime in. But at this point I'm probably giving up and implementing Game Center for iOS and keeping GPGS only for Android.
Saturday, July 12, 2014
Saturday, April 19, 2014
Integrating Google Play Game Services into Cocos2D-X
I’m making a few mobile games, and finally decided it was time to move to a cross-platform engine. One piece that seemed daunting, and didn’t have a TON of documentation, was integrating a Cocos2D-X game with the Google Play Game Services APIs. GPGS has some great features, like cloud saves, achievements, and leaderboards, and I wanted those.
This is by no means an “expert” opinion, and I fully acknowledge that I use hacky/subpar code in places. Feel free to disagree with my design decisions, etc. This is less a “this is how it’s done” and more of a “this worked for me” article. Coder beware, and all that.
I used Cocos2D-X 2.2.3, the current non-beta version. I have no idea what might be different if you use 3.x, or any other version.
I also borrowed some from the Avalon game center example (link below the post). I kept the “Game Center” name, but little of the code beyond the concept and some JNI helpers.
I didn’t do cloud saves for this one, only Achievements and Leaderboards. If I do the cloud saves later, I’ll post an update.
I initially started looking at the C++ library available for GPGS, and decided that was a mistake. While it certainly CAN be done that way, you lose some of the handier integrations with iOS and Android for things like launching the popups/activites for viewing achievements and leaderboards. The solution I used, at a high level, looks like this:
- A header file (GameCenter.h) that both Android and iOS see.
- A .mm file (GameCenter.mm) stored outside the main Classes folder structure, so I can make it so only Xcode can see it.
- A .cpp file (GameCenter.cpp) stored in the Classes folder structure, but only included in Eclipse.
...you can see where this is going, I hope. Both the .mm and the .cpp file implement the header contract. The .mm file points at the obj-C functionality, while the .cpp file uses JNI to pass code to GameCenter.java static methods.
While I know it’s complex, I’m going to skip over the initial setup of GPGS. Follow the guidelines Google publishes for adding GPGS to your iOS and Android projects. A couple tips when using Cocos2D-X:
- Grab the BaseGameActivity (and the related classes and helpers) from the Android sample. Set your main activity to inherit from it, and change BaseGameActivity to inherit from Cocos2dxActivity. I know it’s not a FragmentActivity... that doesn’t seem to cause issues.
- Use the RootViewController in Xcode. For me, the viewDidLoad stuff wouldn’t fire when launching a Cocos2D-X project, so I put my initial launch code in initWithNibName.
- I used a static reference to my Activity to launch the GPGS methods from my static java Game Center. Yes, I know this is leaky and bad.
Without any further ado, the code:
GameCenter.h
#ifndef __XXXXX__GameCenter__
#define __XXXXX__GameCenter__
class GameCenter
{
public:
void showAchievements();
void unlockAchievement(const char* achievementId);
void showLeaderboard(const char* leaderboardId);
void postToLeaderboard(const char* leaderboardId, int score);
bool isSignedIn();
void signIn();
};
#endif /* defined(__XXXXX__GameCenter__) */
GameCenter.mm
#include "GameCenter.h"
#include "RootViewController.h"
bool GameCenter::isSignedIn()
{
return [(RootViewController*)[[[UIApplication sharedApplication] keyWindow] rootViewController] isSignedIn];
}
void GameCenter::signIn()
{
[(RootViewController*)[[[UIApplication sharedApplication] keyWindow] rootViewController] startGoogleGamesSignIn];
}
void GameCenter::showAchievements()
{
[(RootViewController*)[[[UIApplication sharedApplication] keyWindow] rootViewController] showAchievements];
}
void GameCenter::unlockAchievement(const char* achievementId)
{
[(RootViewController*)[[[UIApplication sharedApplication] keyWindow] rootViewController] unlockAchievement: [NSString stringWithUTF8String:achievementId]];
}
void GameCenter::showLeaderboard(const char* leaderboardId)
{
[(RootViewController*)[[[UIApplication sharedApplication] keyWindow] rootViewController] showLeaderboard: [NSString stringWithUTF8String:leaderboardId]];
}
void GameCenter::postToLeaderboard(const char* leaderboardId, int score)
{
[(RootViewController*)[[[UIApplication sharedApplication] keyWindow] rootViewController] postToLeaderboard: [NSString stringWithUTF8String:leaderboardId] withScore:score];
}
GameCenter.cpp
RootViewController.mm (the relevant parts)
GameCenter.java (the toast doesn't work, I just hadn't pulled it yet)
That should be enough to get you going! Happy Coding!
Google Play Game Services
Cocos2D-X
Avalon (GitHub)
#include "GameCenter.h"
#include "cocos2d.h"
#include "platform/android/jni/JniHelper.h"
#include <jni.h>
namespace helper {
namespace gamecenter {
const char* const CLASS_NAME = "com/bloodhoundstudios/gridinfect/GameCenter";
void callStaticVoidMethod(const char* name)
{
cocos2d::JniMethodInfo t;
if (cocos2d::JniHelper::getStaticMethodInfo(t, CLASS_NAME, name, "()V")) {
t.env->CallStaticVoidMethod(t.classID, t.methodID);
t.env->DeleteLocalRef(t.classID);
}
}
bool callStaticBoolMethod(const char* name)
{
cocos2d::JniMethodInfo t;
if (cocos2d::JniHelper::getStaticMethodInfo(t, CLASS_NAME, name, "()Z")) {
bool result = (t.env->CallStaticBooleanMethod(t.classID, t.methodID) == JNI_TRUE);
t.env->DeleteLocalRef(t.classID);
return result;
} else {
return false;
}
}
void callStaticVoidMethodWithString(const char* name, const char* idName)
{
cocos2d::JniMethodInfo t;
if (cocos2d::JniHelper::getStaticMethodInfo(t, CLASS_NAME, name, "(Ljava/lang/String;)V")) {
jstring jIdName = t.env->NewStringUTF(idName);
t.env->CallStaticVoidMethod(t.classID, t.methodID, jIdName);
t.env->DeleteLocalRef(t.classID);
t.env->DeleteLocalRef(jIdName);
}
}
void callStaticVoidMethodWithStringAndInt(const char* name, const char* idName, const int score)
{
cocos2d::JniMethodInfo t;
if (cocos2d::JniHelper::getStaticMethodInfo(t, CLASS_NAME, name, "(Ljava/lang/String;I)V")) {
jstring jIdName = t.env->NewStringUTF(idName);
t.env->CallStaticVoidMethod(t.classID, t.methodID, jIdName, (jint)score);
t.env->DeleteLocalRef(t.classID);
t.env->DeleteLocalRef(jIdName);
}
}
} // namespace gamecenter
} // namespace helper
bool GameCenter::isSignedIn()
{
try
{
return helper::gamecenter::callStaticBoolMethod("isSignedIn");
}
catch (std::exception& e)
{
}
return false;
}
void GameCenter::signIn()
{
try
{
helper::gamecenter::callStaticVoidMethod("signIn");
}
catch (std::exception& e)
{
}
}
void GameCenter::showAchievements()
{
try
{
helper::gamecenter::callStaticVoidMethod("showAchievements");
}
catch (std::exception& e)
{
}
}
void GameCenter::unlockAchievement(const char* achievementId)
{
try
{
helper::gamecenter::callStaticVoidMethodWithString("unlockAchievement", achievementId);
}
catch (std::exception& e)
{
}
}
void GameCenter::showLeaderboard(const char* leaderboardId)
{
try
{
helper::gamecenter::callStaticVoidMethodWithString("showLeaderboard", leaderboardId);
}
catch (std::exception& e)
{
}
}
void GameCenter::postToLeaderboard(const char* leaderboardId, int score)
{
try
{
helper::gamecenter::callStaticVoidMethodWithStringAndInt("postToLeaderboard", leaderboardId, score);
}
catch (std::exception& e)
{
}
}
RootViewController.mm (the relevant parts)
#import "RootViewController.h"
#import <GooglePlayGames/GooglePlayGames.h>
@implementation RootViewController
static NSString * const kDeclinedGooglePreviously = @"UserDidDeclineGoogleSignIn";
static NSInteger const kErrorCodeFromUserDecliningSignIn = -1;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
// Custom initialization
GPPSignIn *signIn = [GPPSignIn sharedInstance];
signIn.clientID = kClientID;
signIn.scopes = [NSArray arrayWithObjects:
@"https://www.googleapis.com/auth/games",
@"https://www.googleapis.com/auth/appstate",
nil];
signIn.language = [[NSLocale preferredLanguages] objectAtIndex:0];
signIn.delegate = self;
signIn.shouldFetchGoogleUserID =YES;
self.currentlySigningIn = [[GPPSignIn sharedInstance] trySilentAuthentication];
}
return self;
}
(...)
-(void)finishedWithAuth:(GTMOAuth2Authentication *)auth error:(NSError *)error
{
self.currentlySigningIn = NO;
if (error.code == 0 && auth) {
NSLog(@"Success signing in to Google! Auth is %@", auth);
[self startGoogleGamesSignIn];
} else {
NSLog(@"Failed to log into Google\n\tError=%@\n\tAuthObj=%@", [error localizedDescription],
auth);
if ([error code] == kErrorCodeFromUserDecliningSignIn) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kDeclinedGooglePreviously];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
}
-(bool)isSignedIn
{
return self.currentlySigningIn || [[GPGManager sharedInstance] isSignedIn];
}
-(void)startGoogleGamesSignIn
{
if ([[GPGManager sharedInstance] isSignedIn]) {
[[GPGManager sharedInstance] signOut];
} else {
[[GPGManager sharedInstance] signIn:[GPPSignIn sharedInstance]
reauthorizeHandler:^(BOOL requiresKeychainWipe, NSError *error)
{
if (requiresKeychainWipe)
{
[[GPPSignIn sharedInstance] signOut];
}
[[GPPSignIn sharedInstance] authenticate];
}];
}
}
- (void)showAchievements {
GPGAchievementController *achController = [[GPGAchievementController alloc] init];
achController.achievementDelegate = self;
[self presentViewController:achController animated:YES completion:nil];
}
- (void)achievementViewControllerDidFinish:(GPGAchievementController *)viewController {
[self dismissModalViewControllerAnimated:YES];
}
- (void)unlockAchievement:(NSString*)achievementId {
GPGAchievement *unlockMe = [GPGAchievement achievementWithId:achievementId];
[unlockMe unlockAchievementWithCompletionHandler:^(BOOL newlyUnlocked, NSError *error)
{
if (error)
{
}
else if (!newlyUnlocked)
{
}
else
{
NSLog(@"Hooray! Achievement unlocked!");
}
}];
}
- (void)showLeaderboard:(NSString*)leaderboardId {
GPGLeaderboardController *leadController = [[GPGLeaderboardController alloc] initWithLeaderboardId:leaderboardId];
leadController.leaderboardDelegate = self;
[self presentViewController:leadController animated:YES completion:nil];
}
- (void)leaderboardViewControllerDidFinish:(GPGLeaderboardController *)viewController {
[self dismissModalViewControllerAnimated:YES];
}
- (void)postToLeaderboard:(NSString*)leaderboardId withScore:(int)score {
GPGScore *myScore = [[GPGScore alloc] initWithLeaderboardId:leaderboardId];
myScore.value = score;
[myScore submitScoreWithCompletionHandler: ^(GPGScoreReport *report, NSError *error)
{
if (error)
{
}
else
{
}
}];
}
@end
GameCenter.java (the toast doesn't work, I just hadn't pulled it yet)
import android.widget.Toast;
import com.google.android.gms.games.Games;
public class GameCenter {
static void signIn() {
try {
if (XXXXX.Instance != null) {
if (!XXXXX.Instance.isSignedIn()) {
XXXXX.Instance.beginUserInitiatedSignIn();
} else {
XXXXX.Instance.signOut();
Toast.makeText(XXXXX.Instance, "Signed out", Toast.LENGTH_SHORT).show();
}
}
} catch (Exception ex) {
}
}
static void showAchievements() {
try {
if (XXXXX.Instance != null) {
if (XXXXX.Instance.isSignedIn()) {
XXXXX.Instance.startActivityForResult(
Games.Achievements.getAchievementsIntent(
XXXXX.Instance.getApiClient()),
41267);
}
}
} catch (Exception ex) {
}
}
static void unlockAchievement(String achievementId) {
try {
if (XXXXX.Instance != null) {
if (XXXXX.Instance.isSignedIn()) {
Games.Achievements.unlock(XXXXX.Instance.getApiClient(), achievementId);
}
}
} catch (Exception ex) {
}
}
static void showLeaderboard(String leaderboardId) {
try {
if (XXXXX.Instance != null) {
if (XXXXX.Instance.isSignedIn()) {
XXXXX.Instance.startActivityForResult(
Games.Leaderboards.getLeaderboardIntent(
XXXXX.Instance.getApiClient(), leaderboardId),
76789);
}
}
} catch (Exception ex) {
}
}
static void postToLeaderboard(String leaderboardId, int score) {
try {
if (XXXXX.Instance != null) {
if (XXXXX.Instance.isSignedIn()) {
Games.Leaderboards.submitScore(XXXXX.Instance.getApiClient(), leaderboardId, score);
}
}
} catch (Exception ex) {
}
}
}
That should be enough to get you going! Happy Coding!
Google Play Game Services
Cocos2D-X
Avalon (GitHub)
Sunday, March 30, 2014
Cocos2D-X Android UnsatisfiedLinkError: Couldn't load cocos2dcpp
So this stumped me for several hours, and in an effort to help others who might have the same issue, I'm posting this here. Hopefully someone finds this in their frantic Google searching.
I always use the Intel emulators, because they are much faster (especially with HAXM installed!). When developing with the Android NDK and Cocos2D-X, however, that's apparently an issue.
So, if you're stuck with that linker error, try making an emulator using ARM and see if that works. Solved it for me.
Happy coding!
I always use the Intel emulators, because they are much faster (especially with HAXM installed!). When developing with the Android NDK and Cocos2D-X, however, that's apparently an issue.
So, if you're stuck with that linker error, try making an emulator using ARM and see if that works. Solved it for me.
Happy coding!
Thursday, August 29, 2013
Quick Notes on Android RSS
This is a quick and dirty post for a very small user base, but I figured it's been a while so I thought I'd throw up something new.
I am building a podcast app in android, and I'm using Android Annotations (AA) for the feed reading. AA uses Spring Android for the rest (de)serialization, which uses the Android ROME Feed Reader for the various rss formats, which is what I need for my podcasts. Everyone follow that chain of dependencies? Good. Don't forget JDom at the end or ROME blows up. ;-)
So AA allows me to set the converter for reading the xml like so:
@Rest(rootUrl = "", converters = { SyndFeedHttpMessageConverter.class, })The problem here is SyndFeedHttpMessageConverter only supports application/atom+xml and application/rss+xml out of the box. And anyone who's ever tried to consume web code knows that everyone and their mother has a content type to use because the format is pretty open. So you wind up with rss or atom sites with contentTypes of application/xml or text/xml, which will fail.
AA doesn't let you set the accepted media types, however, at least not as of version 2.7.1 - so you are forced to leave them behind, which is what I did here. All you have to do is copy out the AA generated code and make your changes, but it's not easy to figure out that that's an actual shortcoming of AA, hence this post. Here's my new shiny code, with the changes in bold:
Hopefully this post saves someone else doing REST with AA some time: while my example is focused on podcasts, it's definitely helpful to anyone looking to add some additional supported media types to their web calls.public class PodcastReaderProxy2{private RestTemplate restTemplate;private String rootUrl;public PodcastReaderProxy2() {restTemplate = new RestTemplate();SyndFeedHttpMessageConverter converter = new SyndFeedHttpMessageConverter();List<MediaType> supportedTypes = new ArrayList<MediaType>();supportedTypes.addAll(converter.getSupportedMediaTypes());supportedTypes.add(MediaType.APPLICATION_XML);supportedTypes.add(MediaType.TEXT_XML);converter.setSupportedMediaTypes(supportedTypes);restTemplate.getMessageConverters().add(converter);rootUrl = "";}public void setRootUrl(String rootUrl) {this.rootUrl = rootUrl;}public SyndFeed getFeed() {HttpHeaders httpHeaders = new HttpHeaders();HttpEntity<Object> requestEntity = new HttpEntity<Object>(httpHeaders);return restTemplate.exchange(rootUrl.concat(""), HttpMethod.GET, requestEntity, SyndFeed.class).getBody();}}
Some Links:
https://github.com/excilys/androidannotations/wiki/Rest-API#rest
http://www.springsource.org/spring-android
http://static.springsource.org/spring-android/docs/1.0.x/reference/htmlsingle/
Happy Coding!
Thursday, April 11, 2013
Extending Dobjanschi - An Attempt at an Android REST Client Pattern
I've been working on several mobile apps, most of them needing some way to communicate with services of the REST variety. This is an incredibly useful talk from Google I/O 2010 by Virgil Dobjanschi - if you make RESTful clients and haven't seen it, I recommend you find an hour to watch it and then find considerably longer to digest it.
Google I/O 2010 video and presentation materials here.
The pattern that really jumped at me was B: the idea of sending everything through the Content Provider. So I started making a 'simple' POC that quickly grew out of control. Without going into the development details TOO much at this point, I was stuck on two points:
- I didn't like the idea that my CRUD calls to the Content Provider were either hitting a service, or hitting a local database. I wanted to distinguish them.
- When Dobjanschi made his talk, Loaders didn't exist yet.
In the end, I created a pattern that was something of an amalgam of A and B: I've attempted to draw it here.
To explain in words:
- Your activity/fragment inits a Cursor Loader and assigns something (probably itself) as a callback.
- Your activity/fragment creates an adapter - probably ideally something that extends CursorAdapter - and assigns it to your listviews, etc.
- Any time your activity/fragment needs to DO something (even w/o user interaction, like making a refresh request on load) it calls into your Content Manager.
- Content Manager crafts your intent and fires it off to your Service Helper.
- Service Helper, a singleton, tracks existing calls. If your call is a 'new' one not already running, it fires it off to the Service - which uses Intent Filters.
- The Service (which maybe or maybe not has a queue of requests and processes them one at a time) makes your REST params, spins up a new thread, and passes them to the REST Method.
- The REST Method handles the HTTP request/response calls, and sends the raw data to the Processor.
- The Processor munges response data into some Content Provider-centric logic (either DTOs, or if you prefer straight to ContentValues) and makes the CRUD calls into the CP to make your database reflect the results.
- After finishing, the Processor notifies the Content Resolver of new data. This can also be done pre- or post- HTTP request/response, if you want to show the loading process to the user. (In which case, the REST Method will need to call the Processor during each step.)
- Because you already registered your UI components, the rest is 'magic': your adapter will find the new data and refresh the UI automatically.
This assumes a SQLite database behind your Content Provider - I put another layer between my CP and my DB and called it "__DataSource" - my CP handles the URI routing to different DataSource methods while DataSource handles the actual heavy lifting (making queries, parsing ContentValues, etc). I've also found it handy for my DTOs to have 'fromCursor' methods on them - makes it very easy for my adapter to do something like "view.bind(Data.fromCursor(cursor));" in bindView.
Hopefully this gives some good direction on how I've gone about implementing a responsive client. I feel the pattern is good enough that it works beyond REST - any long-running async calls would likewise benefit from something like this, and it keeps you from having to constantly know WHEN to update the front end. You just bind it, and walk away: whenever the data changes, you sound off to the Content Resolver and any active pieces that care come running.
Up next (soon I hope) I'll discuss using an in-memory-only "database" for your Content Provider repo.
Happy coding!
Wednesday, October 3, 2012
Wanted: 2D artist/animator for mobile game(s)
EDIT: I've selected an artist, thanks to all the applicants!
I am looking for an artist and animator to work with me on my current project, a mobile app game for Android (and eventually iPhone). The project is basically feature complete, I have been working on it for several months now, and what is left is polishing and adding a few animations I haven't made placeholders for. The art right now is all just temp stuff that I made myself using Paint and Gimp.The current company name is Sevomasean Productions, the same name I used when making flash games in college (and is, as such, open to change since it's a pretty terrible name). Right now I have no real web presence, as it's just me and the 10-15 hours a week I find time to contribute to the project. Once this project has real art and is closer to release, I plan to set up a site (possibly under a new name) and start hyping up the project more. I'm aiming for a holiday release, as my current projects have me done with my end around Halloween (then add testing, extra balancing, etc).
For my resume of past completed games, my Newgrounds site is http://arclite83.newgrounds.com. Please note that Newgrounds cleared out most of the images associated with my account through some cleanup they ran about a year ago, so the only pieces that still have icon art, etc, are the ones that are still linked in featured categories (a defense game, and a valentines day holiday game). The short of it is I made flash games in college for beer money. Now that I'm a professional programmer I'm applying my work skills to making a few apps in my spare time, this being the first of hopefully many.
The Game:
Run an alchemist's potion shop: fill orders, unlock upgrades, become rich and popular, and pay your wizard landlord to keep him from turning you into a frog!Work fast to assemble customer's orders from a recipe list that starts simple and grows as more ingredients and formulas are unlocked. The simple color-based recipes are easy and intuitive, while the extra ingredients add complexity and keep the game interesting. Fill orders quickly and accurately to earn popularity, giving you more customers and better tips. As you progress, your evil wizard landlord will throw obstacles in your path, such as preventing you from restocking an ingredient or making rounds shorter, to try and ensure your failure. Earn money, restock your inventory, and buy permanent or temporary upgrades from the market that allow you to make even larger profits. Set aside rent money before it's due, or work extra hard right before the wizard shows up to collect; just make sure you have all the money when the time comes, or it's game over!
Easy mode is designed for kids or ultra-casual players, and features no rent and a small credit each round to ensure the fun never ends. Normal is a balanced game, with fair rent and a reasonable number of obstacles to make the game challenging but still relaxing. Hard mode has a much steeper rent curve, and the wizard will repeatedly harass you to try and trip you up. Once you earn enough you can purchase the deed to the potion shop, and be rid of the wizard forever! Afterwards, you can continue to play your character and see how much money you can accrue, or start again on a different difficulty setting.
Amount of work:
This is all approximate based on what I have now and my best guesses for the finished product.6 background screens
10-12 button animations
~30 icons for inventory items, etc
app launcher icons
5-6 static section backgrounds (popup notices, money, etc)
11 cut scenes (intro and game over scenes, plus 9 short 'announcement' scenes when events happen)
player animations, 6-7 distinct actions
customer animations, 2-3 actions
Compensation:
This is a tough one for me to settle on what is fair (upfront payments vs percentage of profit, etc), and I'm open to talking about it more with the right candidate. Like I said, right now the team is just me, and I'm looking for someone interested in possibly forging a longer relationship for other game app ideas I have. This is not intended to be some big life changing money maker, though I do believe I have something that could do well if it's given enough attention to detail and polish. Also, I don't have a lot of capital for this, it's just a spare time thing right now. I'm much more open to a profit percentage than shelling out buckets upfront. If that's not something you would be comfortable with, I completely understand: people deserve to get paid for their work, and this position will likely NEVER be something you can feed yourself on solely. I certainly don't expect to. The plan is put it on Android, use whatever profits come from that to port it to Apple, and then move on to the next app. If it pays for new laptop, I'll call it a big win.Job Requirements:
I'm really looking for someone who can draw/animate, be it by hand or computer. I'm looking for a childish, whimsical feel, like a Saturday morning cartoon, or a Disney short, or Bitey of Brackenwood, etc. This is meant to be a game for a generally younger audience, so going for a lighter feel is a big priority for me. I'm especially interested in simple animation samples if you've got them.Please send inquiries, resumes, and work samples to <REMOVED>. Show me yours and I'll show you mine - quality work samples from interested parties will get a link to the latest beta APK file to load on their Android devices and see what you would be working on.
I look forward to hearing from you!
Friday, September 14, 2012
Clarity over Cleverness: Follow Up, and a Practical Example
I had a nice discussion with a coworker about my last post, and wanted to add some clarity: specifically, the topic was about a piece of unit test code that looked something like this:
public class UnitTestHere()
{
...
Db.CreateThing();
...
}
public static int CreateThing(this IDbCommand command,
Action<Thing> modifier = null)
{
var thing = new Thing
{
...
};
if (modifier != null)
{
modifier(thing);
}
command.Insert(thing);
return (int) command.GetLastInsertId();
}
So that, to modify the default values for the 'Thing' in the unit test, users had to do this:
public class UnitTestHere()
{
...
Db.CreateThing(t =>
t.someField = someNonDefaultValue);
...
}
My argument was along the lines of 'I don't want to have to do this to write my unit test'.
To which I got the reply (very correctly, I might add) that if I don't know it, then I should use it as a learning opportunity. Which is very true. In my case, the argument was less that I didn't know it and more that I felt the delegate was fancy-clever-overkill.
In this case, the code is both clever and clear - it serves a valid purpose, allows people reading the unit tests to see very obviously what values are being set IN the test, and it's dynamic enough to handle changes to any and all value changes in the inserted 'Thing' that might come up in future tests (like the one I was writing that brought about this discussion, for example). The hurdle of writing it is pretty quickly solved, and once the code is there it is clear enough to be maintained / changed with little effort.
I never meant to imply that more advanced tools don't have a place, just that sometimes people use them when they may not be better than the more basic options available to them.
And with that, I'm dropping this one. Happy Coding!
Tuesday, September 11, 2012
Coding at a Gradeschool Level
There are plenty of articles available for advice on writing clean and clear code. Many of the general principles are easy to grasp: short methods, straightforward logic and flow, clear and unambiguous variable names, consistent terminology usage, etc.
All of these
things make your code easier to read, to understand, and ultimately to maintain
and change. A good programmer knows that writing something clever and cool
doesn't necessarily mean it was written the best way, even if it makes the code
smaller. There are certainly limits to this (no one would argue much if you
turns a 200 line class into 2 lines, even if those 2 lines weren't the clearest
in the world, for example) but the point stands. There are 1000 ways to write
code for a given feature, and there is no one true 'right' way for anything.
Which brings me to
my point: a lot of times, writing something 'clever' may not be as good in the
long run as writing something 'simple'. That abstract class filled with generic
methods and callback delegates may get the job done and then some, may allow
you to easily extend it and do anything you might ever have to do, but it's
probably not easy to pick up and understand at a glance. We can set aside the
benefits/risks of violating YAGNI in my example; it's worth addressing, but
that's not my focus here.
In many cases, I
try to keep my code as simple as I can. The analogy I try to about is a
newspaper; in order to reach and be read and understood by the masses, most
papers will dumb down their verbiage in order to be better understood.
Similarly, I try and write my code so that a majority of it can be picked up
and read by entry level programmers.
That's not to say
don't used advanced programming techniques, but I know a lot of people who will
use them because they are 'neat' or 'clever' or any other word that describes
using it because it's interesting and not necessarily because it is the right
tool for the job. There is nothing wrong with an if-else, a switch, or a
foreach loop, and basic overloading or overriding of methods can get you very
far. The keys to clear code lie in intelligent naming and intuitive methods.
Few people will
look at this and not get the basic idea of what you are trying to do:
public void OnClick(int id)
{
var ball = GetClickedBallById(id);
ball.Roll(Units.Yards, 10, Direction.East);
}
private Ball GetClickedBallById(int id)
{
foreach(var ball in ballList)
{
if (ball.Id == id)
return ball;
}
return null;
}
But some, especially new programmers or those unfamiliar with the code, might scratch heads at something like this:
public void Clicker(int i)
{
Get<int>(i).Execute(2, 10, 1);
}
private BaseClass Get<T>(T i)
{
return objList
.Where(o => o.Id == i)
.FirstOrDefault();
}
Now, I'm not
saying I hate generics or Linq, because actually I don't, and they definitely
have a place and can be very intuitive. This is just an example, and an
observation that more people will quickly understand a foreach loop with a
couple of if statements than a possibly complex where clause in a Linq lambda.
I know in reviewing my own code after the fact, sometimes what seemed like a
really helpful and clever replacement would come back and bite me when I needed
to tweak some minor functionality in that loop that I hadn't considered
originally.
Every developer
should ask themselves some questions in the above example: does killing those
4-5 lines make the code harder to read? Is there any way I can fix that? Is
there a middle ground between clarity and concise code? How much do I expect my
programmers to understand when they pick up the code? What about the BAs, or
managers?
Don't be afraid to
dumb down in the interest of clarify. There's nothing stopping you from
refactoring it later into some super-generic-awesomeness if you REALLY feel you
need it, and that it actually helps your code.
Happy coding!
Thursday, September 6, 2012
Using SQL xQuery to Select Multiple Node Values
A recent task involved us having to grab information out of an rdl whose dataset was saved as xml in a sql table (Yes, you can shudder at that statement, it's ok, I know I did). This was my first foray into xQuery in a sproc, so it was a pretty interesting experience.
There was existing code for dealing with a single value (handily, as a 'value' command), but not much on selecting multiple rows. What we wound up doing was a select from the table using a cross apply to grab the nodes we cared about, then performing a value on the returned data. Like this:
SELECT temp.dataStuff.value('.','varchar(50)')
FROM XmlTable xt
CROSS APPLY xt.xml.nodes('(/root/nodes/data)') as temp(dataStuff)
If you do a select, you will see the nodes we return are untyped sql; the value call just says 'grab the nodes we returned and shove them into varchars'.
So, with an XmlTable entry with an xml column of this:
<root>
<nodes>
<data>ABC</data>
</nodes>
<nodes>
<data>DEF</data>
</nodes>
<nodes>
<data>GHI</data>
</nodes>
</root>
You would return this out of SQL:
ABC
DEF
GHI
Another helpful source for this is here: http://stackoverflow.com/questions/1393250/getting-multiple-records-from-xml-column-with-value-in-sql-server
In the linked example the cross apply wasn't necessary because they started with an xml string. Since we had to join in and grab multiples from different rows, we needed it here.
BizTalk Scripting Functiods with Same Name Methods
So a coworker had this happen to him today, and I thought it was interesting enough to share.
Create a BizTalk map with two different C# scripting functoids, both with the same method name and input parameters, like this:
System.String GetMe(System.String input)
{
return System.String.Format("Test 1: {0}", input);
}
System.String GetMe(System.String input)
{
return System.String.Format("Test 2: {0}", input);
}
Now link them to and from some placeholder xml schemas. I won't bother detailing the boiler-plate code for that here, if you know BizTalk I'm assuming you can link two schemas using a map.
You won't get an error, but the map won't function as expected: VS/BizTalk never catches the fact that there is an invalid overloaded method in the code attached to the map. What will happen is BizTalk will execute the first valid method it finds (in this case, 'Test 1' will probably appear in both outputs).
Hopefully this will help some figure out the cause of their strange map behavior!
Friday, August 31, 2012
Eat That Frog: A Helpful Philosophy
I've been following this philosophy for about a week now, both at my job and working on my Android game on the side. It really does help! I feel like I'm getting a lot more done, and the 'frog' doesn't taste as bad going down as I think it will looking at it. There is a lot to be said for just rolling up your sleeves and diving in to a problem.
Check it out!
Feeling pretty accomplished at the moment. I would love to hear someone else's opinion on it! Does it work for you, or if not, what does?
Check it out!
Feeling pretty accomplished at the moment. I would love to hear someone else's opinion on it! Does it work for you, or if not, what does?
Tuesday, August 28, 2012
Android Apps and Image Memory Management
Recently I discovered an interesting (and somewhat counter-intuitive) feature of drawing drawables as bitmaps in an Android game.
The intent: Produce a refreshed canvas at something around 30 to 50 FPS, using about a dozen images.
I had already made several of the images into sprites with source rectangles to handle grabbing the different frames: if you want to know more about what I'm referring to, Google 'drawing animated sprites', and do some digging into the concepts of source rectangles vs destination rectangles.
The responsible programmer in me approached this problem with the idea that I was going to load all my images, draw them, and then recycle and clear them. That would allow the garbage collector to come along and efficiently manage my memory.
After dealing with the surprising limitations of bitmap sizes in the Android VM (a simple 1024x768 background image caused an OutOfMemory exception initially), I was still not drawing nearly as fast as I wanted to.
For reference, one way to load larger images is to use temp storage when loading the drawables into bitmaps, like this 16k buffer I typically use:
Options options = new Options();
options.inTempStorage = new byte[16*1024];
_image = BitmapFactory.decodeResource(_context.getResources(),
resourcePointer, options);
The goal was 30 to 50 times a second, the initial reality was about 1 frame every 30 seconds. What I was seeing was that the GC was happily gobbling my memory... and taking about 100ms for every image cleanup. Adding that to the load times from the BitmapFactory, and multiply by 12, and there you go.
The solution? At first, it was 'blindly frontload the images'. That way I could avoid the load/dispose delays, but it kept a lot of data around in memory that I wasn't thrilled with. I had to reconcile my need to clean up with the reality that if I needed it again in 20ms, I probably wanted to keep it around.
Eventually I wrote a piece of dynamic code that iterated over all my image processing classes and said 'load or unload based on game state' and returned a boolean telling me to run GC when I was done with the processors. That code ran every frame draw, but only caused delays when changing game state, which I had written such that it happened asychronously when I finished fading out the previous screen but before fading in the new one.
private void Redraw(Canvas canvas)
{
LoadUnloadImages();
switch (_game.State)
{
case X:
RedrawX(canvas);
break;
(...)
}
}
private void LoadUnloadImages()
{
Boolean runGC = false;
runGC |= xProcessor.LoadUnloadImages(_game.State);
(...)
if (runGC)
System.gc();
}
xProcessor.LoadUnloadImages(_game.State) has another switch in it that says to Load or Unload based on state, and the loads/unloads only happen if the image is actually there to unload (or not there to load) so it doesn't waste time on pointless executions. GC only comes back on successful unloads, since the only time I need to explicitly clean up is after I recycle the bitmaps.
That way, each state has only the images it needs, and kills them as soon as the state changes (typically a screen change: menu, paused, main game screen, cutscenes, etc). The combination of all these responsible memory changes have allowed me to easily achieve the 50FPS goal I set out for at the start.
Another thing I should note: this is the only place in the entire app I call System.gc(), because that command WILL slow down your execution if you call it too much / inappropriately. Let the gc work on it's own if you can afford to, but you might still want to think about recycling your unused bitmaps and nulling them out.
Hope this helps all you Android devs out there with similar image delays!
Friday, August 3, 2012
Calling an Asmx Service using PowerShell
So a recent problem we had at work was calling a one-way asmx service on some kind of repeatable schedule; no security concerns to speak of, it was all internal-facing. So it became my job to write something as small and light as possible.
My first approach involved jumping into what I will call our 'existing' ticketing system. The popular analogy we were using was 'using an ICBM to kill a mosquito'. It involved a windows scheduler waking up an executable to drop a message onto an MSMQ, that was picked up by a BizTalk service, and was routed to some command processor. In this case, that would have been an HTML send port in same said BizTalk world... not simple, not quick, and pretty much vomit-inducing complexity for what should be REALLY dumb and simple.
My next approach, somewhat better: a quick console app, pulling the configurable values (server, command parameters) from the app.config. It looked something like this:
static void Main()
{
try
{
var username = ConfigurationManager.AppSettings["username"];
var password = ConfigurationManager.AppSettings["password"];
var client = new XXXSoapClient();
client.ProcessXXX(username, password);
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.ReadLine();
}
}
Better, and certainly usable in our current world. Our Enterprise Architect made a great suggestion to me though; check out PowerShell, see if there is some really light way to just 'get' the proxy and make the call. Turns out, there was:
$page = New-WebServiceProxy -Uri 'http://server/InternalServices/XXX.asmx'
$page.ProcessXXX('USER','PASS');
And there you have it. Two PowerShell lines to call the server, get a proxy, and make the call.
My first approach involved jumping into what I will call our 'existing' ticketing system. The popular analogy we were using was 'using an ICBM to kill a mosquito'. It involved a windows scheduler waking up an executable to drop a message onto an MSMQ, that was picked up by a BizTalk service, and was routed to some command processor. In this case, that would have been an HTML send port in same said BizTalk world... not simple, not quick, and pretty much vomit-inducing complexity for what should be REALLY dumb and simple.
My next approach, somewhat better: a quick console app, pulling the configurable values (server, command parameters) from the app.config. It looked something like this:
static void Main()
{
try
{
var username = ConfigurationManager.AppSettings["username"];
var password = ConfigurationManager.AppSettings["password"];
var client = new XXXSoapClient();
client.ProcessXXX(username, password);
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.ReadLine();
}
}
Better, and certainly usable in our current world. Our Enterprise Architect made a great suggestion to me though; check out PowerShell, see if there is some really light way to just 'get' the proxy and make the call. Turns out, there was:
$page = New-WebServiceProxy -Uri 'http://server/InternalServices/XXX.asmx'
$page.ProcessXXX('USER','PASS');
And there you have it. Two PowerShell lines to call the server, get a proxy, and make the call.
Thursday, January 26, 2012
Logging In to Google: A Commentary on OAuth
Maybe it's just the fact that I'm going about it wrong, and that it's really not meant for C#, but my general opinion of interfacing with Google APIs is that it's like pulling teeth, at least when it comes to DotNet.
First, make sure you find the right pages, and know -exactly- what you want to do and how you want to do it, because there are about 50 different ways to log in and use Google tools, and all of them are slightly different. Then make a project, and get your special top secret keys: something that I agree with on the surface, but why do I need a key for a console app and a web app? And while straight up mobile apps count as console apps, what does a wcf server count as? Because in the end, I want to authenticate on the client, but authorize on the server (to do things like pull your friends list, etc). Add to that the fact the tutorials are cryptic and complex, and it's really just a complicated, painful experience.
In the end, I'm still not fully set up with Google. I also want to interface with Facebook: hopefully that's a little simpler than this has been.
First, make sure you find the right pages, and know -exactly- what you want to do and how you want to do it, because there are about 50 different ways to log in and use Google tools, and all of them are slightly different. Then make a project, and get your special top secret keys: something that I agree with on the surface, but why do I need a key for a console app and a web app? And while straight up mobile apps count as console apps, what does a wcf server count as? Because in the end, I want to authenticate on the client, but authorize on the server (to do things like pull your friends list, etc). Add to that the fact the tutorials are cryptic and complex, and it's really just a complicated, painful experience.
In the end, I'm still not fully set up with Google. I also want to interface with Facebook: hopefully that's a little simpler than this has been.
Monday, December 5, 2011
Pathfinding Options
So this will probably be my most 'half-baked' article so far, mainly because it's on a topic I haven't fully fleshed out. At some point I may come back and add my conclusions, but for now, I am typing about the journey as I am taking it, so sorry if I steer people down the wrong path.
I am working on a side project that involves moving pieces around a rather complex game board. I am in serious need of path finding, which happens be a topic we didn't really cover in my college courses (among them a course specifically on game design, which to me feels like a pretty big miss. Then again, I helped write the course structure, so I share some of the blame for that.)
So far, my search has turned up a number of algorithms, all deigned to handle different levels of complexity. It does, however, give me (and you) a great place to state, so I will just post all the links so far and allow you to research yourself.
A*
Wiki
Tutorial (this is where I am starting, but it may not be enough to handle 'teleporting' squares)
I am working on a side project that involves moving pieces around a rather complex game board. I am in serious need of path finding, which happens be a topic we didn't really cover in my college courses (among them a course specifically on game design, which to me feels like a pretty big miss. Then again, I helped write the course structure, so I share some of the blame for that.)
So far, my search has turned up a number of algorithms, all deigned to handle different levels of complexity. It does, however, give me (and you) a great place to state, so I will just post all the links so far and allow you to research yourself.
A*
Wiki
Tutorial (this is where I am starting, but it may not be enough to handle 'teleporting' squares)
Friday, October 28, 2011
Wolf Run Green: My List. Thoughts?
Here is the new list I am looking to put together for the local circuit:
- Main Deck:
- 17 Forest
- 4 Rampant Growth
- 4 Garruk, Primal Hunter
- 4 Primeval Titan
- 4 Birds of Paradise
- 4 Dungrove Elder
- 3 Green Sun's Zenith
- 3 Solemn Simulacrum
- 3 Beast Within
- 2 Inkmoth Nexus
- 2 Llanowar Elves
- 2 Kessig Wolf Run
- 2 Mountain
- 1 Batterskull
- 1 Wurmcoil Engine
- 1 Acidic Slime
- 1 Copperline Gorge
- 1 Rootbound Crag
- 1 Slagstorm
- Sideboard:
- 3 Thrun, the Last Troll
- 2 Sword of Feast and Famine
- 2 Blasphemous Act
- 2 Ancient Grudge
- 1 Tree of Redemption
- 1 Arc Trail
- 1 Viridian Corrupter
- 1 Ghost Quarter
- 1 Gut Shot
- 1 Acidic Slime
I welcome opinions on the numbers, especially the sideboard! I was torn on the Slime, or if i need something like the fourth Beast Within. Also maindeck, I'm torn on Copperline versus Rootbound: at the end, either tutors off a Titan for the red next turn (provided you aren't getting Inkmoth, etc), but still.
17th at Connecticut 2011s with Red Deck!
So I was the highest placed 'burninator' in States this year. I knocked out 2 other RDW decks on the way to the top, the key being 4 Volshok Refugees and 8 ways to kill their copies of the red dread. I was 5-1 going in to the last round, and we had exactly 128 players... so we had 16 people in contention for Top 8. I lost to the man who eventually won the event. He ran W Humans, games 1 and 2 he went 'turn 1 Champion, turn 2 human /human/swing 3'... Game 1 involved a Angelic I couldn't answer, game 2 a Hero I also couldn't answer, and that was all she wrote.
Since then I've been tweaking the deck, and after running the numbers, I've come up with a list that I am pretty happy with. My biggest divergence from this is my lack of Grim Lavamancer: I would just rather have Spikeshot Elder, I feel Grim just sits around without enough graveyard cards to be effective half the time.
Here is the list I recommend: it is 5 cards off from what I ran at States. In light of recent events I'm planning to move from RDW to Primetime (Primieval Titan)... anything not blue makes me happy. :-)
Since then I've been tweaking the deck, and after running the numbers, I've come up with a list that I am pretty happy with. My biggest divergence from this is my lack of Grim Lavamancer: I would just rather have Spikeshot Elder, I feel Grim just sits around without enough graveyard cards to be effective half the time.
Here is the list I recommend: it is 5 cards off from what I ran at States. In light of recent events I'm planning to move from RDW to Primetime (Primieval Titan)... anything not blue makes me happy. :-)
- Main Deck
- 18 Mountain
- 4 Stromkirk Noble
- 4 Stormblood Berserker
- 4 Shrine of Burning Rage
- 4 Incinerate
- 3 Arc Trail
- 3 Brimstone Volley
- 3 Koth of the Hammer
- 3 Chandra's Phoenix
- 2 Hero of Oxid Ridge
- 3 Rootbound Crag
- 2 Spikeshot Elder
- 2 Grim Lavamancer
- 2 Volt Charge
- 1 Geistflame
- 1 Goblin Arsonist
- 1 Gut Shot
- Sideboard
- 4 Volshok Refugee
- 3 Ancient Grudge
- 1 Traitorous Blood
- 1 Arc Trail
- 1 Hero of Oxid Ridge
- 1 Perilous Myr
- 1 Manic Vandal
- 1 Manabarbs
- 2 Dismember
Good luck slinging spells!
XmlSerializer VS DataContractSerializer
So I am doing a project on the side involving a RESTful WCF Service involving many different types of clients. Recently I was stumped by several issues with the XmlSerializer and the attributes on my data classes: the main issue originated from using WCF to serialize the responses, but not using the DataContractSerializer to deserialize the responses. (Which was clearly a mistake from the start... the lesson here is don't mix and match technologies if you have the tools available native!)
Some fun facts if you find yourself in this predicament (or really, REALLY, want to use XmlSerializer):
The simple solution (provided you find this, or some similar article) is to use the serializer intended for WCF, and to stick with the DataContract/DataMember structure of more classic WCF service classes. So long as you specify names and namespaces, and don't care about fine-grained serialization control, everything should work great.
If you look around, you will find lots of recommendations to use 'ResponseFormat = WebMessageFormat.Xml' on your contracts. If you can avoid it, do so - WCF 4 lets clients specify in the request header their return types (an issue I discussed a little in a previous post), and IMO that is a far better way to go, especially if you have any plans of interfacing with clients more at home with JSON (i.e., Android devices).
Happy coding!
References:
http://www.danrigsby.com/blog/index.php/2008/03/07/xmlserializer-vs-datacontractserializer-serialization-in-wcf/
http://social.msdn.microsoft.com/Forums/en-AU/csharpgeneral/thread/dfd587ec-a269-49a9-a1a5-0f69d915c776
Some fun facts if you find yourself in this predicament (or really, REALLY, want to use XmlSerializer):
- XmlSerializer will not recognize lists when the complex objects in those lists exist in a different namespace than the list itself, even if you specify this fact on the XmlElement for said list. The same goes for arrays. And give up on loosely types interfaces like 'IEnumerable': the serializer chokes on these.
- You will be stuck with the yucky 'http://schemas.microsoft.com/2003/10/Serialization/+class namespace' namespace in your xml. This can become VERY inconvenient to your class structure, especially when paired with the first issue.
- The XmlSerializer will fail if it finds a root node it doesn't understand, but will simply null any inner nodes: make sure to check your entire object, a single element in a complex class can be null without the serializer giving you any warning.
- Off Topic: One issue I have dealt with a little in other projects, but not for this, is the complete lack of support for serializing multidimensional arrays. One way around it is to turn your array into a single array of complex objects with more in it than just the next 'dimension'. Even a dummy 'int' in the class will work, because it prevents the serializer from trying to 'simplify' the code into a structure it can't handle. (I should note, this recommendation comes from BizTalk experience, not WCF, so it may not apply. But if you are stuck, it's worth trying.)
The simple solution (provided you find this, or some similar article) is to use the serializer intended for WCF, and to stick with the DataContract/DataMember structure of more classic WCF service classes. So long as you specify names and namespaces, and don't care about fine-grained serialization control, everything should work great.
If you look around, you will find lots of recommendations to use 'ResponseFormat = WebMessageFormat.Xml' on your contracts. If you can avoid it, do so - WCF 4 lets clients specify in the request header their return types (an issue I discussed a little in a previous post), and IMO that is a far better way to go, especially if you have any plans of interfacing with clients more at home with JSON (i.e., Android devices).
Happy coding!
References:
http://www.danrigsby.com/blog/index.php/2008/03/07/xmlserializer-vs-datacontractserializer-serialization-in-wcf/
http://social.msdn.microsoft.com/Forums/en-AU/csharpgeneral/thread/dfd587ec-a269-49a9-a1a5-0f69d915c776
Thursday, June 30, 2011
Magic Cards: M12, Sphinx of Uthuun
I play Magic: The Gathering. If you don't know what this is, that's fine; it's a strategy card game where you play cards that represent spells, and you try and defeat your opponents. Definitely nerdy, but the fantasy flavor is great, and it's also an excellent mental challenge for those interested in that sort of thing. I'm always looking for games that make me think and push my brain, and this is definitely one of those. There are lots of similar games, and a lot of them are geared to kids (Pokemon, Yu-Gi-Oh, etc), but MtG was the original, and tends to have an older average age, especially in the tournament scene, which I enjoy.
So the next set is called a 'core' set, and features staple cards plus a bunch of new stuff. It sort of sets the tone for the next year. While originally numbers 4th edition, 5th, etc, after 10th they changed to Magic 2010, the set that shot tons of new life back into the game. We are now up to 2012, or M12.
One of the new cards that caught my eye is Sphinx of Uthuun, a completely impractical (it costs 7, which is enormous in terms of competitive play generally), but has some very interesting effects, and combines two cards into one with a total cost of 10. That alone makes in interesting, and with the average cost of playable spells creeping up to the 5-6 range lately (intentionally; WotC is trying to slow the game down over the long term to make it less 'obvious' who will win just based on opening hands), it could see play somewhere. Definitely in casual formats.
Here are the cards in question:
It's definitely and interesting concept, and I may throw one of these in my Commander deck to play around with. Fact of Fiction is a great card, especially when you can stack your deck before it resolves to make the most of the 'I'm getting at least one of these cards' effect.
References:
Magic: The Gathering
Star City Games, the company that has successfully privatized the tournament scene.
So the next set is called a 'core' set, and features staple cards plus a bunch of new stuff. It sort of sets the tone for the next year. While originally numbers 4th edition, 5th, etc, after 10th they changed to Magic 2010, the set that shot tons of new life back into the game. We are now up to 2012, or M12.
One of the new cards that caught my eye is Sphinx of Uthuun, a completely impractical (it costs 7, which is enormous in terms of competitive play generally), but has some very interesting effects, and combines two cards into one with a total cost of 10. That alone makes in interesting, and with the average cost of playable spells creeping up to the 5-6 range lately (intentionally; WotC is trying to slow the game down over the long term to make it less 'obvious' who will win just based on opening hands), it could see play somewhere. Definitely in casual formats.
Here are the cards in question:
It's definitely and interesting concept, and I may throw one of these in my Commander deck to play around with. Fact of Fiction is a great card, especially when you can stack your deck before it resolves to make the most of the 'I'm getting at least one of these cards' effect.
References:
Magic: The Gathering
Star City Games, the company that has successfully privatized the tournament scene.
A Small Follow-up on C++ vs C#
My recent post mentioned the fact that game developers use C++ over C# almost exclusively, citing speed as the primary reason.
One of my co-workers sent me this article on the issue: it's an interesting read for those interested in the performance differences of C++, C#, as well as x86 vs x64.
The article concludes by saying a programmer who keeps performance in mind can make their code just as fast if not faster in C# than C++, plus they get the win of smoother development in C#. I'm not sure if I agree with that at face value, but I can see the potential; if you assume (and you can, to some extent) that game developers tend to be some of the sharpest minds in coding, at least for their generation (and I can have a whole article on the nature of game developers, but for now I will settle on a few links), then you can hope they will have the wherewithal to avoid the heavier libraries and other potentially-monolithic code that can creep in through reference libraries and 'high level' architecture. There are programs written to streamline code in C++ to make the most of every clock cycle, I haven't done the research but I assume something similar may exist somewhere in C# land... or maybe that's an untapped market, and I should get to work coding something up. ;-)
Until next time, here are your reference links:
Code Project: C++ vs .Net, the benchmark article.
Ars' article on 'The Death March', the constant crunch-time for game developers.
The now-famous 'EA Spouse' article, detailing the harsh conditions forced on these young minds.
One of my co-workers sent me this article on the issue: it's an interesting read for those interested in the performance differences of C++, C#, as well as x86 vs x64.
The article concludes by saying a programmer who keeps performance in mind can make their code just as fast if not faster in C# than C++, plus they get the win of smoother development in C#. I'm not sure if I agree with that at face value, but I can see the potential; if you assume (and you can, to some extent) that game developers tend to be some of the sharpest minds in coding, at least for their generation (and I can have a whole article on the nature of game developers, but for now I will settle on a few links), then you can hope they will have the wherewithal to avoid the heavier libraries and other potentially-monolithic code that can creep in through reference libraries and 'high level' architecture. There are programs written to streamline code in C++ to make the most of every clock cycle, I haven't done the research but I assume something similar may exist somewhere in C# land... or maybe that's an untapped market, and I should get to work coding something up. ;-)
Until next time, here are your reference links:
Code Project: C++ vs .Net, the benchmark article.
Ars' article on 'The Death March', the constant crunch-time for game developers.
The now-famous 'EA Spouse' article, detailing the harsh conditions forced on these young minds.
Subscribe to:
Posts (Atom)
