Posted on Business

Hourible: the time tracking tool I use

I made a new online tool!

Okay so new is a bit of a stretch. It’s based on an internal tool I’ve used at altered effect for years now, but I’ve cleaned that up and now it’s available for public use:

Time tracking is the horrible. (see what I did there?) But does it have to be? I don’t know.

It’s one of the great mysteries of life, and something I worry we’ll never know the answer to.

Long story long, I wasn’t happy with any of the time tracking solutions I found so I settled on something quick and easy – using Google calendar. I was already comfortable using it, and I it was surprisingly intuitive to just drag events to the appropriate size in order to keep track of how much time I spend on projects.

It actually works wonderfully, but when it comes for invoicing you need to add up the hours.

Or do you? Trick quesion – you do, but Hourible does it for you!

Over whatever time frame you like. Plus, you can easily filter the events. Since I use Logipar any chance I get, Hourible supports logical operators in the filter string. It sums up your hours for you, and you can take a deeper look to see how they’re broken down.

Of course, you still need to make the invoices then. Which you can do manually if you prefer, or Hourible can generate them automatically with the click of a button. Which… is a lot less work for me than making them manually. It’s quite convenient actually.

All that and Hourible is free as you want it to be!

Give it a try if you want, especially if you regularly work on multiple projects or for various clients. Maybe it’ll help.

hourible.com

Posted on crafting

A new handle on knife

See what I did there? You’re welcome.

The handle on one of my mother’s kitchen knives was deteriorating so I decided to “fix” it.

See? Deteriorating. Why didn’t you just believe me?

So I took some measurements…

Just look at those scribbles. Those are some quality measurements.

And using those measurements I made a 3D model in Fusion 360.

     

Which I printed a bunch of times on my SnapMaker 2.0! With adjustments here and there until I was satisfied with the fit.

Don’t mind the teeth or circuitry in the background. Focus instead on all the knife scales. Can you tell which ones are 3d printed?
Here’s the knife without its scales. It looks so naked.
The 3D print worked pretty well! If only I wasn’t worried about.. you know, thermo plastic being near heat sources and a perfect surface area for bacteria growth.

Once I was happy with the model, I CNCed it out, also using my SnapMaker 2.0, on a piece of test wood. Yeah it does CNC and 3D printing, so what? (Also laser cutting.)

The SnapMaker 2.0 is pretty cool.
They seemed fine.

They the test CNCing went pretty well, so I did it again – but this time from using some black walnut from a tree that grew on her childhood home.

The brass rods fit! I taped up the knife blade for “safety”, he said trying to distract from how dull the blade actually is.

Then I cut the brass rods down to size with a hacksaw, and started peening them. Oh also, I glued them in (and the scales to tang) with some resin.

Peening has begun.

It turned out pretty alright, I think! I did some sanding and used some wood filler to plug a gap, which… I should have used a different colour, but I happened to have one on-hand.

Also I made a quick leather sleeve for it because I started to feel embarrassed by the painters tape.

Here’s the pointy end!
Here’s the stubby end.
Posted on Programming

Heaps/Hashlink on Android

I wrote Hexlock using Haxe and Heaps, which went quite well – until I decided to compile it for Android. But it’s there now and we can all relax. Aside from getting Heaps setup to compile on Android, I ran into a few other issues.

  1. Touch event coordinates were wildly inflated

This made it seem like my interaction events weren’t going through at all (because in a sense they were not), but I tracked this back to touch events being multiplied by 10,000. They come in as a value between 0 and 1, and I discovered that they’re multiplied by windowWidth (which makes sense), but then divided by something called TOUCH_SCALE. It turns out I needed to add -D hl_ver=1.12.0 to my compile.hxml file to get it to use the appropriate TOUCH_SCALE.

2. I wanted to trigger vibration on Android

So I did. Click here to if you want to know how: triggering vibration on Android in Heapse/Hashlink.

3. Handling the back button

If you’re playing, the back button should take you back to the level select screen. If you’re at the level select screen, it should do what the back button does on Androids and minimize the app. And now it does! If you want to know how, check out dealing with the Android back button for Heaps/Hashlink.

4. hxd.Save didn’t work out-of-the-box

At this point I was too tired (lazy) to dig into why, so I just implemented my own method of saving using Android shared preferences. It seems to work! If you’d like to learn how, click here: saving data for Android with Heaps/Hashlink.

Again, I am not saying these are the best, correct, or even good ways to handle those issues. They’re just what I did, and they worked for me.

Posted on Programming

Heaps/Hashlink saving data for Android

When I was making Hexlock, I was disappointed but not overly surprised to find that hxd.Save didn’t work out-of-the-box on Android. It’s quite possible that it would have after some simple changes, but at that point I was too tired to dig through source files to try to discover why and what those changes may be.

So I just implemented my own method of saving using Android shared preferences.

The first step was to load and store data differently when compiling for Android, which wasn’t too bad because I already had my own wrapper to handle that. A lot of this will contain code specific to Hexlock, which you can ignore or not at your discretion. Basically I keep a map of levels (JSON strings) to score data, load it once when the game starts, and save the entire map out on a new high score.

public static function loadSaves():Void {
	var defValue = new Map<String, Highscore>(); // The default value
	#if android
		var bytes = Main.load(getInstance().name); // These are hl.Bytes
		var data = @:privateAccess String.fromUTF8(bytes); // Convert it to a String

		// This is 100% overkill but a wise man once said:
		// It is better to just throw in random possible values
		// than to wait for Android Studio to finish compiling and check 
		// what they actually are.
		if (data.length == 0 || data == "null" || data == null) { 
			// Welp, at least we have the default value
			getInstance().data = defValue;
			return;
		}

		// This code is basically taken from hxd.Save
		var obj : Dynamic = haxe.Unserializer.run(data);

		// set all fields that were not set to default value (auto upgrade)
		if( defValue != null && Reflect.isObject(obj) && Reflect.isObject(defValue) ) {
			for( f in Reflect.fields(defValue) ) {
				if( Reflect.hasField(obj, f) ) continue;
				Reflect.setField(obj, f, Reflect.field(defValue,f));
			}
		}
            
		getInstance().data = obj; // That's our data now            
	#else
		// If it's not for Android, just use hxd.Save
		getInstance().data = Save.load(defValue, getInstance().name);
	#end
}
public static function save(level:Level, highschore:Highscore) {
	getInstance().data.set(Json.stringify(level), highscore); // Add the score
	#if android
		var data = haxe.Serializer.run(getInstance().data);
		Main.save(getInstance().name, data);
	#else
		Save.save(getInstance().data, getInstance().name);
	#end
}

For both save() and loadSaves() hxd.Save is used, except when compiling to Android, because I added -D android to my compile-to-c.hxml. There may already be a hlNative one #shrug. I didn’t see one, and adding -D android was easy enough.

You can see from above that the code requires the function Main.save(String, String) and Main.load(String); getInstance().name is just a constant string identifier like ‘my_cool_highscore_file’.

Since those functions are only referenced when compiling for Android, they only need to be defined when compiling for Android, which is convenient because hl.Bytes isn’t available on non-hashlink targets.

And in Main.hx:

#if android
@:hlNative("Java_io_heaps_android_HeapsActivity")
public static function save(name:String, data:String) {}
#end

#if android
@:hlNative("Java_io_heaps_android_HeapsActivity")
public static function load(name:String):hl.Bytes { var i = 4; return hl.Bytes.fromValue("null", i); }
#end

Who knows if I needed a body for load(String) – not me – but it worked, so I didn’t look into it further.

When compiling to C code for Hashlink, calls to the function Main.save() get replaced with Java_io_heaps_android_HeapsActivity_save(), which I defined in my jni.c1 file, and of course an equivalent transformation happens to Main.load().

JNIEXPORT vbyte* JNICALL Java_io_heaps_android_HeapsActivity_load(vstring *name) {
	(*jvm)->AttachCurrentThread(jvm, &thisEnv, 0); //this is important to avoid threading errors
	jclass cls = (*thisEnv)->FindClass(thisEnv, "io/heaps/android/HeapsActivity");
	jmethodID method = (*thisEnv)->GetStaticMethodID(thisEnv, cls, "loadData", "(Ljava/lang/String;)Ljava/lang/String;");

	char *cname = hl_to_utf8(name->bytes);
	__android_log_print(ANDROID_LOG_DEBUG, "JNI.c", "loading... %s", cname);

	// Is this necessary? Who knows! Better safe than seg fault though.
	char *buf = strdup(cname);
	jstring jstrBuf = (*thisEnv)->NewStringUTF(thisEnv, buf);

	jstring result = (*thisEnv)->NewStringUTF(thisEnv, "null");
	if (method > 0)
		result = (jstring) (*thisEnv)->CallStaticObjectMethod(thisEnv, cls, method,jstrBuf);
    
	char *cresult =   (*thisEnv)->GetStringUTFChars(thisEnv, result, 0);
	__android_log_print(ANDROID_LOG_DEBUG, "JNI.c", "returned... %s",cresult);


	free(buf); // Free the string
	return cresult;
}

JNIEXPORT jstring JNICALL Java_io_heaps_android_HeapsActivity_save(vstring *name, vstring *data) {
	(*jvm)->AttachCurrentThread(jvm, &thisEnv, 0); //this is important to avoid threading errors
	jclass cls = (*thisEnv)->FindClass(thisEnv, "io/heaps/android/HeapsActivity");
	jmethodID method = (*thisEnv)->GetStaticMethodID(thisEnv, cls, "saveData", "(Ljava/lang/String;Ljava/lang/String;)V");

	char *cname = hl_to_utf8(name->bytes);
	char *cdata = hl_to_utf8(data->bytes);

	char *bname = strdup(cname);
	char *bdata = strdup(cdata);

	__android_log_print(ANDROID_LOG_DEBUG, "JNI.c", "saving %s: %s", bname, bdata);

	jstring jname = (*thisEnv)->NewStringUTF(thisEnv, bname);
	jstring jdata = (*thisEnv)->NewStringUTF(thisEnv, bdata);

	if (method > 0)
		(*thisEnv)->CallStaticVoidMethod(thisEnv, cls, method, jname, jdata);

	free(bname);
	free(bdata);
}

To find out more about where thisEnv and jvm come from, check out triggering vibration on Android from Heaps/Hashlink.

Haxe strings come through Hashlink as vstrings, and the JNI uses jstrings, so you can see me converting between the two, above. Onload I just send back the char* instead of dealing with transforming it back into a vstring, and handle that in Haxe (which you can see even further above). Is this a memory leak? Who knows! Not me. But if it is, oh well – the load function is only called once during the apps lifetime.

As you can see, the C code assumes there will be saveData() and loadData() functions in io.heaps.android.HeapsActivity, and there will be, because we’re about to add them!

static public void saveData(String name, String data) {
	SharedPreferences sharedPref = getContext().getSharedPreferences(name, Context.MODE_PRIVATE);
	SharedPreferences.Editor editor = sharedPref.edit();
	editor.putString(name, data);
	editor.apply();
}

static public String loadData(String name) {
	SharedPreferences sharedPref = getContext().getSharedPreferences(name, Context.MODE_PRIVATE);
	return sharedPref.getString(name, "null");
}

Above you can see that I just use name for both the SharedPreferences file and for the key in it. Whoops, that’s embarrassing. I could have used another string defined in the java, or just omitted specifying a name completely but it works and that’s good enough for me.

If you want to find out more about the files I’m referencing (including a Git repo hosting them), you can do so here: Hello World; Heaps on Android.

Posted on Programming

Dealing with the Android back button for Heaps/Hashlink

I wanted the back button on Android to work in a reasonable way for Hexlock: If you’re playing, the back button should take you back to the level select screen. If you’re at the level select screen, it should do what the back button does on Androids, and minimize the app.

It turns out that different versions of Android send different key codes with the event when you hit the back button, (and I also wanted to support Esc and Backspace on keyboard), so I matched a few of them.

var g = new LevelSelect();
sceneManager.requestMainMenu = function() {
	var s = sceneManager.switchScene(g);
	if (s == g) {
		trace("Already on the main menu, exit game");
		exitapp();
	}
}

hxd.Window.getInstance().addEventTarget(function(event : hxd.Event) {
	switch(event.kind) {
		case EKeyUp: 
			// backspace and escape; the last one is what my android sends for back
			if (event.keyCode == 8 || event.keyCode == 27 || event.keyCode == 1270)
				sceneManager.requestMainMenu();
		case _: // Match the rest
	}
});

Then I added a static function exitapp() to my Main class, the idea being that it would do nothing unless I was compiling for Android. (I added -D android to my compile-to-c.hxml for just this purpose). There may already be a hlNative one, but who knows at this point. I didn’t see one, and adding -D android was easy enough.

#if android
@:hlNative("Java_io_heaps_android_HeapsActivity")
#end
public static function exitapp() : Void { }

When compiling to C code for Hashlink, calls to the function Main.exitapp() get replaced with Java_io_heaps_android_HeapsActivity_exitapp(), which I defined in my jni.c1 file.

JNIEXPORT void JNICALL Java_io_heaps_android_HeapsActivity_exitapp() {
	(*jvm)->AttachCurrentThread(jvm, &thisEnv, 0);
	jclass cls = (*thisEnv)->FindClass(thisEnv, "io/heaps/android/HeapsActivity");
	jmethodID method = (*thisEnv)->GetStaticMethodID(thisEnv, cls, "callbackExit", "()V");
	if (method > 0)
		(*thisEnv)->CallStaticVoidMethod(thisEnv, cls, method);
}

To find out more about where thisEnv and jvm come from, check out triggering vibration on Android from Heaps/Hashlink.

As you can see the C code assumes there is an callbackExit() function in io.heaps.android.HeapsActivity, and there is, because I added one!

public static void callbackExit() {
	Intent intent = new Intent(Intent.ACTION_MAIN);
	intent.addCategory(Intent.CATEGORY_HOME);
	instance.startActivity(intent);
}

And that seemed to work! Even better, it didn’t crash.

If you want to find out more about the files I’m referencing (including a Git repo hosting them), you can do so here: Hello World; Heaps on Android.

Posted on Programming

Trigger vibration on Android from Heaps/Hashlink

I wanted to trigger vibrations in Hexlock – so I did, and here’s how! I am not saying this is the best, correct, or even a good way to go about it. It’s just what I did. Maybe it will help.

I added a static function to my Main class, the idea being that it would do nothing unless I was compiling for Android. (I added -D android to my compile-to-c.hxml for just this purpose). There may already be a hlNative one, but who knows at this point. I didn’t see one, and adding -D android was easy enough.

#if android
@:hlNative("Java_io_heaps_android_HeapsActivity")
#end
public static function vibrate(i:Int) : Void { }

When compiling to C code for Hashlink, calls to that function (Main.vibrate(i)) get replaced with Java_io_heaps_android_HeapsActivity_vibrate(i), which I defined in my jni.c1 file.

JNIEXPORT void JNICALL Java_io_heaps_android_HeapsActivity_vibrate(int duration) {
    (*jvm)->AttachCurrentThread(jvm, &thisEnv, 0); 
    jclass cls = (*thisEnv)->FindClass(thisEnv, "io/heaps/android/HeapsActivity");
    jmethodID method = (*thisEnv)->GetStaticMethodID(thisEnv, cls, "vibrate", "(I)V");

    __android_log_print(ANDROID_LOG_DEBUG, "JNI.c", "Vibrating: %d", duration);

    if (method > 0)
        (*thisEnv)->CallStaticVoidMethod(thisEnv, cls, method, duration);
}

You’re probably wondering where thisEnv comes from, and if you are I can help! I modified my startHL() function to be:

JNIEnv* thisEnv; // I want access to these from other functions
JavaVM  *jvm;
  
 
JNIEXPORT int JNICALL Java_io_heaps_android_HeapsActivity_startHL(JNIEnv* env, jclass cls) {
    thisEnv=env; // Store the JNIEnv for later use
    (*env)->GetJavaVM(env, &jvm); // Also the JVM
     return main(0, NULL);
}

As you can see from two functions back, the code assumes there is a vibrate() function in io.heaps.android.HeapsActivity, and there is, because I added one.

    static public void vibrate(int duration) {
        Log.d("HeapsActivity.java", "static vibration call");
        Vibrator v = (Vibrator)instance.getSystemService(Context.VIBRATOR_SERVICE);
        v.vibrate((long)duration);
    }

And now it vibrates! You can find out more about the files I’m referencing (including a Git repo hosting them) here.

Posted on Programming

Presenting Hexlock, a really fun game™

Hexlock, a simple counting-based puzzle game, now available for your Android device.

Select a level, defeat it, and repeat.

There are twenty-one levels in total, each represented by a different tile on the level select screen, and you can play them in whichever order you like.

In each level, you start by clicking the middle tile. From there, just form a path from neighbour to neighbour until the two numbers at the top match up. Once they do, you’ve won!

The hex tiles each have a value, represented in hexadecimal. The goal is in binary. All you need to do is form a path such that the hex values add up to the binary goal. You can do that by adding and converting between bases in your head, or, you know, just click around until it unlocks.

Once you’ve defeated a level, how well you did will be reflected in that level’s tile on the level select screen. Grey if you have yet to complete the level; green if you scored par or under; blue if you scored under 2 par; orange for less than 3 par; or red if your path was longer than 3 par.

Only your highest score for each level will be saved, and if you ever want to review that path, long press the hexagon for that level in the level select.

Controls

  • Clicking on the most recently selected hexagon will unselect it.
  • Press and hold (long press) the middle tile to reset your current level.
  • Press and hold (long press) a level to load your highest scoring path.
  • Press the back button on your phone, or escape or backspace on a keyboard to return to the level select menu.

Download it today in the Google Play store!

RATED 5 TO 6 HEXAGONS BY REVIEWERS EVERYWHERE1

Or, if you don’t have an android device, you can play the web version here.

1. This is not true; very few people rate things in hexagons these days