rbandrews: (Goomba)
[personal profile] rbandrews
If you have a Mac, you can download DepthCharge and play it, as a normal app, not in the Dashboard. Maybe you just don't like the Dashboard, who knows? Instructions on how to play are available here.

The goal of this was to let me get comfortable in Cocoa, and it went pretty well. I'm about as comfortable in it as I am in Swing, which is saying something. The project went pretty painlessly, over the course of about three evenings.

What I did was translate a Dashboard widget that I wrote maybe a year and a half ago, to a rubycocoa app. I chose rubycocoa because I hated even the idea of dealing with Objective C, and because it's matured enough now that it's not a special hassle to write a Cocoa app using Ruby.

Dashboard widget architecture
The architecture of the old app was quite simple and clean. It wasn't object-oriented at all, because that's sort of weird in Javascript, but it was divided into three files:
  • drawing.js held everything involved with drawing the board to the canvas (DC used an HTML canvas tag for its graphics)
  • depthCharge.js held all the game logic and the code to tie it all together with the drawing stuff
  • gameUtil.js held the utility functions used by the game logic
So, it was fairly MVC already: drawing.js was the view, depthCharge.js was the controller and the model.

Cocoa architecture
Cocoa really wants things to be separate classes, and really, so did I, so I moved everything like this:
  • drawing.js became an NSView subclass called DepthChargeView
  • depthCharge.js became an NSObject subclass called AppController (tying everything together with Interface Builder) and a plain old Ruby class called DepthChargeGame
  • gameUtil.js became a plain old Ruby module called Util that got mixed into DepthChargeGame

AppController
This got basically totally rewritten, but I expected it to. All it does it is receive click information from the buttons and DepthChargeView (in pixel coordinates) and, for the buttons, set flags in DepthChargeGame; for the
view, send it the click (in board coordinates; what actual square was clicked). There's also a little bit of new stuff for dealing with closing the window, etc, stuff that you actually couldn't do in Dashboard.

The path that clicks take through the app is almost identical: in the Dashboard widget, the canvas would report them to one part of depthCharge.js, which would pass them off to another, which would then tell the canvas to redraw. In the new app, DepthChargeView hears them, tells AppController, which tells DepthChargeGame and then tells DepthChargeView to redraw. As game code goes this is the simplest thing imaginable, as everything is driven off of mouse clicks.

DepthChargeView
This has pretty much identical methods, with a few tweaks. I was able to mostly just copy code out of Dashcode here and translate it to Ruby, with some minor API changes between how canvas does things and how NSView/NSImage/NSBezierPath does things. On the whole, Cocoa probably loses here: the code is a lot more verbose, since Cocoa wants you to make NSPoint and NSRect objects, and canvas is happy being passed plain old numbers. But, Cocoa does a lot more, and a wrapper would be easy to write.

DepthChargeGame
Aside from splitting up one big method into four little ones, this class is basically identical to the old file. This is also the place where the Ruby/Javascript change was most annoying: in Javascript, there's no difference between an object and a hash, so all the game status data was stored as just hashes of hashes. In Ruby, there is, so I'm storing a lot of stuff as one big Ruby hash that should probably have been real fields. This meant a lot of typographical changes but not many logic changes.

Util
This is another place where Ruby-to-Javascript meant a lot of changes, but in a good way: most of the things in here were me writing utility methods like "map" or whatever, that Ruby comes with standard. So about half of this stuff just got thrown out, and the other half got rewritten to be one-liners. Ruby is really good at array manipulation, which is what a lot of this was.

About the only real logic change in here was that a function that used to return a lambda now takes a block. It's part of the stuff that finds how far away a mine is, and the only reason it wasn't like that before is that Javascript doesn't know from blocks.

Gotchas
There were almost none. The project was really painless... All the hard parts of the actual game were already written, I just had to look up the answers. There were basically no hard parts of the Cocoa translation, the API docs are really good, and there are plenty of threads and such online (especially on StackOverflow).

About the only thing that really had me going was displaying images, the API for that is kind of awkward: you can display part of an image (given as an NSRect) at a certain point, but if you do that, it'll stretch that part to the dimensions of the whole image for some dumbass reason. But you can also display part of an image on to part of another image, and as long as the source and destination rectangles are the same size it won't stretch.

Xcode, really, just plain sucks though. The editor is really enthusiastic about autocompleting, and really not enthusiastic about keeping the indentation right. This is in exact opposition to how I work with code, so it was really annoying to work with. That and I never got the debugger to work right, and I start to wonder why bother with an IDE at all.

All in all, though, A-plus-would-hack-again.

Code, if you're inclined, is here on Github.

Profile

rbandrews: (Default)
rbandrews

July 2024

S M T W T F S
 123456
78910111213
14151617181920
212223242526 27
28293031   

Style Credit

Page generated Jun. 14th, 2025 05:01 pm
Powered by Dreamwidth Studios

Expand Cut Tags

No cut tags