Posts Tagged haxe

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

Posted on Programming

Hello World; Heaps on Android

Hello, and welcome.

As there’s no way for you to reasonably know, I built a puzzle game recently – in Haxe, using Heaps which I had only used once before, for a website that required a 3D can. Which may seem strange, but I assure you it made sense at the time. Anyway, Heaps seemed fairly nice, so I figured I should try it again.

But I wanted it to be an Android app.

Which is okay, because Heaps works on Android. It says it right there on the website! Unfortunately, there’s not much info on it beyond that, and compiling Heaps for Android turned out rather less straight forward than I had hoped.

Fast forward five days of debugging Android Studio configurations and C code, and I now have a simple Heaps app running on Android. And it’s on GitHub!

https://github.com/altef/heaps-android

It’s built based on a couple of repos1, 2, the second of which was particularly helpful, but I had to mix and match some things between them.

I don’t intend to keep it up-to-date unless I have a use for it, but it works as of November 11, 2022.

How it works

The Haxe code lives in app/src/main/haxe. It’s a basic Heaps hello world project set up for VS Code, as described in the documentation section of the Heaps site. There’s a compile.hxml file which tells the compiler to place the generated C code in the out/ directory of the cpp folder.

The C code lives in app/src/main/cpp. It contains git submodules to some of the necessary libraries, as well as a CMakeLists.txt. Android Studio uses that during its build process. You can find out more about the other things there at the link, above.

This is also where the JNI function for the heaps app lives (in jni.c).

The Java code lives in app/src/main/java, io.heaps.android.HeapsActivity is the entry point to the Heaps hello world and triggers it to run. It extends org.libsdl.app.SDLActivity which handles a bunch of the SDL stuff. Add your own classes as necessary if you want, I probably won’t!

Each of those directories contains a readme with some info about what they contain.


One important note: The NDK version.

It didn’t work with any of the NDK versions I had installed. I ended up having to download r18b and use that. I placed it in the ndk/ folder of my Android SDK directory. You can find this under File > Project Structure, and then by clicking SDK Location.

So I placed it in C:\Users\Brad\AppData\Local\Android\sdk\ndk.

But that’s not all – apparently Android Studio expects the ndk version directory to be named a certain way, so I named mine 18.1.1.

The Android Studio project in the Git repo expects that, so if you name it something else, update build.gradle to reflect that.

After that it should just be a matter of clicking build in Android Studio.


Problems along the way

As you can imagine from the heading nearby I ran into so many problems along the way. So many problems, and so many android log statements.

But my memory is terrible, and Google found surprisingly few results, so I’m going to catalogue some of them here. You know, for next time. You should be able to ignore this portion, they should all be addressed already in the repo code.

OpenAL needs liblog.so: liblog.so needed by libopenal.so not found.

I tried a bunch of things for this, but the solution turned out to be adding arguments -DANDROID_LD=lld to the app gradle.build file.

hashlink/src/std/types.c error: undefined reference to 'hl_zalloc'

hl_zalloc is defined in hashlink/src/gc.c. I added that to CMakeLists.txt when building the Hashlink library.

fatal error: 'minimp3.h' file not found

Added hashlink/include/minimip3 to the target_include_directories for fmt.hdll, in CMakeLists.txt.

java unsupported major.minor version 52

I updated to Java 1.8.0.

E/EGL_emulation: eglCreateContext: EGL_BAD_CONFIG: no ES 3 support (and a bunch of other SDL / window creation errors)

These seemed to go away when I tracked down that Hashlink uses version 3.0 for mobile, added the appropriate declaration to my AndroidManifest.xml, and fiddled with the settings in the emulator. Honestly I’m not sure what ended up fixing these things because nothing did and then I restarted Android Studio and everything was magically working.

At which point I was just willing to accept that.

Posted on Programming

Gradient Vodka Soda

Gradient vodka soda is vodka soda with a novel twist – each bottle has a different amount of alcohol, allowing you to easily taper off your consumption through the night – which they call modern moderation. Check them out if you’re in the market for that sort of thing (and are of legal drinking age).

I programmed their teaser website, which had a bunch of interesting technical requirements. In this post I will go over a few of the more interesting tests I wrote. Most will work best if you have hardware acceleration enabled.

Rotating cans

The website revolves around a 3d can (see what I did there?). To accomplish this, I used, as I do given whatever excuses I can find, the programming language Haxe. And this time in particular, Heaps.

I got a 3d cylinder rotating, and applied a material to it. (It’s not really a cylinder, I made a model to account for the inset near the top.)

I ended up rendering each can to 2D, and positioning it that way. It made it easier to line everything up and prevented skewing from the camera’s perspective. This allowed the overlays to fit consistently. (The metal of the can, and the shadows on it are overlays.) The actual 3D can is just an ambiently lit label applied to the model.

But I couldn’t find an easy way to switch the labels on it; so I ended up writing a shader to do that. And so it went for so many more things –

But what if it was a shader?

I wrote a bunch of these to accomplish neat graphical effects. Heaps actually makes it pretty easy. Though, in most cases, I ended up moving away from the shader solutions to better support visitors without hardware acceleration enabled.

Ever moving blobs

The client wanted some subtle blobs floating around in the background, so I thought to myself, high on my recent shader successes, why not do it as a shader too?

I made a quick proof-of-concept, and it looked kind of neat, almost like a lava lamp. The blobs ended up getting cut in favour of a background video, which then also got cut.

Different sized dots

I tried dots a few different ways to see what which worked best. First I tested moving a small canvas with the mouse, and that worked fairly well. Unfortunately, requirements shifted so I tested drawing dots over a large canvas.

Performance wasn’t great, so I ended up using a small canvas to draw the dot at the appropriate size (the sizes change as you scroll), and used that as a pattern to fill in the larger, screen-sized, canvas.

That seemed to work fairly well, except that iOS apparently doesn’t send mouse events while scrolling, which interacts poorly when you’re making a scroll-based website.

Scroll-based flash animations

As you get to the bottom of the page, there is a box that the cans slide into. It was meant to close, spin around, and settle onto the ground, as you scrolled down. Unfortunately, we didn’t get the rotating box assets in time, so Sheldon amended the animation concept.

I figured it would be a pain to do in code, and animations are exactly the sort of thing Adobe Animate (formerly Flash) was made for. All I had to do was hack their JS to animation to progress through the timeline on vertical scroll rather than time. I wrote a quick proof of concept (and ran into a bunch of issues with more complicated, nested animations), but overall it worked really well.

So well that it got me wondering if that’s already a thing people do. If not, it seems perfect for this sort of website. Adobe should consider adding as an option to their JS output. Or maybe I should just release a library for it.

As an added bonus, using Adobe Animate let Sheldon prepare the animation himself, which meant less work for me!

There were so many problems

Most to do with different devices and browsers, and the different values between them. It took forever to track down and deal with these, but in most cases we managed to get the numbers to line up.

But one thing that kept cropping up was the matter of hardware acceleration.

I kept it turned off for my tests, but apparently some Macbooks have it enabled by default, but disable it when unplugged. This caused a number of surprises when people tested it, many to do with a feature I had implemented for just this purpose.

Framerate scaledown

All of these things going on could be quite a load on a slower system, at times making it painful to use. No matter what, I wanted the website to be useable – responsive design, but along the dimension of framerate rather than browser width.

To help with this, I implemented a system of framerate scaledown. Basically, the browser would check your framerate, and if it was too low too many times, it progressively disabled features.

I was pretty happy with this system. In a lot of cases it would just turn off interactions (remove the background video, make the dots non-responsive to mouse position, etc.). It worked really well, but in conjunction with the above it lead to some confusion. Sometimes the dots resizing under your mouse would work, and sometimes they wouldn’t (they wouldn’t work if they’d been disabled).

If you want to checkout the website, you can find it at drinkgradient.com

Posted on Programming

Logic parsing & cat breeds

Logipar – an open source logic parsing library

Logipar, pronounced Lojipur (and soon you’ll see why), has a history.

It always starts out like this: I’m working on a project and it has some form of filter box – where you can type, and the results shown will be limited to those relevant to what you’ve typed. So you can type “cat” and it’ll show you all cats.

That’s easy enough. But then I want to be able to match multiple things simultaneously – to see all the cats as well as all the dogs.

Still easy, I normally split on commas – “cat, dog” would show all cats and all dogs.

And here is where things get troublesome. Sure, I can list all the cats and all the dogs, but what if i want to list only results that are both cats and dogs.

Okay this example is starting to break down. Where I’m going with this is logic parsing. Splitting on commas, I have to decide whether to join them with a logical OR (show all the cats as well as all the dogs) or with a logical AND (show only results that are BOTH a cat and a dog).

And sure, I can pick one or the other, but what if I want the option to use either?

So I made Logipar. An open source library for parsing logic strings. With Logipar I can type “cat OR dog“, or I can type “cat AND dog“. And it’ll parse that logic for me.

Logipar works in multiple languages, which is convenient because I work in multiple languages. Now to go back and add it to all the various tools I’ve ever written. JK, I probably won’t do that.

But all future projects.. That’s a different story!

You can use it too, if you want, I mean. No pressure.

 


Cat breed data

Having built Logipar, I wanted to create a demo for it. To identify any obvious bugs, sure, but more to better convey what Logipar does and is capable of.

For that, I needed some data. And the first thing that came to mind was cats.

When I went looking for a cat breed dataset, I found nothing. Sure, there were some websites with breed data – but they weren’t great. Fields would randomly be missing, or displayed in completely different ways.

I spent an entire day compiling the data from five different sites (wikipedia, cattime, petfinder, purina, and royalcanin), conjoining it, and then cleaning it up.

Now I have a cat breed dataset.

So if you want to see what Logipar can do and learn about cats, boy, do I have a tool for you: https://altef.github.io/logipar – conveniently iframed below:








It shows more or fewer fields based on the width of its container.



Posted on Programming

Gently flickering flame, part 3

Now that we can talk to the lights, and choose colours, it’s time to make the lights flicker!

Hue doesn’t like you to send more than 10 commands per second. Since I have two lights I’m going to update them once every 200 milliseconds (5 times per second).

This is easy to do, using a normal game-type timing loop. In the constructor of your Main.hx, add addEventListener(Event.ENTER_FRAME, onFrame);.

Then create the onFrame function. Here’s mine:

private function onFrame(e:Event) {
	var n = Lib.getTimer();
	if ( n - _last >= 200 ) { // Don't send an update unless 200ms have elapsed
		if ( change ) { // I watch for colour and brightness changes, and set a this variable when they occur
			var data = {
				"bri": Math.round(Math.min( 254, cp.l*255 )), // brightness is between 0 and 254
				"hue": Math.round(Math.min(65535, cp.h*65535)), // hue is between 0 and 65535
				"sat":Math.round(Math.min( 254, cp.s*255 )) // saturation is between 0 and 254
			}
			var ds = Json.stringify(data);
			for (n in Reflect.fields(lights)) { // Loop through all the lights. We want the light keys, hence the reflection
				hue.update(n, 'state', ds); // Update the light
			}
			change = false;
		} else if (cp.flicker) { // This is the flickering part! cp is my colour picker/user interface's name
			var hsl = flicker.flick( cp.h, cp.s, cp.l );  // The magic flicker function!
			var data = {
				"bri": hsl[2],
				"hue": hsl[0],
				"sat": hsl[1],
				"transitiontime": 2
			}
			var ds = Json.stringify(data);
			for (n in Reflect.fields(lights)) {
				hue.update(n, 'state', ds); // Update the light
			}
		}
		_last = n;
	}
}

For the flicker itself, I kept it pretty simple. I might eventually try a normally distributed pseudo-random variance on the hue.

class Flicker {
	public function new() {}
	public function flick(h:Float, s:Float, l:Float) {
		var b = clamp(0, vary(l*254, 0.2), 254); // Vary brightness by 20%
		var h = clamp(0, vary(h*65535, 0.05), 65535); // Vary hue by 5%
		var s = clamp(0, h*254, 254);
		return [h, s, b];
	}
	function vary(orig:Float, variance:Float):Int {
		var max = orig * variance;
		var half = max/2;
		return Math.round(orig + Math.random()*max - half);
	}
	function clamp( min, val, max ) {
		return Math.round( Math.max( min, Math.min( max, val ) ) );
	}
}

Posted on Programming

Gently flickering flame, part 2 (colours)

Now the we can talk to the lights, It’s time to deal with colours. We can’t just send RGB to the API, so I made a class that displays a rectangle of colours, and can convert from HSL to RGB for displaying each one. The HslToRgb function I stole from stackoverflow.

package;

import openfl.display.Sprite;
import openfl.events.MouseEvent;
import openfl.events.Event;


class Colours extends Sprite {
	private var _width:Int = 60; // Number of hues
	private var _height:Int = 30; // Number of saturations
	private var size:Int = 10;
	
	public var h:Float;
	public var s:Float;
	
	
	public function new() {
		super();
		var l = .9;
		for(x in 0..._width) {
			for(y in 0..._height) {
				var h = _height/2;
				var lo = (1-(y/_height/2)); // This is so it fades into white rather than grey
			
				var rgb = hslToRgb( x*1.00/_width, y*1.00/_height, lo);
				var c = rgb[0];
				c = (c<<8) + rgb[1];
				c = (c<<8) + rgb[2];
				graphics.beginFill(c);
				graphics.drawRect(x*size,y*size,size, size);
				graphics.endFill();
			}
		}
		addEventListener(MouseEvent.CLICK, onclick);
		addEventListener(MouseEvent.MOUSE_MOVE, onmove); // So it picks up finger drags
	}
	
	private function onmove(e:MouseEvent) {	
		if ( !e.buttonDown ) return;
		onclick(e);
	}
	private function onclick(e:MouseEvent) {
		h = Math.min(1, e.localX/size/_width);
		s = Math.min(1, e.localY/size/_height);
		var l = 0.5; // This is alpha
		var rgb = hslToRgb(h,s,l); 
		dispatchEvent( new Event(Event.CHANGE) );
	}
	
	/**
	* Converts an HSL color value to RGB. Conversion formula
	* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
	* Assumes h, s, and l are contained in the set [0, 1] and
	* returns r, g, and b in the set [0, 255].
	*
	* @param   Number  h       The hue
	* @param   Number  s       The saturation
	* @param   Number  l       The lightness
	* @return  Array           The RGB representation
	*/
	public function hslToRgb(h:Float, s:Float, l:Float):Array{
		var r:Float, g:Float, b:Float;
	
		if(s == 0){
			r = g = b = l; // achromatic
		}else{
			var hue2rgb = function hue2rgb(p:Float, q:Float, t:Float):Float{
				if(t < 0) t += 1;
				if(t > 1) t -= 1;
				if(t < 1/6) return (p + (q - p) * 6 * t);
				if(t < 1/2) return (q);
				if(t < 2/3) return (p + (q - p) * (2/3 - t) * 6);
				return (p);
			}
	
			var q:Float = l < 0.5 ? l * (1 + s) : l + s - l * s;
			var p:Float = 2 * l - q;
			r = hue2rgb(p, q, h + 1/3);
			g = hue2rgb(p, q, h);
			b = hue2rgb(p, q, h - 1/3);
		}
	
		return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
	}
}