• Building an Expert System in JavaScript – Part 1

    An expert system emulates the knowledge and expertise of a human being by breaking it up to two parts: A knowledge base, and an inference engine. First developed in the 1960’s and later became widely used in the 1980’s, it is one of the first successful application of AI.

    In this series, we will create a program in JavaScript that will put you into the seat of an animal expert: allowing us to build a knowledge base of animal traits. Once that is built, the program will use the knowledge base to guess which animal most likely fits with those traits. If you want to skip ahead to what we will be making, check out the demo below.

    Click on the image for the demo

    Getting Started

    The goal of this part is to create a way for users to interact with the expert system. This will be accomplished through a series of dynamically generated dialog boxes containing form elements such as textboxes, checkboxes, and buttons, created using JavaScript.

    To get started let’s create a template for getting a user to interact with the Expert System. Create a new HTML file, using the code below as a template. You can run the html file in your favorite browser.

    HTML
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Expert System Part 1</title>
    <style>
    /* Insert CSS Style here*/
    </style>
    </head>
    <body>
    <script>
    // Inset JavaScript Here
    </script>
    </body>
    </html>

    CSS

    First thing we’ll do is use some CSS to create a style for our dialogue boxes. What we have below is something we generated with AI using prompts like blue buttons with rounded corners, grey backgrounds, scalable, and centered on the screen. Put the code below in between the <style> tags.

    CSS
    * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    }
    body {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background-color: #424242;
    font-family: Arial, sans-serif;
    }
    .form-container {
    background-color: white;
    padding: 30px;
    border-radius: 12px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    max-width: 500px;
    width: 100%;
    }
    .form-container h2 {
    margin-bottom: 20px;
    color: #333;
    }
    .form-group {
    margin-bottom: 20px;
    }
    label {
    display: block;
    margin-bottom: 8px;
    color: #555;
    font-weight: bold;
    white-space: pre-line;
    }
    input[type="text"],
    textarea {
    width: 100%;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 6px;
    font-family: Arial, sans-serif;
    }
    textarea {
    width: 100%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 6px;
    font-family: Arial, sans-serif;
    resize: vertical;
    }
    select {
    width: 100%;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 6px;
    font-family: Arial, sans-serif;
    background-color: white;
    }
    .checkbox-list {
    display: flex;
    flex-direction: column;
    gap: 20px;
    padding-top: 20px;
    }
    .checkbox-item {
    display: flex;
    align-items: center;
    }
    input[type="checkbox"] {
    width: 20px;
    height: 20px;
    margin-right: 10px;
    cursor: pointer;
    }
    .checkbox-item label {
    margin: 0;
    font-weight: normal;
    cursor: pointer;
    }
    button {
    padding: 12px 24px;
    margin-right: 10px;
    margin-top: 20px;
    border: none;
    border-radius: 6px;
    font-size: 16px;
    cursor: pointer;;
    transition: background-color 0.3s;
    }
    .btn-yes {
    background-color: #007bff;
    color: white;
    }
    .btn-yes:hover {
    background-color: #0056b3;
    }
    .btn-no {
    background-color: #6c757d;
    color: white;
    }
    .btn-no:hover {
    background-color: #545b62;
    }

    We wrap all of it in a form container using some HTML which we will later manipulate using JavaScript. Put the following HTML tags just above the <script> tags.

    HTML
    <div class="form-container" id="formContainer"></div>

    Dynamic Forms using JS

    To Dynamically create the dialogue boxes in the form container, we’ll use some JavaScript. We’ll call this class DynamicForm and it will be an object under the trf namespace. The template for what we’re doing is as follows:

    JavaScript
    /* Initialize TRF (The Robot Fish) namespace */
    this.trf = this.trf||{};
    //##############################################################################
    // DynamicForm.js
    //##############################################################################
    (function() {
    "use strict";
    function DynamicForm(elementId) {
    this.container = document.getElementById(elementId);
    };
    var p = DynamicForm.prototype;
    p.init = function() {
    //Initialize and clear the form
    };
    p.create = function(config) {
    // Dynamically create the form using the config
    };
    trf.DynamicForm = DynamicForm;
    }());

    For the constructor we simply pass in the elementId so we can reference it as the container later. We start with implementing the init function below.

    JavaScript
    p.init = function() {
    this.textboxes = new Map();
    this.checkboxes = new Map();
    this.selects = new Map();
    while (this.container.firstChild) {
    this.container.removeChild(this.container.firstChild);
    }
    };

    The purpose of the init function is to clear the form and get it ready to be populated. New Map objects are created to keep track of the form items for later referencing. To clear any items that might remain in the container, we loop through all the children and remove them. Copy the code below for the create function.

    JavaScript
    p.create = function(config) {
    this.init();
    // Dynamically create the form using the config
    const form = document.createElement('form');
    const title = document.createElement('h2');
    title.textContent = config.title;
    form.appendChild(title);
    // Create form fields
    for (const field of config.fields) {
    const group = document.createElement('div');
    group.className = 'form-group';
    if (field.type === 'label') {
    const label = document.createElement('label');
    label.textContent = field.text;
    group.appendChild(label);
    };
    if (field.type === 'textbox') {
    const input = document.createElement('input');
    input.type = 'text';
    input.placeholder = field.placeholder || '';
    input.name = field.name || 'input'
    input.addEventListener('keydown', function(event) {
    if (event.key === 'Enter') { event.preventDefault();}});
    group.appendChild(input);
    this.textboxes.set(input.name, input);
    };
    if (field.type === 'textarea') {
    const input = document.createElement('textarea');
    input.placeholder = field.placeholder || '';
    input.name = field.name || 'input'
    input.rows = 5;
    group.appendChild(input);
    this.textboxes.set(input.name, input);
    };
    if (field.type === 'checkboxList') {
    const checkboxContainer = document.createElement('div');
    checkboxContainer.className = 'checkbox-list';
    for (const option of field.options) {
    const checkboxItem = document.createElement('div');
    checkboxItem.className = 'checkbox-item';
    const checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    checkbox.value = option;
    const optionLabel = document.createElement('label');
    optionLabel.textContent = option;
    checkboxItem.appendChild(checkbox);
    checkboxItem.appendChild(optionLabel);
    checkboxContainer.appendChild(checkboxItem);
    this.checkboxes.set(checkbox.value,checkbox);
    };
    group.appendChild(checkboxContainer);
    };
    if (field.type === 'select') {
    const select = document.createElement('select');
    select.name = field.name || 'select';
    for (const option of field.options) {
    const opt = document.createElement('option');
    opt.value = option;
    opt.textContent = option.slice(4);
    select.appendChild(opt);
    };
    group.appendChild(select);
    this.selects.set(select.name, select);
    };
    if (field.type === 'buttons') {
    for (const option of field.options) {
    const button = document.createElement('button');
    button.type = 'button';
    button.className = 'btn-' + option.type;
    button.textContent = option.text;
    if (option.callback) {
    button.addEventListener('click', option.callback);
    }
    group.appendChild(button);
    };
    };
    form.appendChild(group);
    };
    this.container.appendChild(form);
    };

    A couple of things to note for the code above. First, is the use of the init function we created to clear the form. This clears the form at the beginning of each create call.

    this.init();

    After that, we create the form element and the first item we add to it is always a tittle.

    const form = document.createElement('form');
    const title = document.createElement('h2');
    title.textContent = config.title;
    form.appendChild(title);

    Then we loop through all the fields in the config to create the form items like checkboxes, buttons, and labels. We append them to a group, which then gets appended to the form, which then get appended to the container. The example below is what it looks like if we only do this for labels.

    for (const field of config.fields) {
    const group = document.createElement('div');
    group.className = 'form-group';
    if (field.type === 'label') {
    const label = document.createElement('label');
    label.textContent = field.text;
    group.appendChild(label);
    };
    form.appendChild(group);
    };
    this.container.appendChild(form);

    For field types like textbox, checkboxList, and select, we have to set it to the map objects that we created in the init function so that their values and settings can be accessed later. The example below sets the textbox to the textboxes map object using the field name as the key.

    if (field.type === 'textbox') {
    const input = document.createElement('input');
    input.type = 'text';
    input.placeholder = field.placeholder || '';
    input.name = field.name || 'input'
    input.addEventListener('keydown', function(event) {
    if (event.key === 'Enter') { event.preventDefault();}});
    group.appendChild(input);
    this.textboxes.set(input.name, input);
    };

    Lastly, we want buttons to do a callback when clicked. This is included in the options of each field because we would like to have multiple buttons in a group.

    if (field.type === 'buttons') {
    for (const option of field.options) {
    const button = document.createElement('button');
    button.type = 'button';
    button.className = 'btn-' + option.type;
    button.textContent = option.text;
    if (option.callback) {
    button.addEventListener('click', option.callback);
    }
    group.appendChild(button);
    };
    };

    To get a better understanding of how it all works, below is an example of a dialogue box with all of the different kinds of elements in the DynamicForm. Copy and paste it below the DynamicForm class that we just created.

    JavaScript
    //##############################################################################
    // Main Program
    //##############################################################################
    var dynamicForm = new trf.DynamicForm("formContainer");
    dynamicForm.create({
    title: 'Generic Title',
    fields: [
    { type: 'label', text:'This is a new label'},
    { type: 'textbox', placeholder: 'Placeholder Text' , name: 'textbox'},
    { type: 'textarea', placeholder: 'Placeholder Text Area' , name: 'textarea'},
    { type: 'checkboxList', options: ['Item1', 'Item2', 'Item3', 'Item4', 'Item5'] },
    { type: 'buttons', options: [
    {type: 'yes', text: 'Yes', callback: handleYes},
    {type: 'no', text: 'No', callback: function(){alert('No')}},
    {type: 'no', text: 'Cancel', callback: function(){alert('Cancel')}}
    ]}]
    });
    function handleYes(){
    alert(
    "This is what's in the textbox: " + dynamicForm.textboxes.get('textbox').value + "\n" +
    "This is what's in the textarea: " + dynamicForm.textboxes.get('textarea').value + "\n" +
    "This is the state of checkbox Item1: " + dynamicForm.checkboxes.get('Item1').checked
    );
    };

    This is what you get when you get when you run the code above.

    Click on image above to try it out.

    That’s it for all the GUI that we need to make for this project. You can download the complete project file for this part here. In the next parts, we will dive into programming the inference engine and the knowledge base. For now, have fun creating your new dialogue boxes using DynamicForm!

  • HTML5 Game Development Part 2 – Buttons

    Click on image for the Demo.

    In the previous part, we’ve set up a responsive game canvas that fits most screen configurations, now we are going to build on that and add a button that the user can interact with. The goal here is to make the button accessible to all sorts of inputs, mouse, touch, and keyboard.

    To begin, copy the helloWorld.html document we’ve made in the previous part and paste it into a new folder called buttons. Rename the helloWorld.html to buttons.html and change its <title> tag accordingly.

    Adding CSS

    Let’s start by defining the containers that house the buttons. We want the buttons to be an overlay over everything and appear on the corners of the screen. For that we will use fixed positioning with a high z-index and space between content. Copy the styles below and paste it into your CSS code.

    CSS
    .trf_BottomControl {
    position: fixed;
    bottom: 30px;
    left: 30px;
    right: 30px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    z-index: 1000;
    }

    Now we will add the style for the button itself. It is important to have user-select so that default mouse and touch actions do not interfere with the actual pressing of the button. We also created a ButtonActive class to change the button color when the button is pressed.

    CSS
    .trf_Button {
    width: 80px;
    height: 80px;
    background: rgba(100, 200, 255, 0.2);
    border: 2px solid rgba(100, 200, 255, 0.5);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 48px;
    font-family: Arial, Helvetica, sans-serif;
    color: white;
    -webkit-user-select: none; /* Safari */
    user-select: none;
    transform: scale(1);
    }
    .trf_ButtonActive {
    background: rgba(100, 150, 255, 0.9);
    transform: scale(0.95);
    }

    Finally, we will add the elements to the html body. Copy the code below to the section reserved for the bottom overlay. We will give it an id="trf-buttonBR" which will be used later.

    HTML
    <div class="trf_BottomControl">
    <div></div>
    <div class="trf_Button" id="trf-buttonBR">A</div>
    </div>

    Here’s the result:

    Creating the Button Object

    Now that we’ve created our button, we have to write some JavaScript code to interact with it. The best way to do that is to create an object for it so it can be reused in other places. Below is a basic template for a button object that we will be using. Copy this and paste it under our <script> tag before anything else.

    JavaScript
    /* Initialize TRF (The Robot Fish) namespace */
    this.trf = this.trf||{};
    (function() {
    "use strict";
    function Button() {
    // insert constructor here
    }
    var p = Button.prototype;
    createjs.EventDispatcher.initialize(p);
    // insert functions in here
    trf.Button = Button;
    }());

    In this template, we use the function wrapper to keep variables enclosed and to have the anonymous function run right away. "use strict"; is implemented to catch errors.

    (function() {
    "use strict";
    }());

    The trf is used as the identifier to prevent the namespace from getting too polluted.

    this.trf = this.trf||{};

    We also use the CreateJS EventDispatcher class to inject listener functionality so we can dispatch our own custom events.

    createjs.EventDispatcher.initialize(p);

    The var p is there to store the Button prototype so we can use it to add our functions later.

    var p = Button.prototype;

    Finally, we add the button object to the trf using this:

    trf.Button = Button;

    Building the Constructor and Functions

    The constructor of an object runs once when the object is instantiated. It is used to initialize and set object variables.

    When we create the new object, we pass in the Id for the button element, the button active style class, and the keyboard keys that will be tied to the button.

    Replace the constructor with the new one below.

    
    function Button(elementId, activeStyle, keys) {
        this.elementId = elementId;
        this.activeStyle = activeStyle;
        this.keys = keys;
    
        this.element = null;
        this.isPressed = false;
        this.activeControl = "";
    
        this.activate();
    };
    
    
    
    

    Now we add the functions that will make the Button work. Insert these function templates into the Button object.

    JavaScript
    p._onPressAction = function(){
    //private action on press
    };
    p._onReleaseAction = function(){
    //private action on release
    };
    p.activate = function(){
    //attach element, set visible, and add listeners
    };
    p.handleOnPressPointer = function(event){
    //handle pointer down event
    };
    p.handleOnReleasePointer = function(event){
    //handle pointer release event
    };
    p.handleOnPressKeyboard = function(event){
    //handle keyboard press event
    };
    p.handleOnReleaseKeyboard = function(event){
    //handle keyboard release event
    };
    p.destroy = function(){
    //remove element, set not visible, and remove listeners
    };

    Activate and Destroy Function

    We start by defining the activate() function. The purpose of this function is to set the html element the button is attached to, set the element to visible, and add the event listeners.

    The activate function is first called in the constructor but may be called later after the Button has been disabled by the destroy() function. Replace the activate function with the code below:

    JavaScript
    p.activate = function(){
    if (this.element === null) {
    this.element = document.getElementById(this.elementId);
    this.element.style.visibility = "visible";
    this.handleOnPressPointerBind = this.handleOnPressPointer.bind(this);
    this.handleOnReleasePointerBind = this.handleOnReleasePointer.bind(this);
    this.handleOnPressKeyboardBind = this.handleOnPressKeyboard.bind(this);
    this.handleOnReleaseKeyboardBind = this.handleOnReleaseKeyboard.bind(this);
    this.element.addEventListener("pointerdown", this.handleOnPressPointerBind);
    this.element.addEventListener("pointerleave", this.handleOnReleasePointerBind);
    this.element.addEventListener("pointerup", this.handleOnReleasePointerBind);
    document.addEventListener("keydown", this.handleOnPressKeyboardBind);
    document.addEventListener("keyup", this.handleOnReleaseKeyboardBind);
    };
    };

    In the code above, we first check for the this.element being null to prevent duplication. We then set the this.element using the this.elementId that was passed into the constructor. We also set the style visibility of the element to "visible" in the case it was destroyed earlier and was "hidden".

    if (this.element === null) {
    this.element = document.getElementById(this.elementId);
    this.element.style.visibility = "visible";

    Next, we use the bind() function to bind the this keyword to the handle functions. The handle functions get passed into the listener callback, so without this binding, it will not understand where this needs to be pointed at. We save the anonymous functions created by the bind() so we can safely remove it later in the destroy() function.

    this.handleOnPressPointerBind = this.handleOnPressPointer.bind(this);
    this.handleOnReleasePointerBind = this.handleOnReleasePointer.bind(this);
    this.handleOnPressKeyboardBind = this.handleOnPressKeyboard.bind(this);
    this.handleOnReleaseKeyboardBind = this.handleOnReleaseKeyboard.bind(this);

    Finaly, we add the event listeners. We used the pointer events because they encapsulate both mouse and touch interactions. For simplicity we used the release handle for the pointer on both the "pointerleave" and "pointerup" events.

    For the keyboard control we have to add the listener to the document so that it can be captured all the time. We will have to be careful in handling the event, so it doesn’t fire on non-related key presses.

    this.element.addEventListener("pointerdown", this.handleOnPressPointerBind);
    this.element.addEventListener("pointerleave", this.handleOnReleasePointerBind);
    this.element.addEventListener("pointerup", this.handleOnReleasePointerBind);
    document.addEventListener("keydown", this.handleOnPressKeyboardBind);
    document.addEventListener("keyup", this.handleOnReleaseKeyboardBind);

    Finaly, we implement the destroy() function accordingly. Replace the destroy() function with the code below.

    JavaScript
    p.destroy = function(){
    this.element.removeEventListener("pointerdown", this.handleOnPressPointerBind);
    this.element.removeEventListener("pointerleave", this.handleOnPressPointerBind);
    this.element.removeEventListener("pointerup", this.handleOnReleasePointerBind);
    document.removeEventListener("keydown", this.handleOnPressKeyboardBind);
    document.removeEventListener("keyup", this.handleOnReleaseKeyboardBind);
    this.element.style.visibility = "hidden";
    this.isPressed = false;
    this.element = null;
    };

    Handle Functions

    Now that we added the listeners, it is time to define the handle functions. We start with the pointer handlers. Replace the functions in the template with the following code.

    JavaScript
    p.handleOnPressPointer = function(event){
    if (this.isPressed === false){
    this.activeControl = "pointer";
    this._onPressAction();
    };
    };
    p.handleOnReleasePointer = function(event){
    if (this.isPressed === true){
    if (this.activeControl === "pointer"){
    this._onReleaseAction();
    };
    };
    };

    The first thing to do in the handlers is to check the this.isPressed state of the button. This is important to prevent interference with the keyboard actions. Only on the press events do we set the this.activeControl, which will lock in the control type until release.

    If all the checks pass, we then execute the private action functions. The underscore _ is just for notation and readability only. We will use these functions to add or remove the active style and also dispatch out own custom onPress and onRelease events which we will listen for later.

    Let’s define these private functions by replacing them in our template with this below:

    JavaScript
    p._onPressAction = function(){
    this.element.classList.add(this.activeStyle);
    this.isPressed = true;
    this.dispatchEvent("onPress");
    };
    p._onReleaseAction = function(){
    this.element.classList.remove(this.activeStyle);
    this.isPressed = false;
    this.dispatchEvent("onRelease");
    };

    Finaly, we finish this off by adding the keyboard listeners.

    JavaScript
    p.handleOnPressKeyboard = function(event){
    if (this.isPressed === false){
    if (this.keys.includes(event.key)) {
    this.activeControl = "keyboard";
    this._onPressAction();
    };
    };
    };
    p.handleOnReleaseKeyboard = function(event){
    if (this.isPressed === true){
    if (this.activeControl === "keyboard"){
    if (this.keys.includes(event.key)){
    this._onReleaseAction();
    };
    };
    };
    };

    It is very similar to the pointer handlers except; we also have the added check of the key press by looking for the event.key in the array this.keys that was passed in.

    if (this.keys.includes(event.key)){

    Finishing Up

    Now that our Button Class is ready, all we have to do is use it. Replace the entire main program with the following code:

    JavaScript
    var button, vel = 0;
    var stage,circle;
    function init() {
    stage = new createjs.Stage("trf-gameCanvas");
    circle = new createjs.Shape();
    button = new trf.Button("trf-buttonBR",
    "trf_ButtonActive",
    ["a", "A", " "]);
    circle.graphics
    .beginFill("DeepSkyBlue")
    .drawCircle(0, 0, 50);
    circle.x = 400;
    circle.y = 300;
    button.addEventListener("onPress", function(event){
    vel = -2;
    });
    button.addEventListener("onRelease", function(event){
    vel = 1;
    });
    createjs.Ticker.framerate = 60;
    createjs.Ticker.on("tick", tick);
    stage.addChild(circle);
    window.removeEventListener("load", init);
    }
    function tick(event) {
    circle.y += vel;
    stage.update(event);
    }
    window.addEventListener("load", init);

    A couple of things to note for the code above; first, we add a new button and velocity variable:

    var button, vel = 0;

    Next, we create a new button object with the element id, the active button CSS class and a list of buttons to bind to. We’ve chosen the a, A, and space keys.

    button = new trf.Button("trf-buttonBR",
    "trf_ButtonActive",
    ["a", "A", " "]);

    Then we add the listeners for our custom button events. Here we are setting the velocities.

    button.addEventListener("onPress", function(event){
    vel = -2;
    });
    button.addEventListener("onRelease", function(event){
    vel = 1;
    });

    After that, we add a ticker event that fires 60 frames per second.

    createjs.Ticker.framerate = 60;
    createjs.Ticker.on("tick", tick);

    Finaly, we add the tick event handler itself to update the circle position and draw the objects.

    function tick(event) {
    circle.y += vel;
    stage.update(event);
    }

    And we are done! We now have a button object that is responsive to mouse, touch and keyboard, and we can add as many of them as we want. In the next part, we will take what we have here and build a simple game.

    You can try the button demo here.

    You can download the project files here.

  • HTML5 Game Development Part 1 – helloWorld

    Click on image for the Demo.

    A long time ago, back in the early 2000s, the internet looked a lot different than it is today. Youtube and social media was only getting started, and the popular way to share and interactive content online was through this player you would download on the browser called Adobe Flash.

    Back then, I worked several co-ops developing games on the Adobe Flash platform and also making games of my own which I would upload to my Yahoo geocities webpage. Now that Adobe Flash has been fully replaced by HTML5, I want start developing games again, but on the HTML5 platform.

    One of the games I worked on as a Flash developer co-op.

    In this series, I will document how to develop a game from start to finish using HTML5, JavaScript, and some CSS. The intention is to only use the basics, so all you need is a code editor and an internet browser to start coding. Buckle up!

    Getting Started

    There are many ways to develop games on the HTML5 platform. You can use a game engine like Godot or Phaser, but I don’t like this approach because it adds extra complexity and overhead, and I want to just keep things simple. After doing some research, I decided the best thing to do is to write the entire game on a single html file. This way it can be loaded directly to the WordPress media library and makes it easy for anyone wants to host the game online.

    This is where the CreateJS libraries come in. Built by the people who previously worked with Adobe Flash, CreateJS is a set of modular libraries that make working with the HTML5 canvas a lot easier.

    To begin, all we need is a code editor and an internet browser. You can use whatever you’re familiar with; I am using Visual Studio Code as my code editor and Microsoft Edge as my internet browser. Let’s start by creating a folder called helloWorld and create a file inside it called helloWorld.html.

    Simple HTML page

    Now that we’ve created a helloWorld.html file, it’s time to set up a basic template for our game. Copy the basic template below into your helloWorld.html file.

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <title>HelloWorld — HTML5 Game</title> 
    
        <style>
            /* Insert CSS Style here*/
        </style>
    
    </head>
    <body>
        <div>
           <!-- Insert Top Controls Overlay Here-->
        </div>
    
        <div>
           <!-- Insert Game Canvas Here-->
        </div>
    
        <div>
           <!-- Insert Bottom Controls Overlay Here-->
        </div>
    
        <script src="https://code.createjs.com/1.0.0/createjs.min.js"></script>
    
        <script>
            /* Insert JavaScript Code Here*/
        </script>
    
    </body>
    </html>

    In this template, we made comments where you would put CSS and JavaScript code. You can test it by opening this html file on your browser, but when you do, all you get is a blank page.

    Adding CSS

    To spice it up, we need to add some CSS code to differentiate between the background and our canvas. Add the CSS code below to the <style> tags in our template:

    html,body{
        height:100%;
        margin:0;
        touch-action: none !important;
    }
    body{
        display:flex;
        align-items:center;
        justify-content:center; 
        background:rgb(46, 46, 46)
    }
    
    #trf-gameCanvas{
        width:100%; 
        height:auto; 
        border-radius:8px;
        background:white
    }

    This snippet of code will center all the items, make the background grey, and make the game canvas white. We will call the game canvas id="trf-gameCanvas", and it will fill the space with width: 100%; while maintaining the aspect ratio using height: auto;. All we need to do now is add the game canvas itself. Insert this code in the <div> tag for the game canvas:

    <canvas id="trf-gameCanvas" width="800" height="600" ></canvas>

    Here is where we set the aspect ratio for the game. In this case, we have set the width="800" and the height="600" making the aspect ratio the classic 4:3. This ends up being a good compromise on PC and modern mobile screens because although 16:9 will fill up the screen more completely, it doesn’t work as well especially when the orientation of the screen is changed. The aim here is make the game canvas work on all devices, so this setting will do the trick.

    Here’s what it looks like when you open the html file!

    Vertical Scaling Problem

    At its current state, the canvas is already responsive to changes in width of the screen, but you may have noticed it having problems scaling vertically. This is a problem on mobile devices where the user may try to do a landscape rotation, which will mess it all up.

    We can solve this problem by creating a media rule in the CSS:

    @media (max-height: 600px) and (aspect-ratio > 4/3) { 
        #trf-gameCanvas{
            height:75vh; 
            width:auto;
        }
    }

    Now when you open the html file, the canvas will scale both horizontally and vertically to the size of the window. The height:75vh; is important here because on mobile devices certain UI elements can cover up the canvas. Having the height only be 75% of the viewing height solves that problem.

    Drawing a circle

    Finaly, let’s finish off our Hello World program by drawing a blue circle with the CreateJS library. Using this library is quite simple, we begin by copying the code below and paste it under the JavaScript <script>.

    var stage,circle;
    
    function init() {
    
        stage = new createjs.Stage("trf-gameCanvas");
        circle = new createjs.Shape();
    
        circle.graphics.beginFill("DeepSkyBlue").drawCircle(0, 0, 50);
        circle.x = 400;
        circle.y = 300;
    
        stage.addChild(circle);
        stage.update();
    
        window.removeEventListener("load", init);
    }
    
    window.addEventListener("load", init);

    What we’re doing here is we created a shape called circle, use the graphics inside it to draw the blue circle, and then positioned it at the center of the game canvas coordinates. We then added the circle to the stage, which is the game canvas that we created earlier, and we used the stage.update() function to draw all elements on that stage. The init() function is created to run after the page is loaded.

    Hello World!

    We’ve done it. In this part, we created a responsive game canvas that we can view on any device, and as the artists that we are, we drew a perfect blue circle. Where things go from here is up to you, the possibilities are literally endless. In the next part, we will add some controls to manipulate our blue circle.

    To host this helloWorld.html file, all we have to do is upload it to the WordPress media library. We’ve done it, and you can see it in action here.

    You can download all the files here.

  • Expert System Part 1

    An expert system emulates the knowledge and expertise of a human being by breaking it up to two parts: A knowledge base, and an inference engine. First developed in the 1960’s and later became widely used in the 1980’s, it is one of the first successful application of AI.

    In this series, we will create a program in python that will put you into the seat of an animal expert: allowing us to build a knowledge base of animal traits. Once that is built, the program will use the knowledge base to guess which animal most likely fits with those traits.

    Getting Started

    If you haven’t already, set up your environment and install python. You can follow the instructions from our previous post on the ELIZA Chatbot.

    To begin, we will create a file called Dolittle.py after the famous animal whisperer doctor. Our first job is to define the knowledge base, and it must be persistent. We will use pickle to save and load the knowledge base to a Dolittle_data.dat file.

    import tkinter
    import pickle
    import tkinter as tk
    from tkinter import ttk, messagebox, simpledialog, IntVar
    
    datafile = 'Dolittle_data.dat'
    
    class KnowledgeBase:
        def __init__(self):
            self.animals = set()
            self.queries = []
    
    # Load existing knowledge base if available
    try:
        with open(datafile, 'rb') as f:
            unpickler = pickle.Unpickler(f)
            knowledgeBase = unpickler.load()
            print("Knowledge base loaded.")
    except FileNotFoundError:
        knowledgeBase = KnowledgeBase()
        print("No existing knowledge base found. Starting fresh.")
    
    # To Do: Add something here
    
    # Save knowledge base on exit
    with open(datafile, 'wb') as f:
        pickler = pickle.Pickler(f)
        pickler.dump(knowledgeBase)
        print("Knowledge base saved.")

    When you first run this code, it would look for the data file and fail to find one. It will then create a new knowledge base object and save it to the datafile. This is output you would see:

    No existing knowledge base found. Starting fresh.
    Knowledge base saved.
    
    Knowledge base loaded.
    Knowledge base saved.

    Adding Some GUI

    In the place of the To Do comment up above, we will write some GUI code to get started. The placeholder guessAnimal() function will be used later.

    def guessAnimal():
        pass
    
    # Create main application window
    root = tk.Tk()
    frame = ttk.Frame(root, padding="10")
    frame.grid()
    tk.Label(frame, text="Think of an animal").grid(column=0, row=0)
    goButton = tk.Button(frame, text="OK", command=guessAnimal)
    goButton.grid(column=1, row=0)
    
    #  Do the main loop
    root.mainloop()

    This will just create a prompt window to think of an animal until you close the window.

    Asking Questions

    In this program, we will simplify the query to a yes or no question. It’s up to the animal expert to determine what they are. Some examples could be: Does it fly? – yes or no, Does it eat meat? – yes or no, Does it have four limbs? – yes or no, etc.

    To do this we will create a Query class under the KnowledgeBase class. It will store a question and a list of animals that fit in the yeses category and a set of animals that fit in the nos category. We will also give the Query an ask function that will take a set of possible animals and match it with the yeses and nos.

    class Query:
        def __init__(self, question, yeses=set(), nos=set()):
            self.question = question
            self.yeses = yeses
            self.nos = nos
        
        def __str__(self):
            return'[Q: ' + self.question + ' Y:' + str(self.yeses) + ' N:' + str(self.nos) + ']'
        
        def ask(self, possible):
            answer = tk.messabox.askquestion(title = "Question", message = self.question)
            if answer == 'yes':
                return possible.intersection(self.yeses), 'yes'
            else:
                return possible.intersection(self.yeses), 'no'

    After the Query class we will create a function that checks if the existing questions is sufficient to find the new animal. If it returns False, then we know we need to ask another question.

    def canWeFind(newAnimal):
        possible = knowledgeBase.animals
        for query in knowledgeBase.queries:
            if newAnimal in query.yeses:
                possible = possible.intersection(query.yeses)
            if newAnimal in query.nos:
                possible = possible.intersection(query.nos)
        return len(possible) == 1

    That’s it for now. In the upcoming parts, we will finish off the GUI stuff, and the very important guessAnimal() function.

  • ELIZA Chatbot Part 3

    Now that we can match the patterns, we have to get the keywords from a user’s input and find it in the rulebook. Then use the keywords to answer the question using the patterns and responses from the rulebook. Let’s go!

    The Rulebook

    For the rulebook we will be using a python file derived from the original ELIZA program script. Below is a sample of what it looks like. You can download the entire file here.

    rulebook = { "sorry": (0,
    			[
    				[[0],
    					["please do not apologize"],
    					["apologies are not necessary"],
    					["what feelings do you have when you apologise"]]					
    			]),
    		"remember": (5,
    			[
    				[[0, "you", "remember", 0],
    					["do you think often of", 4],
    					["does thinking of", 4, "bring anything else to mind"],
    					["what else do you remember"],
    					["why do you remember", 4, "just now"],
    					["what in the present situation reminds you of", 4],
    					["what is the connection between me and", 4]],
    				[[0, "do", "i", "remember", 0],
    					["did you think I would forget", 5],
    					["why do you think I should recall", 5, "now"],
    					["what about", 5]]
    			]),
    

    To use the rulebook we will import the file using this code:

    from ELIZArules import conversions, rulebook

    Finding Keywords from Rulebook

    We will use the findKeywords() function to return a list of keywords and the phrases they appear in. There’s no fancy sorting here, it just looks through the rulebook for matching keywords and putting the highest priority items first.

    def findKeywords(text):
        for phrase in splitClauses(text):
            words = list(transform(splitWords(phrase)))
            maxPriority  = 0
            keywords = []
            for word in words:
                if word in rulebook:
                    priority = rulebook[word][0]
                    if priority > maxPriority:
                        maxPriority = priority
                        keywords.insert(0, word)
                    else:
                        keywords.append(word)
        if len(keywords) > 0:
            return keywords, words
        return [], []

    Answering the Question

    With all the keywords found, all we need is to match the input to a response pattern. We will use the compose() function to put the sentences together. If no matches are found, then a randomized default answer will be provided.

    defaultAnswers = [
        "I'm not sure I understand you fully.",
        "Please go on.",
        "What does that suggest to you?",
        "Do you feel strongly about discussing such things?",
        "That is quite interesting.",
        "Tell me more about that.", 
        "How does that make you feel?",
        "Why do you say that?"
    ]
    
    def compose(template, fields):
        result = ''
        for t in template:
            if type(t) == int:
                result += ' ' + fields[t-1]
            else:
                result += ' ' + t
        return result.strip()
    
    def answer(keywords, words):
        for keyword in keywords:
            for test in rulebook[keyword][1]:
                pattern = test[0]
                responses = test[1:]
                success, matches = matchPattern(pattern, words)
                if success:
                    response = random.choice(responses)
                    return compose(response, matches)
        return random.choice(defaultAnswers)

    Putting it all together

    To run program, we just have to call the functions we just created:

    while True:
        text = input('?')
        if len(text) == 0:
            break
    
        keywords, words = findKeywords(text)
        print(answer(keywords, words))

    That’s it, below is a sample conversation I’ve had with this chatbot. The entire code can be downloaded here.

    ?i feel sad, i want to play
    why do you want to play
    ?it make me feel happy
    of what does feeling happy remind you
    ?good times on the playground
    Please go on.
    ?playing on the swings, jumping up and down
    What does that suggest to you?
    ?i am a child at heart
    do you believe it is normal to be a child at heart
    ?i think it should be
    That is quite interesting.

    Conclusion

    As primitive as this 1966 chatbot is, it can in some instances create interesting conversations as an approximation of a therapist. Obviously, it can easily be fooled to giving out non-sensical responses, but it can be made better with a more complex rulebook, input scanning, and spell checks. What I learned from this exercise is that a chatbot does not necessarily need to understand language to function.

    The main advantage of this kind of chat bot is that it is very predictable and will never hallucinate answers. If the chatbot is given enough information in its rulebook, it can be an expert in a particular field. For example, querying banking information where a user is expected to ask certain types of questions.

    Overall, this has been a fun exercise and introduction to AI. I hope you learn something as well. Leave a comment below!