Scaling in HTML’s Canvas

Jan 31 2013

Send to Kindle

I’m probably not dropping any huge knowledge bombs here on many of you, but there are a few neat things about scaling when drawing to an HTML5 Canvas that I didn’t realize until a few months ago. So there are probably one or two of you who might find the info handy too.

Let’s say we’re drawing a circle in a canvas. We do this:

  context.beginPath();
  context.arc(100, 100, 50, 0, Math.PI * 2, false);
  context.stroke();

And we get this:

scale_01

(I’m assuming in all of these examples that you have an HTML document with a canvas element, and you’ve grabbed a reference to its 2d context, stored in a variable named “context”. You laugh, but someone’s bound to paste the above code into a text editor somewhere and complain to me that it doesn’t work.)

You probably also know that you can scale the circle like so:

  context.save();
  context.scale(2, 2);
  context.beginPath();
  context.arc(100, 100, 50, 0, Math.PI * 2, false);
  context.stroke();
  context.restore();

This scales everything by a factor of 2, so we get a circle that’s twice as large:

scale_02

But what might not be immediately obvious is the fact that the size of the shape, and the size of the stroke can be scaled separately. In this case because we set the scale before doing anything and restored the transformation matrix after all was done, everything was doubled, the size, the position, the stroke’s width.

But let’s try moving the restore line up before the stroke:

  context.save();
  context.scale(2, 2);
  context.beginPath();
  context.arc(100, 100, 50, 0, Math.PI * 2, false);
  context.restore();
  context.stroke();

This set up gives us a double sized circle, but because we cancelled the transform before applying the stroke, it stays one pixel thick, the default:

scale_03

We can even do the opposite and draw the circle the normal size, then scale up, stroke it, and restore:

  context.beginPath();
  context.arc(100, 100, 50, 0, Math.PI * 2, false);
  context.save();
  context.scale(4, 4);
  context.stroke();
  context.restore();

scale_04

So how is this useful? At least one great way: drawing ellipses. There is no native way to draw an ellipse in canvas. You can of course use some trig and draw a bunch of line segments, but that can be problematic. How many segments to draw? Too few for a large ellipse and it looks choppy. As the size goes down, you need less segments, and drawing too many is a waste of resources. Another complex and inexact solution involves using bezier curves. The easiest way is to just draw a circle scaled differently on one axis than the other. Of course, if you’re using a stroke on the arc, you can wind up with something weird like this:

  context.save();
  context.scale(10, 2);
  context.beginPath();
  context.arc(20, 20, 10, 0, Math.PI * 2, false);
  context.stroke();
  context.restore();

scale_05

See how the stroke was also scaled more on the x axis than on the y, making it look more like an Oakley logo than anything else. To fix it, just do what we did in the second example, moving the restore to before the stroke:

  context.save();
  context.scale(10, 2);
  context.beginPath();
  context.arc(20, 20, 10, 0, Math.PI * 2, false);
  context.restore();
  context.stroke();

scale_06

With this, we can even make a neat little ellipse function like so:

  function ellipse(x, y, w, h) {
    context.save();
    context.translate(x + w / 2, y + h / 2);
    context.scale(w / 2, h / 2);
    context.beginPath();
    context.arc(0, 0, 1, 0, Math.PI * 2, false);
    context.restore();
    context.stroke();
  }
  
  ellipse(100, 100, 200, 80);

I’ll leave you to work through that on your own. Here’s what calling it gives you:

scale_07

Finally, you can go really funky on it, scaling the shape one way and the stroke the other way, like so:

  context.save();
  context.scale(1, 2);
  context.beginPath();
  context.arc(100, 100, 60, 0, Math.PI * 2, false);
  context.restore();
  
  context.save();
  context.scale(20, 1);
  context.stroke();
  context.restore();

With a bit of experimentation, you can wind up with something that’s a half decent fake perspective:

scale_08

Anyway, old news to some of you, new news to others (I hope). More stuff to play with.

Send to Kindle

No responses yet

Circle Layering

Jan 28 2013

Send to Kindle

If you were wondering where my last post was heading…

growth

growth2

growth3

growth4

Oops, gotta turn off the “capture cursor” on that screenshot program. :)

Anyway, this was an idea I first explored several years ago on my art from code site, but has been in the back of my mind as something I wanted to play with more. (see: http://www.artfromcode.com/?s=cells) In the end I ditched the whole idea of perfection I was going for in the last post, as the fact of the outer circles having some space at the end gives it a more organic feel. In fact, you can see that I even exaggerated the space in some of the examples. Well, just a quick follow up. Carry on.

Send to Kindle

4 responses so far

Working out a Math Problem

Jan 25 2013

Send to Kindle

So I had this idea the other day for a graphic effect. I thought it might be interesting to walk through how it went from concept to functional code. So here it is. The effect involved placing smaller circles around a larger circle. Here’s about what I was looking to do:

circle

Note that all the outer circles exactly touch the inner circle and all the outer circles exactly touch each other. There are no spaces and no overlapping. When I went about coding it and found there were a few different ways to do such a thing and it wasn’t as straightforward as I thought it’d be.

I first tried specifying the size of the inner circle and the size of the outer circles. You can then go around the perimeter of the inner circle placing as many outer circles as will fit. Rarely will you get an exact fit this way, which means that if you don’t overlap, you’ll have some space. You can either have one big space at the end, or try to precalculate how many circles will fit and space them out evenly. Not what I wanted.

Another option is to specify the size and number of the outer circles. You can then arrange them so they just touch each other, figure out how far from the center they are, which then lets you calculate the size of the inner circle. This was fairly easy, but not my use case. I wanted to draw circles around an existing circle of known size.

The final option was to specify the size of the inner circle and how many outer circles you want. You then just have to figure out how big to make those outer circles so they sit on the edge of the inner circle and just touch each other. This would work, but I didn’t know how to figure out how big to make the outer circles. Time for some math! And some sketchy sketches.

math01

 

I made the above sketches to get a clear idea of what I wanted to do. On the bottom left is the inner circle with a couple of the outer circles shown. I know the angle between them, as it’s just 2 * PI / number of circles. And I know the radius of the inner circle. All I need is the radius of the outer circles.

The drawing on the top right is a close up of the top triangle in the first one. Angle A is one half of the angle between each circle, or PI / number of circles. R is the radius of the inner circle and N is the radius of the outer circle. From this, I could come up with the equation shown:

sin A * (R + N) = N

In other words, the sine of angle, times the radius (R + N) equals the opposite side (N). Basic trig.

Now I just needed to solve for N. My algebra was a bit rusty, but after a few false starts I got it down:

math02

 

Bottom line: N = (sin A * R) / (1 – sin A)

So I put it down in code and amazingly, it worked. Here’s what I came up with. Be it known that this code has been worked over quite a bit. It was originally just the onload function with one long linear mess of statements and vars. Once I saw that it worked, I refactored it into some halfway decent functions and cleaned it up a bit. It could still use work to make it more reusable, but at least it’s readable enough to post now:

window.onload = function() {
  var canvas = document.getElementById("canvas"),
      context = canvas.getContext("2d"),
      width = canvas.width,
      height = canvas.height,
      circles,
      inner,
      count = 20;
  
  // create inner circle and draw it:
  inner = {
    x: width / 2,
    y: height / 2,
    radius: 100
  };
  drawCircle(inner);
  
  // get outer circles and draw them:
  circles = getOuterCircles(inner, count);
  for(var i = 0; i < circles.length; i += 1) {
    drawCircle(circles[i]);
  }
    
  
  
  function getOuterCircles(inner, count) {
    var circles = [],
        angle = Math.PI * 2 / count, // angle between circles
        outerRadius = getOuterRadius(count, inner.radius),
        r = inner.radius + outerRadius,
        currentAngle;
  
    for(var i = 0; i < count; i += 1) {
      currentAngle = i * angle;
      circles.push({
        x: inner.x + Math.cos(currentAngle) * r,
        y: inner.y + Math.sin(currentAngle) * r,
        radius: outerRadius
      });
    }
    return circles;
  }
  
  function getOuterRadius(count, innerRadius) {
    var s = Math.sin(Math.PI / count);
    return (s * innerRadius) / (1 - s);
  }
  
  function drawCircle(c) {
    context.beginPath();
    context.arc(c.x, c.y, c.radius, 0, Math.PI * 2, false);
    context.stroke();
  }
};

As you can see, a circle is just an object with x, y, and radius properties. Create the inner circle and draw it.

The getOuterCircles function creates an array of circle objects that are placed around the inner one. It needs the inner circle and the count of outer circles to create. This function uses the getOuterRadius function to get the size of the smaller circles. The var s represents sin A in the original equation. And (s * innerRadius) / (1 – s) is basically the (sin A * R) / (1 – sin A) that I originally came up with.

The rest should hopefully be pretty obvious.

To see it in action, check it out here: http://jsbin.com/icahul/1/edit

Send to Kindle

12 responses so far

Windows 8: Pinning Documents to the Start Page #2

Jan 10 2013

Send to Kindle

In a post last week, I gave a kind of convoluted solution to pinning a document to the Windows 8 Start Page. Jesse Freeman just pointed me to a Windows 8 app that does the same thing in a bit more of an elegant way: Tile A File. You simply start the app, browse to a file you want to pin, and tell it to create a Start Page tile. You can opt to use the default icon it picks, or choose a custom tile image. Nice solution.

Send to Kindle

One response so far

Windows 8: Pinning Documents to the Start Page

Jan 06 2013

Send to Kindle

Before diving into all the stuff below (which still might be useful in some cases) you might want to look at this post: http://www.bit-101.com/blog/?p=3657

If you’ve read past the post title and into this text, I’m going to assume you have some experience or at least some interest in Windows 8. Therefore you know that there’s a Start page with tiles of various applications installed on your system. You can rearrange these, remove and re-add them, and in some cases resize them. A lesser known fact is the ability to pin folders to the Start page – just right click the folder in Explorer and you’ll see a “Pin to Start” menu item.

What seemed to be missing for me though, was the ability to add documents to the Start page. For example, I have an Excel spreadsheet where I log all my running, with various charts, weekly and monthly tallies and other analysis all baked into it. I use it almost every day, so I thought it’d be nice to have it pinned to the Start page to bring it up as fast as possible. There doesn’t seem to be any automatic way to do this, but I did figure out a little trick. It’s not quite a single-click solution, but for those few documents you use all the time, it’s worth the minute or less that it takes to set up the one time. You could also probably create some kind of script or utility to do this, but not sure it’s worth the extra effort. Anyway, here’s what you do:

1. From Explorer, right click on the document you want to pin and choose “Create Shortcut”. This will create a new file next to the original, which is an alias pointing to the document.

2. Now you want to copy this shortcut into the following folder:

C:\ProgramData\Microsoft\Windows\Start Menu\Programs

It’s probably going to tell you that you need administrator privileges to paste it there. You can just click continue to acknowledge this, assuming you do have such privileges.

3. You may want to rename the file. By default it will be something like “Keith Running Log.xls – Shortcut”. This is what will show up on your Start page under the tile, so if  you want, change it to something like “Keith Running Log”.

4. Now go to the Start page. You won’t see the tile there, but start typing the name of the file and it should quickly filter it out. Now right click it and choose “Pin to Start” from the App Bar. Bingo! A pinned document. (You’ll see I got lazy and didn’t even rename it.)

Note that this is not limited to MS Office docs. As far as I can tell, any document with a registered extension that will open in some application can have this method applied.

I’d love to see a Windows update that makes this a bit more straightforward, but at least it’s possible.

 

Send to Kindle

8 responses so far

Game in Progress: Infiltration

Jan 04 2013

Send to Kindle

I just wanted to share a little bit about the game I’m currently in the process of creating. It will be a Windows 8 game, called “Infiltration”. The game play, as well as the visual style, is very much a homage to the arcade classic Gravitar, but it is far from an exact copy. The concept is that you have to infiltrate this alien world, fly through various barriers and tunnels and capture one or more targets. I guess I’ll make up a story of what these targets represent – information, energy crystals, or whatever. Anyway, once you grab all the targets, while avoiding being shot, taking out the guns that are shooting you, and keeping track of your fuel, the exit will appear and you fly into it to complete the level. Here are a few screenshots of some of the levels:

  

The controls are basically what you would find in Gravitar or Asteroids: turn left/right, thrust, shoot, with some friction so if you are drifting left or right you will come to a stop fairly soon. The world of the game has basic one dimensional gravity, i.e. your ship is pulled down so you need to regularly use some upward thrust to keep from crashing. Thrusting of course uses fuel and when you run out of fuel, you’re gonna crash. Most of the levels have fuel pods you can pick up to replenish your reserves.

A Word about Gravitar

Gravitar is one of my favorite old school video games. This is not to say that it was my favorite back in the day. I remember it in the arcades, but I never played it that much. Why? Because it was freaking HARD! Now I play it on MAME, which doesn’t waste my hard-earned quarters (worth a lot more back in the early 80′s), but I’m still not even close to beating all the worlds in the first universe. If you want to see for yourself how tough it is, try the online version here: http://my.ign.com/atari/gravitar.

Beyond the visual style and basic game play (fly, shoot, fuel, get out), I did want to capture a bit of that difficulty in Infiltration. So most of the levels are pretty damn hard. I imagine some casual users might consider a few of them impossible. But 1. I will have completed every level personally, and 2. I still don’t think it’s as hard as Gravitar.

If you are interested in Gravitar, you need to check out Dan Coogan’s Gravitar site. Seriously, this is virtual shrine to the game. There you can find screenshots and walkthroughs of every level, interviews with the creators, scans of the original manuals, flyers, ads, etc. There is even 350+ scanned pages of the original Gravitar design binder – original sketches, plans, meeting minutes, office correspondence about the game, cabinet specs, etc. An amazing resource to see what went into making a game like that back in the day.

Designing Levels

It became obvious to me very early on that if I was going to be designing a bunch of levels, doing it by plugging numerical coordinates into some kind of text document, loading the level, seeing what it looked like, tweaking the values and checking again, etc. was going to be very painful. So I set about making a graphical level editor for the game. This was also done as a Windows 8 app and became a major side project for a few weeks. I can now draw out a full level, place, move, and delete any game items, all with a full undo/redo stack, and load and save levels as JSON. It’s got a grid you can toggle on/off as well at toggle snap-to-grid, and inputting of level metadata. I’m pretty proud of it. Here’s what it looks like in action:

The next pain point was after creating a level, I had to add that level to the game project and compile the game. And then recompile the game every time I made a change. So I added a feature in the game itself to be able to load custom external (not compiled in) levels from the file system. At this point, I don’t even need to have Visual Studio open anymore. I create the levels in the editor and test them in the live game. Tweak the level, save, reload the level in the game.

This has worked out so well that I’ve decided to leave the external level feature in the game itself, and release the editor as a public app as well. So, when this is finally all completed, people will be able to create their own levels, play them, share them with friends, etc. Maybe I could even get some kind of best level contest going.

Current Progress

As of this writing, I have 23 levels done. I plan to have 30 in total. The first few are pretty easy tutorial-type levels. It gets tough pretty quickly after that though. I think I’m going to check out the try/buy functionality for this game, releasing a trial version with a selection of levels, and a full version with all levels plus the ability to load custom levels. The editor would be free, but only useful if you have the full version of the game. The trial may have ads as well. Not looking to get rich here, but I never signed my vow of poverty either.

I’ve been working on this since the beginning of October. So it’s been a couple of months and I’m itching to be done with it. I think there is little, if any, coding left to be done, just level creation, which I hope to wrap up by this weekend. Then I’ll probably need to tweak and rearrange the levels a bit to make them flow. Hopefully you’ll see this in the store by the middle of the month, if all goes right.

Send to Kindle

4 responses so far

2012 in Review

Dec 31 2012

Send to Kindle

Following a tradition several years in the running, I bring you my personal retrospective into 2012 and a glimpse into the new year.

In fact, 2012 followed much the same pattern as 2011 – some interesting stuff in the start of the year, a long period of just kind of being bored with everything, and then finding something to be excited about as the year drew to a close.

As 2011 ended, I was starting to get more seriously into JavaScript and web development. In early 2012 I posted a few opinionated opinions on object creation in JavaScript, which sparked some good conversation. I followed these up with a couple of articles on the same subject on the Adobe Developer Center.

In April I went to Beyond Tellerand – Play, a creative/development conference in Cologne, Germany. I revived one of my most popular talks, Playing With Chaos, for this conference, redoing all the examples in JavaScript. It went over amazingly well. The talk is always really well received. After that, I had the idea to turn the talk into a self published book and got a good start on it. But that kind of fizzled out after a couple of months. Perhaps I’ll revive it some time in the new year. You can actually watch the whole talk at the BT Play site, along with the other sessions.

It turned out that BT Play was the only speaking gig I did all year – the least amount of speaking I’ve done since I’ve started. I did have a couple of other opportunities to speak, but have not been super interested in doing so, to be honest. There are few if any “Flash conferences” around anymore, and even if there were, I’m not really doing any Flash these days. I don’t feel expert enough to try to get into the JavaScript conference speaking circuit. I probably could do more stuff like the chaos talk, which would go over well in any kind of creative type situation, but the impetus just wasn’t there for some reason. I guess another part of it is that the Flash conferences had a certain group of people you knew were going to show up. A large part of the pull to speak at these was the hooking up with friends from around the world and hanging out with them. I do miss that.

In June, my coworker and friend, Todd Anderson, and I went down to Austin for the TXJS conference, as attendees. It was very odd not knowing anyone else at the conference, and I’ll admit it, not having people coming up to me saying they read my books, blog, used my components, etc. It was also around this time that I started to get a bit bored with JavaScript. This statement needs a lot of clarification. I don’t have any problem with the language itself or what it can do. And at first, I was pretty excited about all the stuff happening in the community – so many new libraries and frameworks coming out all the time. But this latter part is what eventually got me bored with it. Too many frameworks, too many opinions. and everyone in your face telling you the RIGHT way to do things. I guess I was a bit guilty of this myself earlier in the year with my object creation posts. But I just kind of got tired of all the king-of-the-hill playing that’s going on in web development this last year or so. I got too caught up in the “how to” part and wasn’t really MAKING anything.

So you might have noticed that from June to August of this year I went pretty dark. Honestly I wasn’t even doing anything blogworthy. A bit of a technology sabbatical, covered well enough in this post. Also mentioned in that post is that Windows 8 programming is what pulled me back into activity. I now have two apps in the Windows 8 Store and am very close to having my next one ready for submission.

If it’s not obvious already, I LOVE Windows 8 programming. Everything I’ve done so far has been with HTML/JavaScript, which is the one thing that has most excited me over the last couple of years. But there’s very little of the community chaos that you get in web development. You are essentially developing for IE 10. So all the self-righteous, holier-than-thou web dev hipsters aren’t going to talk to you anyway. :) I’m not a standards guru, but from what I know, IE 10 is pretty good. And even if it’s not 100% standards compliant, since you are only developing for the one platform for desktop Win8 apps, it’s kind of a standard itself.  There’s none of the cross-browser / cross-platform stuff to worry about. Basically, you can just ignore all the noise and MAKE stuff. And that’s what I’m doing. And loving it.

On the job front, I’m still at Infrared5 and that’s all going pretty well. Although nothing jumps out as being hugely exciting there for me in the last year, there were no huge problems either. I did one project in ActionScript / Flex / Desktop AIR, which was pretty unique in that it embeds a Red5 server in the app itself to do local recording of video. I have Paul Gregoire to thank for the Red5 help and in hacking together a stripped down version of the Red5 server that could be run on the low end hardware we were targeting. It was quite a technical challenge and worked out pretty well. Most of the rest of the year I’ve been doing iOS stuff for a client of the company that keeps hiring me back. Earlier in the year I helped build out a custom iOS library for the client, and in the latter half, I’ve been helping to build an app based on that library – truly eating my own dog food! It’s an interesting situation to be working with the black box of a closed library that you helped build but no longer have access to the source of. A bit frustrating at times, but quite an eye opener as well.

So what’s in store for 2013? More of the same. Specifically, my plan for the moment is to participate in this: http://onegameamonth.com/ The One Game A Month… er… project? It’s not a game jam, it’s not a contest, it’s just a couple thousand people who say they are going to try to make a game each month for 2013. Fantastic idea. I’ve spent way too long on the game I’m currently working on. That’s a common problem with any personal programming project. There’s no deadline, so you fiddle with it forever, trying to make it perfect, eventually get bored with it and start something else. This boxes out your time. One month. Get it done. Ship it. The game I’m working on now will wind up being my January game, and I have a great concept for February. I’m excited about this.

I also really do plan to do more blogging. I’m going to try to put something up 2-3 times a week. Probably a lot of it will be about the game I’m working on at that particular time. Any tricks or tips, problems or insight.

Also, as many of you know from my tweets, G+ posts, other comments, and occasional mentions on this blog, I’ve been heavily into running in the last few years. This past year I ran my first two marathons and am now in training for my first ultramarathon, a 50K (31 mile) trail race in April. I don’t like to put too much of that info on this blog, but if you are interested to hear more about that, here’s another year in review post from that viewpoint, on my personal blog.

Send to Kindle

2 responses so far

Falling Balls Update

Dec 05 2012

Send to Kindle

At the end of October this year, Falling Balls was released into the Windows 8 Store. After just over a month, I thought it would be interesting to discuss how it is doing, and to answer the question, “should I build games or apps for Windows 8?”

Hard statistics.

Here you can see the download stats. It’s averaging roughly 900 downloads a day, with a best day of just over 1500, and generally trending up. The weekends usually see more downloads than the weekdays. You can also see the demographics of who is downloading the app.

As of this writing, the game has been downloaded about 25,000 times.

The dev center also gives you some pretty cool status such as average app usage per day. Amazingly, in mid-November, people were playing Falling Balls for an average of 17 minutes per day! Average! That’s insane. It’s now around 6-7 minutes per day, which to be honest, still amazes me.

Money.

So what about profit? I decided to follow the successful pattern of releasing the app for free and using advertising. I used Bing ads which integrate well in Windows 8 apps, but you can easily implement any other HTML-based ads in a Windows 8 HTML/JS application.

Here you can see daily ad revenue. There was a bug in my own code in the first release that was preventing the ads from showing up. Obviously, that got fixed on the 8th and has been generally uptrending since then, again with peaks on the weekends. Averages are in the range of around $5 on weekdays and $7-8 on weekend days. (The dropoff for the last day is just because the reports lag a day behind, so there is no data for today’s revenue yet.) In the three or four weeks that the ads have actually been appearing, I’ve made around $120-130. But I suspect that will increase a bit, especially since I’m planning a few upgrades to the game.

Summary.

So far, I can’t say that the game has been the runaway, oh-my-god-what-happened success that it was on iOS, but I’m sure there are a number of factors that play into that, such as the fact that iOS devices were broadly available for a year or so before the app store was open to developers. There was a huge base of people wanting new apps and games from day one. There is also a big difference between the mobile app store market and a mostly desktop / somewhat tablet based app store.

I’m hopeful that the trends will continue upwards as Windows 8 gains traction and more people get Win8 devices over the holidays and in general. But even so I’m not complaining. Already, I’m looking at a couple hundred dollars in passive income. Nothing to quit the day job over, but certainly welcome.

If the situation changes markedly in the future, I will be sure to report on it.

Send to Kindle

One response so far

Falling Balls for Windows 8 release 2 is out

Nov 08 2012

Send to Kindle

I pushed Falling Balls out the door with bare minimum functionality. In the week or so it’s been out though, it’s gotten a couple of thousand downloads and a solid 4 star rating. Early this week I decided to enhance it a bit with two new difficulty levels – “hard” and “ninja”.

In hard mode, the balls start coming out faster right away and you’ll see a lot more of them. If you had a decent score in the original mode, you’ll be hard pressed to get anywhere near that in hard mode.

Ninja mode is a bit faster than easy (original) mode, but the balls come at you from both directions. You can still last a while on it, but it’s far more challenging, and in my opinion, a lot more interesting.

I also fixed a bug which was preventing ads from showing up in most cases. I’m saying this because for most people it will look like I just added the ads in this release, but in fact they were there all along, just not working very well. Like the iOS version, the ads will only show after you die and will go away while you are in active game play. Feel free to click on them if you see something vaguely interesting, and support … me. :)

Send to Kindle

No responses yet

Falling Balls for Windows 8 now available

Nov 01 2012

Send to Kindle

Just got the email last night, Falling Balls is now available in the Windows 8 Store!

I’m really interested to see how this does. I’ve implemented Bing ads in the game, exactly the same way they are implemented in the iOS version, i.e. when you die, there is a banner ad in the top center of the screen. It goes away when you start playing, so it’s not there to distract you from game play. You don’t want to see an ad – don’t die! However, I’m not seeing any ads in the live app. It took a while to see any at all while I was testing, though it worked fine using a test ad account. From looking at various forum posts, I think they just don’t have that much inventory, so there is very low fulfillment rate. I may need to switch to another ad provider if Bing doesn’t pull through soon.

I also have some additional features I want to add to the game. Considering making them in-app purchases, but if I can get ads working a bit better, I’d be happy to keep them free.

Send to Kindle

4 responses so far

« Newer posts Older posts »