Pardon the mess, Play My Code is in beta!

READY TO PLAY?
CLICK TO LOG IN!

sign up - lost password

Quby Syntax

Play My Code uses our own language built specifically for writing games.

Why Quby?

Quby is our own dynamic, pure object-oriented language with some static analysis built specifically for programming games in the browser.

Fast!

Quby does not use an interpreter, instead compiling directly to JavaScript. This means in most cases it can be as fast as the equivalent JavaScript.

The Quby compiler also optimizes code for each browser. This allows it to take advantage of many browser specific features.

Secure!

Even in the latest browsers there are still annoying and malicious actions that can be performed using JavaScript. These include tracking users, generating popups and redirecting users to other websites.

Quby does not allow the developer to perform lots of these actions making Quby games safe to be shared and embedded across the internet.

Better Language!

JavaScript is missing lots of common features, such as classes. Quby includes these making programming games more straight forward.

Its syntax is based on Ruby, a language well known to be easy to learn. It also adds some static analysis, which languages such as JavaScript do not provide. This means that some bugs which are normally caught whilst playing a game can be caught much earlier when compiling.

Finally Quby has direct access to a HTML5 canvas with simple command provided with game development in mind. With JavaScript you need to construct these yourself, thus making the game development process in Quby much faster and simpler.

A bit more info ...

Dynamic

Variables can hold any value, and you can change its contents at any time.

    // a is now declared, and it is holding a number
    a = 5

    // now a is holding a string
    a = "blah"

Pure Object-Oriented?

Like Ruby, everything in Quby is an object and all objects have methods. This includes Numbers, Strings, Booleans and Arrays. One exception is null, which is still a primitive type (for performance reasons). Why? This leads to more consistent code because you can call a method on anything. It is also more readable, for example converting an angle to degrees is just...

    degrees = radians.toDegrees()

All of the other math functions also live in the Number class rather than in a static Math class (which most languages do)...

    num = 30
    num2 = 50
    num3 = 40

    highest = num.max( num2 ).max( num3 )

Static Analysis

Quby is a lot more strict then Ruby about what you can do, and code is also checked for a number of static errors at compile time.

Unlike Ruby, the following are considered to be compile time errors:

Why? This won't catch them all, but it's aimed to help to catch most of the bugs caused by typos at compile time (a mispelt variable, function or method).

Even if the code containing the typo is not reached then they will still result in a compile time error. On the plus side you can declare classes and functions after they have been used.

Variables

Variables are declared when they have their first value assigned to them.

    a = 4 // A variable is born

Variables defined outside a function can be accessed anywhere except inside any functions. Variables defined inside a function can be accessed anywhere within that function. The one exception to this is with blocks, variables which are defined only within the block are local to within the block. If you define a variable outside of a block, then it can be accessed and altered from within the block.

Globals

Global variables can be accessed anywhere, inside or outside of a function.

    $a = 0

    def f()
        $a = $a+1
    end

    f()

All global variables must have a value assigned to them somewhere in your code. Using a variable which is never assigned to is a compile time error.

Accessing a global which has not yet been assigned to will cause a runtime error, like:

    def f()
        b = $a // runtime error thrown here
    end
    f()

    $a = 0 // defined after it's used

Literals

All the common literals you would normally expect are in Quby. Strings can be declared with either double or single quotes...

    myString = "first string"
    myString = 'another string'

There is no difference in using single or double quotes, except that you don't have to escape the single or double quote character (depending on which your using).

Next there are symbols. They are defined using a colon at the beginning and can contain underscores, letters and whole numbers.

    mySymbol = :a_symbol
    anotherSymbol = :myCat

Symbols are very similar to strings, however you are guaranteed that two symbols will always be the same.

The intention is to use symbols where you want to pass in a description of what you want. Very much as an alternative to using constant identifiers.

For example if you have a collection of objects you could categorize them using symbols.

    ships = getUnits( :ships )
    
    def getUnits( type )
        if type == :ships
            // return your ships
        else if type == :bullets
            // return your bullets
        end
    end
    
    ships.each() do |ship|
        ship.turn( :right )
    end

Numbers can be declared as:

    IntegerNum = 20
    DecimalNum = 0.392

However you can also use underscores within numbers as seperators...

    million = 1_000_000
    nums    = 10_000.234_938

Numbers cannot start with an underscore (as then it would be a variable or method name).

You can also use hexadecimal numbers for integers using the '0x' prefix:

    red   = 0xff
    green = 0xaa
    blue  = 0xEE

Booleans...

    aBoolean    = true
    anotherBool = false

Finally Quby uses 'null' for no object.

    var = null

Note that unlike languages such as PHP or JavaScript there is no value for declaring 'undefined' (or similar) in Quby; use null for that instead.

Comments

Comments are in the C-style, supporting both single and multiline comments.

    // this is a comment
    /* this is a
       multi-line
       comment */

Statement Ending

Statements must all appear on separate lines, but if you want to have several statements on one line you can separate them with a semi-colon; like...

    a = 1
    b = 2

    // or 
    a = 1; b = 2

If Statements

    if a > b
        // run code
    else if b < a
        // run different code
    else
        // default statement
    end

Quby also supports some slight alternatives. You can also write 'elseif' instead of 'else if', and you can also the 'then' statement after the condition. For example...

    if a > b then
        // run code
    elseif b < a then
        // run different code
    else
        // default statement
    end

Loops

Where is the For loop?

Quby doesn't support a standard for or for-each loop. Instead you use call methods on the object your iterating on and define a 'block' that handles the loop contents. For example to iterate from 0 to n you can do...

    n.times() do |i|
        // do stuff with i here
    end

... and for iterating from a to b you can use 'upTo', 'downTo' or the 'to' function if you don't care about direction. For iterating over the elements of an array you use the 'each' method, like...

    values.each() do |val|
        // do stuff with val here
    end

Loop-While

Tests the condition at the beginning, and if it passes will then loop. This repeats until the condition is false.

    while a > b
        b = b + 1
    end

Loop-Until

You can also run the loop with an inverse of it's condition using 'until' instead of 'while'. This is the same as inverting the condition in your while loop, however it tends to be more readable.

    until a > b
        b = b + 1
    end

For example the above until loop is the same as...

    while ! ( a > b )
        b = b + 1
    end

Loop-While and Loop-Until

With while and until loops the condition is evaluated at the beginning, before the body of the loop is run.

Alternatively you can use loops which evaluate the condition at the end, after the body of the loop. This means the body of the loop is run at least once.

    loop
        a = a - 1
    end while a > b

There is a version for both while and until...

    loop
        a = a + 1
    end until a > b

Functions

Like the majority of languages Quby supports functions.

Declaring

Functions are declared with the 'def' keyword. They can only be declared outside of a statement; for example they cannot be inside of an if statement, class, while loop or another function.

    // declares the function 'f' with three parameters
    def f( parameter1, anotherParameter, yetAnotherParameter )
    end

Returning a value from a function is done using the 'return' keyword, like...

    def double( val )
        return val * 2
    end

Unlike Ruby, the last value is not automatically returned. Instead null is returned by default, unless you manually return a value.

Calling

Quite simple; function name and then the parameters within brackets. But all function calls must have parameters!

    f( a, 'blah', 90 )

You can call a declared function anywhere in your program, even before it's declaration.

Overloading

Unlike Ruby, Quby supports function overloading instead of optional parameters. This means you can define the same function, multiple times, with different numbers of parameters.

    def f()
    end

    def f( a )
    end

    def f( a, b, c )
    end

Anonymous Functions

Quby supports anonymous functions, otherwise known as lambdas, like in Ruby and JavaScript.

Anonymous functions are functions that can be held inside of a variable. This means you can pass them around, pass them into functions, and call them later. They are called 'anonymous functions' because they do not have a defined name.

This makes them very similar to blocks, and in practice they are interchangable. However you are advised to use anonymous functions when you plan to store the function, and pass it around to other parts of your game.

To create an anonymous function you use the 'def' keyword, like defining a function, but you omit the identifier (the name of the function). For example:

    // a doubling function
    double = def( x )
        return x * 2
    end
    
    // creating a function, and passing it into a function call
    foo(
            a, b,
            def(x, y) return x+y end
    )

Anonymous functions are of the Function class, and so have all of it's methods. This includes the two 'call' methods, which you can use to the function:

    getValue = def()
        return 42
    end
    
    // x is set to 42
    x = getValue.call()
    
    equation = def( a, b, c )
        return a + b*c
    end
    
    // y is set to '1 + 2*3', which is 7
    y = equation.call([ 1, 2, 3 ])

The array is required in the second use of call, as it receives an array of values.

You can also create functions directly using the constructor:

    double = def( x )
        return x * 2
    end
    
    double = new Function do |x|
        return x * 2
    end

However for technical reasons, the first will be marginally faster.

Blocks

Blocks are pieces of code that can be added onto the outside of a function call. Inside the function it can 'yield' which passes control and values to the block.

They are mainly used for certain types of loops and iteration. For example to add up the numbers from 0 to 10 (exclusively)...

    sum = 0
    10.times() do |i|
        sum = sum + i
    end

The block is everything from the 'do' to the 'end', and the block is being placed on the 'times()' method call. You can use the 'do' and 'end' keywords, or you can use braces...

    sum = 0
    10.times() { |i| sum = sum + i }

By convention you use braces for single line blocks and do-end for multi-line blocks (however you can use either for both multi-line and single-line blocks).

The variables between the bars (the |i| bit) are the parameters for the block where values being passed in will be stored.

Another example adding up the sum of an array...

    nums = [ 3, 9, 2, 0, -3, 98, 4 ]
    sum = 0
    nums.each() { |num| sum = sum + num }

Block Parameters

Blocks can be caught as a parameter of a function using the ampersand.

    def f( &block )
        myBlock = block
    end
    
    f() do 
        // some code
    end

The block parameter must be the last parameter, and only 1 is allowed.

If a block is attached to the function then it is held in the block parameter. If it is missing then the block parameter is null. Yielding will automatically call the block.

    def f()
        yield 500
    end
    
    f() do |n|
        // outputs 500
        console( n )
    end

Block parameters are instances of the Function class, so you can also call it manually using the 'call' method:

    def f( &block )
        yield 5
        
        args = [ 10 ]
        block.call( args )
    end
    
    f() do |n|
        // First run using the 'yield' with n equal to 5,
        // and then again, using the 'call', where n is equal to 10.
    end

This allows you to catch blocks and then call them later. Using this you can then use blocks as events.

    button = new Button()
    button.onClick() do
        fireRockets()
    end

The 'onEachFrame' function is an example of a function that stores the block to be executed later.

However you can also use Anonymous Functions for creating Function objects.

Arrays

As shown previously, Quby also supports Ruby like arrays. They allow you to efficiently map an integer index to an object.

Arrays are defined using the square brackets, and can contain initial values on creation.

    emptyArray = []
    nums = [ 3, 9, 2, 4 ]
    names = [ 'John', 'Alan', 'Brian', 'Sam' ]
    animals.add("cat")

Arrays can only use positive indexes, including 0, for setting values. They store a value from every index from 0 (inclusive) to its size (exclusive). When setting a value outside of an arrays size it will automatically pad itself to fill the empty elements with 'null'.

Values are retrieved using an index that returns the value stored there in the Array. If the index is out of bounds then 'null' is returned.

    names = [ 'John', 'Alan', 'Brian', 'Sam' ]
    
    name = names[ 0 ] // sets name to 'John'
    name = names[ 2 ] // sets name to 'Brian'
    name = names[ 9 ] // sets name to null

You can use negative indexes for retrieving values. These work back from the end of the array. So -1 refers to the last element, -2 to the second from last and so on.

    name = names[ -1 ] // sets name to 'Sam'
    name = names[ -2 ] // sets name to 'Brian'
    name = names[ -9 ] // sets name to null

Like all other objects Arrays also contain methods. These are documented in their documentation.

2D Arrays

Multi-dimensional arrays can defined by just placing an array within an array. For example:

    grid = [
            [ 1, 2, 3 ],
            [ 4, 5, 6 ],
            [ 7, 8, 9 ],
    ]

The above defines a 2-dimensional grid of numbers; or an array of arrays. You can then access them like you would access a standard array.

    number = grid[1][0] // returns 4

The first index pulls out the element stored at 1, which is the row '[ 4, 5, 6 ]'. The second index then returns the first item stored at 0, the value 4.

Hashes

Hashes are similar to arrays in that they store a mapping from a value to an object. In an array the index must be an integer; in a hash the index can be any object.

Hashes are defined using curly braces, and can contain mappings on creation.

    emptyHash = {}
    
    countries = {
            :UK     : new Country('UK'),
            :USA    : new Country('USA'),
            :France : new Country('France')
    }
    
    // a hash using different types of keys
    people = {
            42         : foo,
            92         : bar,
            "a string" : foobar
    }

Mapping a key to a value is achieved using a colon to denote the mapping (like in JavaScript and Ruby 1.9).

You can then use the key to get back the value mapped behind it. If the value is not found then null is returned.

    country = countries[ :France ]
    country = countries[ :China  ] // country is null

There is more information on the methods for the Hash class in their documentation.

Classes

Classes are defined using the 'class' keyword. Currently classes only support defining methods and constructors.

    // a class with no methods
    class Person
    end

    // creates an instance of Person
    person = new Person()

Instances of the class are then created using the 'new' keyword, like above.

Methods

Methods are defined just like a function, and all methods are fully public.

    class Person
        def name(name)
            @name = name
            @age = 0
        end

        def setAge(age)
            @age = age
        end
    end

    person = new Person( "Brian" )
    person.setAge( 20 )

Methods are called on an object using dot notation where a dot after an expression denotes calling a method on that object. All values (except null) are objects and so can have methods called on them. For example here a method is being called on the result of a times b:

    a = rand( -10, 10 )
    b = 20
    value = (a * b).abs()

Constructors

Any method called 'new' is a constructor, and this is called automatically when the class is created.

    // a class with no methods
    class Person
        def new()
            @name = "unknown"
        end

        def new(name)
            // a name field
            @name = name
        end
    end

    // creates an instance of Person
    person = new Person( "Brian" )

If no constructor is defined then a default constructor with no parameters is automatically defined.

Fields

The fields for a class are accessed using the @ symbol.

    // a class with no methods
    class Point
        def new()
            @x = 0
            @y = 0
        end

        def setLocation(x, y)
            @x = x
            @y = y
        end

        def move(x, y)
            @x = @x + x
            @x = @y + y
        end

        def getX()
            return @x
        end

        def getY()
            return @y
        end
    end

    point = new Point()
    point.setLocation( 10, 10 )
    point.move( 0, 5 ) // point is now at 10, 15

All fields are fully private to a class, they cannot be accessed from outside of it. So getters and setters need to be defined to allow you to alter the fields.

Inheritance

To help reuse classes you can have one class extend another. This is achieved by using the left arrow (the < symbol) for denoting that you 'extend' another class.

Note that you are only allowed to extend 1 class (like in Ruby, multiple inheritance is not allowed).

    class Health
        def new( hp )
            @hp = hp
        end

        def hurt( damage )
            @hp = @hp - damage
        end

        def getHP()
            return @hp
        end
    end
    
    class Player < Health
        def new()
            super( 10 )
        end
    end
    
    user = new Player()
    user.hurt( 4 )

In the above example the Player class receives all of the properties of the Health class. This allows you to call any method in Health class on instances of Player.

Method Overriding

If a super class has method with the same name and number of parameters as one in its super class, then this method will replace it.

    class Health
        def new( hp )
            @hp = hp
        end

        def hurt( damage )
            if @hp > 0
                @hp = @hp - damage
            
                if @hp <= 0
                    onDeath()
                end
            end
        end

        def getHP()
            return @hp
        end
        
        def onDeath()
            // do nothing
        end
    end
    
    class Player < Health
        def new()
            super( 10 )
        end
        
        def onDeath()
            // player death sequence
        end
    end

In the above example the Player class overrides the 'onDeath' method in Health class. When the Player's hp goes below 0, then 'onDeath' is called by the Health actor. This will call Player's 'onDeath' rather than its own because the Player class also has a definition for the 'onDeath'.

Calling Super Constructors

To initialize the super class you need to call it's constructor from your constructor. This is done by calling 'super' and passing in the parameters.

    class Cactus
        def new( numSpikes )
            @numSpikes = numSpikes
        end
        
        def new()
            @numSpikes = 1000
        end
    end
    
    class SuperCactus < Cactus
        dew new( numSpikes )
            super( numSpikes )
        end
        
        def new()
            super()
        end
    end

In the above example each constructor in the SuperCactus calls up to its super-class constructor.

You don't have to call super, this is optional, and you can also call super multiple times if you wish (although if you need to then you should probably re-think your code).

Inheritance and Fields

Fields are to the class they are defined in. They cannot be accessed by any sub-class.

For example...

    class Animal
        def new( name )
            @name = name
        end
        
        def getSpecies()
            return @name
        end
    end
    
    class Pet < Animal
        def new( species, name )
            super( species )
            
            @name = name
        end
        
        def getName()
            return @name
        end
    end
    
    myCat = new Pet( "Cat", "Ming" )
    myCat.getSpeciest() // returns 'Cat'
    myCat.getName() // returns 'Ming'

Setting a value to the 'name' in the Pet class has no effect on the 'name' field in its super Animal class.

The motivation behind this is because it is generally considered bad practice to access fields outside of your own class. Like in the example above; this also helps to avoid accidental conflicts between fields in different classes.

The Object class

If a class does not extend another class, then it will automatically extend the 'Object' class.

This means that anything defined in Object will be available in all classes, since everything is a sub-class of Object (either directly or in-directly).

    class Object
        def blah()
            return 'Chunky Bacon'
        end
    end
    
    class Pet
        def blah()
            return 'I am a Fox!'
        end
    end
    
    class Other
    end
    
    something = new Other()
    something.blah() // returns 'Chunky Bacon'
    
    5.blah()      // returns 'Chunky Bacon'
    "cats".blah() // returns 'Chunky Bacon'
    
    pet = new Pet()
    pet.blah() // returns 'I am a Fox!'

The method 'blah' is available on every object because it is defined in the Object class.