Sunday, July 13, 2014

Javascript Input Mask jQuery Plugin

Below is a jQuery plugin that can easily be adapted to mask or limit keystrokes while you type in a textbox. This script blocks invalid keystrokes from being processed. It seems to work reliably in all modern browsers except Chrome for Android (Chrome for Android doesn't seem to fully support the keydown/keypress events). The example below only allows a valid currency value.

Credit: the caret function was borrowed from Masked Input Plugin.

Usage

$(document).ready(function()
{
 $('#text').currencyMask();
});

Code

$.fn.currencyMask = function(options)
{
    options = options || {};
    options.valid = function(s) { return s.match(/^\-?\d{0,7}(:?\.\d{0,2})?$/); };
    return this.inputMask(options);
}

$.fn.inputMask = function(options)
{
 if (this.length == 0)
        return;
 
 return this.each(function()
 {
  $(this).keydown(function(e)
  {
   if (e.which == 8 || e.which == 46) //backspace or delete?
    filter(this, false, e);
  });
  
  $(this).keypress(function(e)
  {
   if (e.which != 0 && e.which != 8) //is it printable?
    filter(this, true, e);
  });
  
  $(this).bind('paste cut blur', function(e)
  {
   setTimeout($.proxy(function()
   {
    if (!options.valid($(this).val()))
     $(this).val('');
   }, $(this)), 0);
  });
 });
 
 function filter(element, press, e)
 {
  if (!e.ctrlKey && !e.altKey && !e.metaKey)
  {
   var caret = $(element).caret();
   var begin = caret.begin;
   var end = caret.end;
   var val = $(element).val();
   var src = String.fromCharCode(e.which);
   var result = val;
   
   if (!press && e.which == 8) //backspace
   {
    begin = begin != end ? begin : Math.max(begin - 1, 0);
    result = val.substring(0, begin) + val.substring(end);
   }
   else if (!press && e.which == 46) //delete
   {
    end = begin != end ? end : Math.min(end + 1, val.length);
    result = val.substring(0, begin) + val.substring(end);
   }
   else if (press) //printable
   {
    result = val.substring(0, begin) + src + val.substring(end);
   }
   
   if (!options.valid(result))
   {
    e.preventDefault();
   }
  }
 }
};

$.fn.caret = function()
{
    if (this.length == 0)
        return;
    
 var begin = 0, end = 0;
    var item = this[0];
    
 item.focus();
    if (item.setSelectionRange)
 {
        begin = item.selectionStart;
        end = item.selectionEnd;
    }
 else if (document.selection && document.selection.createRange)
 {
        var range = document.selection.createRange();
        begin = 0 - range.duplicate().moveStart('character', -100000);
        end = begin + range.text.length;
    }
    return { begin: begin, end: end };
}

Android Fragment Replace vs. Remove+Add and the Backstack

The Android documentation has this to say about FragmentTransaction.replace:

Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here.

I found the documentation to be slightly misleading because there is an important difference between replace vs. remove+add when the backstack is involved:

  1. If the backstack A->B->C is built using remove+add, then popped back to fragment A, then fragment B's onResume method will be triggered.
  2. If the backstack A->B->C is build using replace, then popped back to fragment A, then fragment B's onResume method will NOT be triggered.

Android Drawable Cache (Xamarin / Mono)

The code below is useful if you need to cache images in Android. Weak references are used to help protect against memory leaks.

Drawable Cache Class:
public class DrawableCache
{
 public Context Context { get; set; }
 Dictionary<object, WeakReference<Drawable.ConstantState>> _cache = new Dictionary<object, WeakReference<Drawable.ConstantState>>();

 public DrawableCache(Context context)
 {
  Context = context;
 }

 public void Put(object key, Drawable drawable)
 {
  _cache[key] = new WeakReference<Drawable.ConstantState>(drawable.GetConstantState(), false);
 }

 public Drawable Get(object key)
 {
  WeakReference<Drawable.ConstantState> value;
  if (_cache.TryGetValue(key, out value))
  {
   Drawable.ConstantState target;
   if (value.TryGetTarget(out target))
   {
    return target.NewDrawable(Context.Resources);
   }
   _cache.Remove(key);
  }
  return null;
 }
}
Optional Drawable Cache Key class
public class DrawableCacheKey
{
 public int Key { get; set; }
 public int Flags { get; set; }

 public DrawableCacheKey(int key, int flags)
 {
  Key = key;
  Flags = flags;
 }

 public override int GetHashCode()
 {
  int hash = 17;
  hash = hash * 23 + Key.GetHashCode();
  hash = hash * 23 + Flags.GetHashCode();
  return hash;
 }

 public override bool Equals(object obj)
 {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  var cast = obj as DrawableCacheKey;
  return cast != null && Key == cast.Key && Flags == cast.Flags;
 }
}

Sunday, January 5, 2014

Simple C# Artificial Neural Network

Artificial intelligence interests me, especially when modeled from or inspired by biology. Artificial neural networks are one such example:

"Artificial neural networks are computational models inspired by animals' central nervous systems (in particular the brain) that are capable of machine learning and pattern recognition. They are usually presented as systems of interconnected "neurons" that can compute values from inputs by feeding information through the network." - Wikipedia

I'm not going to try explain how artificial neural networks work. The topic is much too complex and there is already good documentation available elsewhere. If you are interested in learning more about artificial neural networks then I recommend the following article: Neural Network Back-Propagation Using C#. Most of the code in this post is based on the ideas presented in that article.

There are many different types of artificial neural networks. My implementation is a feed-forward, multi-layer, perceptron network which uses the back-propagation algorithm for learning. I believe this is one of the most common types of neural networks and is a good place to start for beginners. I'm still a beginner myself; I understand the basic concepts but the math is still slightly mysterious. Hopefully I can find time in the future to learn more about neural networks and artificial intelligence.

I tried to make the code as simple as possible. Code is below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NeuralNetwork
{
    class Program
    {
        static void Main(string[] args)
        {
            var network = new NeuralNetwork(2, 3, 1);

            Console.WriteLine("Training Network...");
            for (int i = 0; i < 100000; i++)
            {
                network.Train(0, 0);
                network.BackPropagate(1);

                network.Train(1, 0);
                network.BackPropagate(0);

                network.Train(0, 1);
                network.BackPropagate(0);

                network.Train(1, 1);
                network.BackPropagate(1);
            }

            double error;
            double output;

            output = network.Compute(0, 0)[0];
            error = network.CalculateError(1);
            Console.WriteLine("0 XOR 0 = " + output.ToString("F5") + ", Error = " + error.ToString("F5"));

            output = network.Compute(1, 0)[0];
            error = network.CalculateError(0);
            Console.WriteLine("1 XOR 0 = " + output.ToString("F5") + ", Error = " + error.ToString("F5"));

            output = network.Compute(0, 1)[0];
            error = network.CalculateError(0);
            Console.WriteLine("0 XOR 1 = " + output.ToString("F5") + ", Error = " + error.ToString("F5"));

            output = network.Compute(1, 1)[0];
            error = network.CalculateError(1);
            Console.WriteLine("1 XOR 1 = " + output.ToString("F5") + ", Error = " + error.ToString("F5"));
        }
    }

    public class NeuralNetwork
    {
        public double LearnRate { get; set; }
        public double Momentum { get; set; }
        public List<Neuron> InputLayer { get; set; }
        public List<Neuron> HiddenLayer { get; set; }
        public List<Neuron> OutputLayer { get; set; }
        static Random random = new Random();

        public NeuralNetwork(int inputSize, int hiddenSize, int outputSize)
        {
            LearnRate = .9;
            Momentum = .04;
            InputLayer = new List<Neuron>();
            HiddenLayer = new List<Neuron>();
            OutputLayer = new List<Neuron>();

            for (int i = 0; i < inputSize; i++)
                InputLayer.Add(new Neuron());

            for (int i = 0; i < hiddenSize; i++)
                HiddenLayer.Add(new Neuron(InputLayer));

            for (int i = 0; i < outputSize; i++)
                OutputLayer.Add(new Neuron(HiddenLayer));
        }

        public void Train(params double[] inputs)
        {
            int i = 0;
            InputLayer.ForEach(a => a.Value = inputs[i++]);
            HiddenLayer.ForEach(a => a.CalculateValue());
            OutputLayer.ForEach(a => a.CalculateValue());
        }

        public double[] Compute(params double[] inputs)
        {
            Train(inputs);
            return OutputLayer.Select(a => a.Value).ToArray();
        }

        public double CalculateError(params double[] targets)
        {
            int i = 0;
            return OutputLayer.Sum(a => Math.Abs(a.CalculateError(targets[i++])));
        }

        public void BackPropagate(params double[] targets)
        {
            int i = 0;
            OutputLayer.ForEach(a => a.CalculateGradient(targets[i++]));
            HiddenLayer.ForEach(a => a.CalculateGradient());
            HiddenLayer.ForEach(a => a.UpdateWeights(LearnRate, Momentum));
            OutputLayer.ForEach(a => a.UpdateWeights(LearnRate, Momentum));
        }

        public static double NextRandom()
        {
            return 2 * random.NextDouble() - 1;
        }

        public static double SigmoidFunction(double x)
        {
            if (x < -45.0) return 0.0;
            else if (x > 45.0) return 1.0;
            return 1.0 / (1.0 + Math.Exp(-x));
        }

        public static double SigmoidDerivative(double f)
        {
            return f * (1 - f);
        }
    }

    public class Neuron
    {
        public List<Synapse> InputSynapses { get; set; }
        public List<Synapse> OutputSynapses { get; set; }
        public double Bias { get; set; }
        public double BiasDelta { get; set; }
        public double Gradient { get; set; }
        public double Value { get; set; }

        public Neuron()
        {
            InputSynapses = new List<Synapse>();
            OutputSynapses = new List<Synapse>();
            Bias = NeuralNetwork.NextRandom();
        }

        public Neuron(List<Neuron> inputNeurons) : this()
        {
            foreach (var inputNeuron in inputNeurons)
            {
                var synapse = new Synapse(inputNeuron, this);
                inputNeuron.OutputSynapses.Add(synapse);
                InputSynapses.Add(synapse);
            }
        }

        public virtual double CalculateValue()
        {
            return Value = NeuralNetwork.SigmoidFunction(InputSynapses.Sum(a => a.Weight * a.InputNeuron.Value) + Bias);
        }

        public virtual double CalculateDerivative()
        {
            return NeuralNetwork.SigmoidDerivative(Value);
        }

        public double CalculateError(double target)
        {
            return target - Value;
        }

        public double CalculateGradient(double target)
        {
            return Gradient = CalculateError(target) * CalculateDerivative();
        }

        public double CalculateGradient()
        {
            return Gradient = OutputSynapses.Sum(a => a.OutputNeuron.Gradient * a.Weight) * CalculateDerivative();
        }

        public void UpdateWeights(double learnRate, double momentum)
        {
            var prevDelta = BiasDelta;
            BiasDelta = learnRate * Gradient; // * 1
            Bias += BiasDelta + momentum * prevDelta;

            foreach (var s in InputSynapses)
            {
                prevDelta = s.WeightDelta;
                s.WeightDelta = learnRate * Gradient * s.InputNeuron.Value;
                s.Weight += s.WeightDelta + momentum * prevDelta;
            }
        }
    }

    public class Synapse
    {
        public Neuron InputNeuron { get; set; }
        public Neuron OutputNeuron { get; set; }
        public double Weight { get; set; }
        public double WeightDelta { get; set; }
        
        public Synapse(Neuron inputNeuron, Neuron outputNeuron)
        {
            InputNeuron = inputNeuron;
            OutputNeuron = outputNeuron;
            Weight = NeuralNetwork.NextRandom();
        }
    }
}

Saturday, October 26, 2013

UDP Proxy / Minecraft PE Proxy

Here is a UDP Proxy implemented in Java that can be used for Minecraft Pocket Edition. Inspired by the Node.js implementation. Runs great on Raspberry Pi.
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class UdpProxy
{
 public static void main(String[] args) throws Exception
 {
  boolean testMode = args.length == 0;
  String serverHost;
  int serverPort;
  int proxyPort;

  if (args.length != 3 && !testMode)
  {
   System.out.println("Use: java UdpProxy <serverhost> <serverport> <proxyport>");
   return;
  }

  if (!testMode)
  {
   serverHost = args[0];
   serverPort = Integer.parseInt(args[1]);
   proxyPort = Integer.parseInt(args[2]);
  }
  else
  {
   serverHost = "192.168.1.114";
   serverPort = 19132;
   proxyPort = 19133;
  }

  new UdpProxy(serverHost, serverPort, proxyPort).runServer();
 }
 
 static final int CONNECTION_TIMEOUT = 60000;
 HashMap<InetSocketAddress, ChannelInfo> channelMap = new HashMap<InetSocketAddress, ChannelInfo>();
 InetSocketAddress serverAddress;
 int proxyPort;

 public UdpProxy(String serverHost, int serverPort, int proxyPort) throws Exception
 {
  InetAddress resolvedAddress = InetAddress.getByName(serverHost);
  this.serverAddress = new InetSocketAddress(resolvedAddress, serverPort);
  this.proxyPort = proxyPort;
 }

 public void runServer() throws Exception
 {
  System.out.println("Listening on " + proxyPort + ", forwarding to " + serverAddress);

  ByteBuffer buff = ByteBuffer.allocate(1024*1024); //this is probably more than necessary
  Selector selector = Selector.open();
  DatagramChannel proxyChannel = addChannel(selector, proxyPort);
  long connectionTestTime = System.currentTimeMillis();
  
  while (true)
  {
   try
   {
    selector.select(10000); //selector.selectNow();
    Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
    
    while (keyIterator.hasNext())
    {
     SelectionKey key = keyIterator.next();
     keyIterator.remove();
     
     if (key.isReadable())
     {
      DatagramChannel currentChannel = (DatagramChannel) key.channel();
      InetSocketAddress localAddress = (InetSocketAddress) currentChannel.socket().getLocalSocketAddress();
      buff.clear();
      InetSocketAddress remoteAddress = (InetSocketAddress) currentChannel.receive(buff);
      buff.flip();
      
      if (!fromServer(remoteAddress))
      {
       ChannelInfo info = channelMap.get(remoteAddress);
       if (info == null)
       {
        DatagramChannel tempChannel = addChannel(selector, serverAddress);
        InetSocketAddress tempAddress = (InetSocketAddress) tempChannel.socket().getLocalSocketAddress();
        info = new ChannelInfo(tempChannel, remoteAddress);
        channelMap.put(remoteAddress, info);
        System.out.println("Added key = " + remoteAddress.toString());
        channelMap.put(tempAddress, info);
        System.out.println("Added key = " + tempAddress.toString());
       }
       
       info.rxTime = System.currentTimeMillis();
       info.channel.send(buff, serverAddress);
      }
      else
      {
       ChannelInfo info = channelMap.get(localAddress);
       if (info != null)
       {
        proxyChannel.send(buff, info.remoteAddress);
       }
      }
     }
    }
    
    //Test & remove old connections
    if (System.currentTimeMillis() - connectionTestTime >= CONNECTION_TIMEOUT)
    {
     connectionTestTime = System.currentTimeMillis();
     Iterator<Map.Entry<InetSocketAddress, ChannelInfo>> entryIterator = channelMap.entrySet().iterator();
     while (entryIterator.hasNext())
     {
      Map.Entry<InetSocketAddress, ChannelInfo> entry = entryIterator.next();
      InetSocketAddress address = entry.getKey();
      ChannelInfo info = entry.getValue();
      if (connectionTestTime - info.rxTime >= CONNECTION_TIMEOUT)
      {
       info.channel.close();
       entryIterator.remove();
       System.out.println("Removed key = " + address.toString());
      }
     }
    }
   }
   catch (Exception ex)
   {
    ex.printStackTrace();
   }
  }
 }
 
 public boolean fromServer(InetSocketAddress address)
 {
  return address.getAddress().equals(serverAddress.getAddress());
 }

 public DatagramChannel addChannel(Selector selector, int bindPort) throws Exception
 {
  DatagramChannel channel = DatagramChannel.open();
  channel.configureBlocking(false);
  channel.socket().bind(new InetSocketAddress(bindPort));
  channel.register(selector, SelectionKey.OP_READ);
  return channel;
 }

 public DatagramChannel addChannel(Selector selector, InetSocketAddress address) throws Exception
 {
  DatagramChannel channel = DatagramChannel.open();
  channel.configureBlocking(false);
  channel.connect(address);
  channel.register(selector, SelectionKey.OP_READ);
  return channel;
 }

 public static class ChannelInfo
 {
  public ChannelInfo()
  {
  }

  public ChannelInfo(DatagramChannel channel, InetSocketAddress remoteAddress)
  {
   this.channel = channel;
   this.localAddress = (InetSocketAddress) channel.socket().getLocalSocketAddress();
   this.remoteAddress = remoteAddress;
  }

  DatagramChannel channel;
  InetSocketAddress localAddress;
  InetSocketAddress remoteAddress;
  long rxTime;
 }
}

Saturday, March 23, 2013

Show / Hide Android Soft Keyboard

Here is a helpful utility class for showing and hiding the Android soft keyboard. The code is written in C# using Mono for Android but should be easy to convert to Java. Similar behavior can be accomplished using Window.SetSoftInputMode but unfortunately SetSoftInputMode doesn't always play well with a tab / fragment architecture.

This code is my attempt to simplify the process of manually showing and hiding of the soft keyboard, which is much more complicated than it has a right to be (I blame vague documentation). My earlier attempts to accomplish this were laden with frustrating bugs and strange side-effects. Now, (hopefully) all those bugs and side-effects have been corrected.

By default there is a 200 millisecond delay on the ShowSoftKeyboard method; this is to accommodate any fragment transition animations that may occur. Showing the keyboard while running an animation can result in poor performance so the workaround is to wait until the animation is finished. If you are not using fragment transition animations then the delay can be eliminated.

*Code has been tested on a handful of real devices as well as a wide range of emulator versions (2.2+).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Views.InputMethods;
using Android.InputMethodServices;
using Android.Util;

namespace Mobile.Util
{
    public static class ActivityExtensions
    {
        public static void HideSoftKeyboard(this Activity activity)
        {
            new Handler().Post(delegate
            {
                var view = activity.CurrentFocus;
                if (view != null)
                {
                    InputMethodManager manager = (InputMethodManager)activity.GetSystemService(Context.InputMethodService);
                    manager.HideSoftInputFromWindow(view.WindowToken, 0);
                }
            });
        }

        public static void ShowSoftKeyboard(this Activity activity, View view = null, int delay = 200)
        {
            new Handler().PostDelayed(delegate
            {
                view = view ?? activity.CurrentFocus;
                if (view != null)
                {
                    if (view.HasFocus)
                        view.ClearFocus(); //bug fix for older versions of android

                    view.RequestFocus();
                    InputMethodManager manager = (InputMethodManager)activity.GetSystemService(Context.InputMethodService);
                    manager.ShowSoftInput(view, 0);
                }
            }, delay);
        }
    }

    public static class DialogExtensions
    {
        public static void HideSoftKeyboard(this Dialog dialog)
        {
            new Handler().Post(delegate
            {
                var view = dialog.CurrentFocus;
                if (view != null)
                {
                    InputMethodManager manager = (InputMethodManager)dialog.Context.GetSystemService(Context.InputMethodService);
                    manager.HideSoftInputFromWindow(view.WindowToken, 0);
                }
            });
        }

        public static void ShowSoftKeyboard(this Dialog dialog, View view = null, int delay = 200)
        {
            new Handler().PostDelayed(delegate
            {
                view = view ?? dialog.CurrentFocus;
                if (view != null)
                {
                    if (view.HasFocus)
                        view.ClearFocus(); //bug fix for older versions of android

                    view.RequestFocus();
                    InputMethodManager manager = (InputMethodManager)dialog.Context.GetSystemService(Context.InputMethodService);
                    manager.ShowSoftInput(view, 0);
                }
            }, delay);
        }     
    }
}

Friday, February 22, 2013

Raspberry Pi

My Raspberry Pi arrived in the mail today. These things are great and only cost about $35-$40. Lots of fun.

Raspberry Pi Model B