FlexFlip

An all HTML5 memory game written using Angular, GreenSock Animation Platform, jQuery, and C# with SignalR and MemoryCache

About FlexFlip

FlexFlip is a memory game which uses a Flexbox layout, hence the name. The basic game logic such as tracking of ...

  • Scores
  • Turns
  • Card flips

... is all managed via a single Angular controller. An Angular service, also a controller dependency, is what provides notification via SignalR. There is no multi or total connection push, just a push from player one to player two and vice-versa.

The FlexFlip menu is controlled via jQuery, GSAP, and the kendo.ui.ColorPicker widget. The menu allows for ...

  • Player assignment
  • Color assignment
  • Shuffling

Leverage Draggable's onClick event, and TweenLite.to or TweenMax.to, to allow both clicks and drags.
The connection to your hub can be used anywhere.
Angular services are your controller adapters.
Create a physical file for the SignalR generated proxy.
The ngTouch version of ngClick is better than ngClick by itself.
Draggable is powerful and feature rich, but succumbs to ghost click.

This snippet taken from FlexFlip is an example of an Angular controller residing within a module.


    var pushEventService = new Memory.pushService.Create();
    var flipManager = new Memory.cardTemplate.Create(@ViewBag.ImageNames);
    var app = angular.module('FlexFlip', ['ngTouch']);

    app.service('PushEventManager', ['$rootScope', pushEventService.PushEventManager]).
    controller('CardController', ['$scope', 'PushEventManager', '$timeout', flipManager.CardManager]);

                    

The flipManager.CardManager class is shown within the module below. CardManager is the single and only member revealed by Memory.cardTemplate.Create.


    var Memory = Memory || {};
    Memory.cardTemplate = Memory.cardTemplate || {};
    Memory.cardTemplate.Create = (function (images) {

        function cardManager($scope, $eventService, $timeout) {

            var self = this;
            this.cards = images;

            .
            .
            .

        }

        return {
            CardManager: cardManager
        };

    });

                    

I'm a big fan of using caching technologies for rapid prototyping. The Redis Cache, or Microsoft's ObjectCache, a concrete implementation of the System.Runtime.Caching.MemoryCache, are two very helpful cache stores for going from concept to code. Here's a simple use of ObjectCache that I used for managing SignalR player connection id's.

    public class CacheItemCollection
    {
        /// <summary>
        /// The concrete version of System.Runtime.Caching.MemoryCache
        /// </summary>
        private ObjectCache InMemoryCache { get; set; }
        /// <summary>
        /// The Default CacheItemPolicy in which the expiration of the cache item is set to 15 seconds.
        /// </summary>
        public CacheItemPolicy DefaultCacheItemPolicy { get; set; }
        /// <summary>
        /// CacheItemPolicy.AbsoluteExpiration is 30 minutes.
        /// </summary>
        private const int DefaultExpirationMinutes = 30;
        private static CacheItemCollection _instance;
        /// <summary>
        /// Lazy-loaded singleton instance.  MemoryCache is not a singleton, but you should create only a few or potentially only one MemoryCache instance and code that caches items should use those instances.
        /// Taken from: http://msdn.microsoft.com/en-us/library/system.runtime.caching.objectcache(v=vs.110).aspx
        /// </summary>
        public static CacheItemCollection Instance
        {
            get
            {
                _instance = _instance ?? new CacheItemCollection();
                return _instance;
            }
        }
        private const string LobbyRegion = "Lobby";
        private CacheItemCollection()
        {
            InMemoryCache = MemoryCache.Default;
        }
        /// <summary>
        /// Adds to the in-memory cache object, and expires the object in 15 minutes.  The caller of this method is responsible for the key to retrieve 
        /// the cached item.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public void AddToLobby<t>(string key, T value)
        {
            if ((!typeof(T).IsValueType) && (value == null))
            {
                return;
            }
            var itemToCache = new CacheItem(key, value, LobbyRegion);
            DefaultCacheItemPolicy = new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(DefaultExpirationMinutes), Priority = CacheItemPriority.Default };
            _instance.InMemoryCache.Set(key, itemToCache, DefaultCacheItemPolicy, null);
        }
        public T GetFromLobby<t>(string key, Func<object, t> converter)
        {
            T valueToReturn = default(T);
            if (_instance.InMemoryCache.Contains(key))
            {
                var tempCachedItem = _instance.InMemoryCache.Get(key, null) as CacheItem;
                valueToReturn = converter(tempCachedItem.Value);
            }
            return valueToReturn;
        }
        public void Replace<t>(string key, T value)
        {
            if ((!typeof(T).IsValueType) && (value == null))
            {
                return;
            }
            if (_instance.InMemoryCache.Contains(key))
            {
                _instance.InMemoryCache.Remove(key);
                var itemToCache = new CacheItem(key, value, LobbyRegion);
                DefaultCacheItemPolicy = new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(DefaultExpirationMinutes), Priority = CacheItemPriority.Default };
                _instance.InMemoryCache.Set(key, itemToCache, DefaultCacheItemPolicy, null);
            }
        }
    }
                    

Here's how I put the cache to work for me in the SignalR hub.

    public class UserEventNotificationHub : Hub
    {
        public void JoinLobby(string initiateGuid, string joinerGuid)
        {
            var connectionId = Context.ConnectionId;
            var playerInLobby = CacheItemCollection.Instance.GetFromLobby(initiateGuid,
                (cachedItem) => cachedItem as PlayerInLobby);
            if (playerInLobby == null)
            {
                CacheItemCollection.Instance.AddToLobby(initiateGuid,
                    new PlayerInLobby
                    {
                        InitiateGuid = initiateGuid,
                        InitiateName = Players.Instance.All.FirstOrDefault(p => p.Key == initiateGuid).Value,
                        RequestedJoiner = new Tuple<string, string>(connectionId, joinerGuid)
                    });
            }
            else
            {
                playerInLobby.RequestedJoiner = new Tuple<string, string>(connectionId, joinerGuid);
                CacheItemCollection.Instance.Replace(initiateGuid, playerInLobby);
            }
            Clients.Client(connectionId).joinedLobbyNotify(connectionId);
            var playerWaiting = CacheItemCollection.Instance.GetFromLobby(joinerGuid,
                       (cachedItem) => cachedItem as PlayerInLobby);
            if ((playerWaiting != null) && (playerWaiting.RequestedJoiner.Item2 == initiateGuid))
            {
                Clients.Client(connectionId).joinedGameNotify(connectionId, playerWaiting.RequestedJoiner.Item1);
                Clients.Client(playerWaiting.RequestedJoiner.Item1)
                    .joinedGameNotify(playerWaiting.RequestedJoiner.Item1, connectionId);
            }
        }
    }
                    

The act of flipping elements is not difficult to understand if you know a few facts. One is, you need two elements nested within a parent. The two elements can be divs, figures, or other elements of your choosing, but they should be nested within a parent element. I assigned the parent div in the example a class named .card. The two divs I chose to nest within the card div are assigned one class each, .front for the first, and .back for the second. A second fact to ponder is that this simple nested element structure works together with CSS3 attributes like transition, backface-visibility, and most importantly transform.

A third fact to be aware of; to initiate the flip upon the firing of some event, it is the card div that is assigned another class defined for the sole purpose of handling the flip action. The front and back elements, however, are the ones that undergo the rotation which occurs simultaneously. That is, the front panel rotates to the back along the axis of your choosing at the same time the back panel rotates to the front along the same axis. It is useful to understand is that the y-axis of rotation increases vertically downwards and the z-axis increases towards you, the viewer. The x-axis increases, as you would expect, to the right. A great explanation of the axes of rotation is here for now: http://www.w3.org/TR/css3-transforms/#transform-rendering

If you click on the demo link below and view source, note the CSS selectors that begin with .diagonal.  There, you will find the rotate3d(x, y, z, ndeg) transforms.  As for the example itself, think of the card game memory.  Simply tap a card to flip it, and tap it to return the card to its original state.