[ian-reeds-games] Re: For scripters: Typescript

  • From: "Craig Brett" <dmarc-noreply@xxxxxxxxxxxxx> (Redacted sender "craigbrett17@xxxxxxx" for DMARC)
  • To: ian-reeds-games@xxxxxxxxxxxxx
  • Date: Sat, 28 Mar 2015 14:19:48 +0000

Ah, nice, that's what I use too.

I'll walk you through the steps I did to get my nice scripting project going. Sorry, long e-mail ahead.

I was going to say first, you need to go download Typescript, but, actually, I think VS comes with it by default now. I could be wrong. Depends what update you're on. In case you don't seem to have it, you can either go grab it from under the Extensions and Updates screen on the tools menu, or you can get it from this link (well it was my download link): https://visualstudiogallery.msdn.microsoft.com/2d42d8dc-e085-45eb-a30b-3f7d50d55304/file/151666/1/TypeScript_1.4_VS2013.exe?SRC=VSIDE

You may already be doing your JS stuff for TB in Visual Studio, but if not I'll tell you how I set up my JS project first.

First, I created a new HTML/Javascript project (this would have been either before TypeScript or perhaps I hadn't gotten into it as much). You could try a Typescript project as well, I think that appears under "Other languages" and then Typescript, then HTML/Typescript. That might be better, but if it doesn't work out, try the JS one and then just roll Typescript in, I'll explain how soon.

Next, you want to add all your existing JavaScript into the project. I added mine under a Craig Brett folder. Up to you how you structure that. Under that folder, I also added a Non-Script files folder and added them files too, so I could edit them in VS as well. I also added a Documentation folder into the project (at the same level as the Craig Brett folder) and put in my documentation .md file in there.

I also have a deployment.bat which I ran after build, but that bit really is just a convenience. I just made it pandoc the documentation, zip up the files and also copy the non-zipped versions into my Data for TB folder, so I could try them. I can show you that if you think it would help.

Now, this was where I was at until Thursday. Which worked quite nicely. But I thought I'd take it a step further and add TypeScript.

A bunch of that hard work was just turning the scripting docs (and in some cases things from the mailing list) into a Typescript definitions file. Which I've done now. Some of it was that I'd forgotten some of the TypeScript basics, like declaring a variable on the global namespace (shared and global, and some of the global functions like say).

I've attached the Typescript definitions file. If you save it and add it to your project, it will automagically be picked up in any other Typescript file. Typescript files have implicit referencing to each other. Which I think is pretty cool.

Next, you need to rename each of your .js files to .ts files. Once you done that, if you hit F4 to check the properties, you should notice that the build action for them now is "Typescript Compile". Which means they're ready to be compiled into JS. And that's most of it right there!

Now is the tricky bit. You've already converted everything into Typescript, but depending on your luck, you may have errors that now appear when you try and build. This is Typescripts compiler error checking for you. It'll suck right now, but it might help you out later. I can't think of too many common types that you'll get straight off the bat. A lot of mine were that I'd tried to make my own scripts type safe and had forgotten to do it everywhere, forgotten to export functions from modules that I wanted to call from other modules, little things like that. Hopefully, it'll compile straight away for you!

Now you'll have all the typescript goodies available to you. classes, interfaces, etc. I've not used too many of them other than modules and perhaps 1 class. But best of all, you'll notice you get intellisense for in-game types now. So if you type "shared", after you do a dot, all the things on the shared object come up. Same if you select map under that.

You can put types on your own variables if you like. You can also put types on the parameters coming into functions. One use of this I've made is to put the type of event coming in to an event subscription function. I'll give an example:

  export function override_tb_unit_ai(e: Ian.EventInfo.HandleUnitAI) {
        var unit = e.unit;
    }

This now means that e will have all the expected objects on it and will be of the right type.

I've organised the thing so that shared and global and the global functions (say, music, sound, randomSound) are available anywhere in your scripts. If you want to use a type, though, for marking a variable as a specific type, they're all under the Ian namespace.

var unit: Ian.Unit;

unit will now always be a TB unit. Maybe I could have called that module TB instead, I don't know. I put all the event type objects under Ian.EventInfo. So you can type your event handling functions with those if you wanted. Depends how type safe you want to go, really I believe non-typed things are any by default. So as little as possible should break.

Let me know if you have any questions. This is just the setup I have, but I thought I'd share it in case it helps other scripters out there.

Craig

On 28/03/2015 02:37, Victorious wrote:

Hi Craig

I’d love to see that file. How much work would it be to convert my existing scripts (about 700 lines of code or so) into typescript and use the modules feature and such? I’d be nice not to always have things in the global namespace. I have vs 2013 community edition installed.

Victorious

*From:*ian-reeds-games-bounce@xxxxxxxxxxxxx [mailto:ian-reeds-games-bounce@xxxxxxxxxxxxx] *On Behalf Of *Craig Brett (Redacted sender "craigbrett17@xxxxxxx" for DMARC)
*Sent:* Saturday, March 28, 2015 5:25 AM
*To:* ian-reeds-games@xxxxxxxxxxxxx
*Subject:* [ian-reeds-games] Re: For scripters: Typescript

I don't think I'll need to update the scripting documentation, I'm not changing anything from the ScriptApi side. It's just on my machine. Typescript spits out Javascript when it's compiled, so really its just a way of organising your Javascript, especially when working with applications or on a larger scale.

Welcome to the JavaScript world! You're doing great for your first foray into it.

If you wanted to try Typescript, you wouldn't feel too far from home with some Java experience. It supports classes, interfaces, static members, inheritance, etc. And importantly type safety, though it is optional type safety.

As for modules, they're what C# call namespaces and I think what Java call packages. It's been a while since I done them in Java. But they're essentially separate spaces where you can store exported types (classes, interfaces). With Typescript, you can also store functions directly in them, unlike C#/Java. Which is something I only learned recently.

So instead of having all your functions and variables littered around the global namespace, you can put them inside an object to keep it all organised.
So instead of: change_unit_type(stuff)
I now have: Transformation.change_unit_type(stuff)

It doesn't look much different, but I find it a little neater. Now I've done the initial work to get TS going for my scripts and making that type definitions file.

That file is practically done now, aside from possibly using some JSDoc to describe things in there or me possibly forgetting something. Let me know if you ever fancy giving TS a go and I'll send the file over.

On 27/03/2015 08:58, Victorious wrote:

    Hi Craig,

    That looks pretty interesting. I look forward to reading the
    definition files and the updated scripting documentation when
    that’s ready. I don’t actually have any prior experience with
    javascript (I wrote my first line of javascript only about 2 weeks
    or so ago) but have some C and java background, so I’ll have some
    catching up to do with typescript. What are modules? Are they like
    methods in classes?

    Victorious

    *From:*ian-reeds-games-bounce@xxxxxxxxxxxxx
    <mailto:ian-reeds-games-bounce@xxxxxxxxxxxxx>
    [mailto:ian-reeds-games-bounce@xxxxxxxxxxxxx] *On Behalf Of *Craig
    Brett (Redacted sender "craigbrett17@xxxxxxx"
    <mailto:craigbrett17@xxxxxxx> for DMARC)
    *Sent:* Friday, March 27, 2015 7:33 AM
    *To:* ian-reeds-games@xxxxxxxxxxxxx
    <mailto:ian-reeds-games@xxxxxxxxxxxxx>
    *Subject:* [ian-reeds-games] Re: For scripters: Typescript

    As a bit of early feedback. I spent a few hours doing this
    tonight. And it's all good!

    Took a while getting through the errors where I'd not been quite
    doing things properly (using var x in array), etc. And more
    commonly where I was setting up submodules.

    One positive upshot here is it should be highly unlikely, with the
    exception of the event handlers that have to be in the global
    namespace, to get a naming conflict, since all my stuff will be
    under the CB module, and various submodules within that.

    The real win for me though is being able to know that an event
    argument has a unit and saying so. Then when I use that variable,
    I can just get all the unit specific methods and fields in
    intellisense. And knowing I can't mistype things now always helps,
    too.

    It all seems to still work fine (after I worked out that the game
    needed event subscriptions to be in the global namespace). I won't
    be releasing this version in the wild until there's a new feature
    to show, though. Which should hopefully give me more testing time
    to make sure all my scripts work. Age Of Warlords, Demon Wars and
    some custom fiddling of my own should give me enough testing, and
    hey it will be fun too.

    On 26/03/2015 20:01, Craig Brett (Redacted sender
    craigbrett17@xxxxxxx <mailto:craigbrett17@xxxxxxx> for DMARC) wrote:

        Hi all,

        This is more something for either the scripters or those who
        are interested in code organisation and things of such a geeky
        nature. If you're not that way inclined, feel free to move on.

        I started work on some more scripts today, and realised I
        could try something new to help myself and help my code
        organization as well. Using TypeScript
        <http://www.typescriptlang.org/>. It's a superset of
        Javascript that compiles into Javascript itself. I use it at
        work and I know Ian is a big fan of it too. It offers a lot of
        cool things that native JS doesn't or does but with some more
        effort.

        So to aid me in this, I was going to make some definition
        files for some of the in game types, such as shared, unit,
        etc. And then wondered whether it was worth sharing them or
        not. And then decided some of it would be less useful
        depending on what you use to write your javascript.

        I used to use Notepad++, but now I use Visual Studio to handle
        all my scripting (as well as my .md files), for various
        reasons, not least code completion, etc. And also being able
        to autocomplete on referenced files outside of the current
        file. As well as a build step which I made which zips up my js
        files and docs and copies them into the right places.

        Typescript makes all this even better. One of the features is
        being able to make definition files of types outside your
        code, so in this case the TB back end script API that the game
        exposes to us. I'm happy to make the ones from the
        documentation and share them if you guys think they'd come in
        handy for you.

        Another cool thing Typescript easily lets you do is put things
        neatly into modules. You can already do this in native
        Javascript, of course, using self-executing functions and
        such, but it's much nicer in Typescript. I'll give the example
        I'm currently working on.

        module CraigBrett.AI {
            export function preferencialSkillOrder(event: any): void {

            }
        }

        And here's the slightly less attractive JavaScript it compiles to.

        var CraigBrett;
        (function (CraigBrett) {
            var AI;
            (function (AI) {
                function preferencialSkillOrder(event) {
                }
                AI.preferencialSkillOrder = preferencialSkillOrder;
            })(AI = CraigBrett.AI || (CraigBrett.AI = {}));
        })(CraigBrett || (CraigBrett = {}));

        It also supports static typing, so things have a real type,
        although you can get around this by using any (as I did
        above). Static typing is better if you can do it, as it helps
        you catch errors early. But I get that it's not for everyone.

        I'm not sure how my TB Typescripting for scripts will go, but
        I thought I'd share my experiment with the other scripters to
        see what you think.

        To add to this, what tools do you guys use to write your
        scripts? I'm just curious. And also wondering whether my TS
        stuff will help any of you.

        Regards,
        Craig


declare var shared: Ian.Shared;
declare var global: any;
declare var say: (message: string) => void;
declare var music: (musicFile: string) => void;
declare var sound: (soundFile: string) => void;
declare var randomSound: (soundName: string) => void;

declare module Ian {
    export interface Shared {
        Map: Map;
        ApplyEffect(effect: string, u: Unit, t: Tile): void;
        Calculate(num: string): number;
        GetPos(t: Tile): string;
        PrepareForIndexing(s: string): string;
        CreateUnit(type: string): Unit;
        GetStorage(name: string): any;
        GetUseableSkills(u: Unit): JSList<Skill>;
        TrySkill(s: Skill, curUnit: Unit, target: Unit, t: Tile): boolean;
        RemoveEffect(effect: string): void;
        GetEffectObjectByName(name: string): Effect;
        GetSkillObjectByName(name: string): Skill;
        GetItemObjectByName(name: string): Item;
        GetUnitObjectByName(name: string): Unit;
        AddSkillToUnit(u: Unit, skillName: string): void;
        RemoveSkillFromUnit(u: Unit, skillName: string): void;
        CreateItem(type: string): Item;
        MakeHash(): JSDictionary<string, Object>;
        MakeIntToObjectHash(): JSDictionary<number, Object>;
    }

    export interface Map {
        Width: number;
        Height: number;
        ReviewTile: Tile;
        AllTiles: JSList<Tile>;
        AllUnits: JSList<Unit>;
        CurrentRound: number;
        CurrentTurn: number;
        Teams: JSDictionary<number, Team>;
        CurrentTeam: Team;
        PlayerTeam: Team;
        ScriptFlags: JSDictionary<string, string>;

        GetTile(x: number, y: number): Tile;
        AddUnit(u: Unit, x: number, y: number): void;
        RemoveUnit(u: Unit): void;
        MoveUnit(u: Unit, x: number, y: number): void;
    }

    export interface EffecterBase {
        FriendlyName: string;
        IndexedName: string;
        AllFlags: JSDictionary<string, string>;
        ScriptFlags: JSDictionary<string, string>;
    }

    export interface Item extends EffecterBase {
        Quantity: number;
        EquipSlots: JSList<string>;
        EquipTypes: JSList<string>;
    }

    export interface Skill extends EffecterBase {

    }

    export interface Effect extends EffecterBase {
        RoundExpires: number;
        TurnExpires: number;
        Hidden: boolean;
    }

    export interface Unit {
        ID: number;
        Name: string;
        FriendlyType: string;
        IndexedType: string;
        Tile: Tile;
        Team: number;
        Points: JSDictionary<string, UnitPoint>;
        Effects: JSList<Effect>;
        Dead: boolean;
        AllFlags: JSDictionary<string, string>;
        ScriptFlags: JSDictionary<string, string>;
        Inventory: Inventory;
        
        GetName(): string;
        DoDamageTo(target: Unit, amount: string, pointName: string, damageType: 
string): void;
        DoDamageTo(target: Unit, pointDamages: JSDictionary<string, string>, 
damageType: string): void;
        DoRestoreTo(target: Unit, amount: string, pointName: string): void;
        DoRestoreTo(target: Unit, pointRestores: JSDictionary<string, string>): 
void;
    }

    export interface UnitPoint {
        Current: number;
        Max: number;
        LookupName: string;
    }

    export interface Tile {
        Selected: Unit;
        X: number;
        Y: number;
        Units: JSList<Unit>;
        Structures: JSList<Unit>;
        Effects: JSList<Effect>;
        Inventory: Inventory;

        CanHold(u: Unit): boolean;
    }

    export interface Team {
        Number: number;
        Name: string;
        Friends: JSList<number>;
        Enemies: JSList<number>;
    }

    export interface Inventory {
        Items: JSList<Item>;

        Add(item: Item): void;
    }

    export interface JSList<T> {
        Count: number;
        length: number;
        [n: number]: T;

        Add(item: T): void;
        push(item: T): void;
        Remove(item: T): void;
    }

    export interface JSDictionary<TKey, TValue> {
        keys: Array<TKey>;

        get(key: TKey): TValue;
        set(key: TKey, value: TValue): void;
    }

    module EventInfo {
        export interface OnMapKey {
            k: number;
            m: number;
        }

        export interface AfterMapLoad {
            map: Map;
        }

        export interface AfterUnitEvent {
            unit: Unit;
        }

        interface hasSourceTargetAndTile {
            source_unit: Unit;
            target_unit: Unit;
            target_tile: Tile;
        }

        export interface AfterPerformSkill extends hasSourceTargetAndTile {
            skill: Skill;
            success: boolean;
        }

        export interface AfterUseItem extends hasSourceTargetAndTile {
            item: Item;
            success: boolean;
        }

        export interface AfterEffectAppliedOrRemoved extends 
hasSourceTargetAndTile {
            effect: Effect;
        }

        export interface AfterEffectFizzled {
            target_unit: Unit;
            target_tile: Tile;
            effect: Effect;
        }

        export interface AfterTurnEnded {
            ending_units: JSList<number>;
        }

        export interface AfterTurnStarted {
            starting_units: JSList<number>;
        }

        export interface OverrideCausers {
            effecter: EffecterBase;
            skill: Skill;
            item: Item;
            effect: Effect;
            cur_unit: Unit;
            target_unit: Unit;
            target_tile: Tile;
        }

        export interface HandleTeamAI {
            units: JSList<Unit>;
            friends: JSList<Unit>;
            enemies: JSList<Unit>;
        }

        export interface HandleUnitAI {
            unit: Unit;
            friends: JSList<Unit>;
            enemies: JSList<Unit>;
        }

        export interface AfterItemEquippedOrUnequipped {
            unit: Unit;
            item: Item;
        }

        export interface BeforeOrAfterPointDamage {
            source_unit: Unit;
            target_unit: Unit;
            effecter: EffecterBase;
            point: UnitPoint;
            amount: number;
        }
    }
} 

Other related posts: