Building a Die-Roll Parser with C# and Irony

9 09 2015

Introduction

As part of a recent project to build a system for online, play-by-post RPGs, I have been working on a system to handle rolling various amounts and types of dice. The rolling itself is relatively simple, as I’m just using the built in Random class. However, I also needed the system to pull the details of what to roll from a string provided by the user. Moreover, the user could optionally employ a variety of basic mathematical operations.

To do this I needed to develop a lexer/parser that could handle basic math as well as a custom operator, D, to signify rolling dice. The D operator would take the first operand as the number of dice and the second as the number of sides on each die.

A few examples:

  1D6         →  1-6
  2D7         →  2-14
  5+1D6       →  6-11
  (1+2)D(3*4) →  3D12 
                →  3-36

Installing Irony via NuGet

While I could have built the lexer/parser from scratch, I figured I would save some time by not reinventing the wheel. I settled on the excellent Irony library to build my “language” with. The Irony library is available through Microsoft’s NuGet package manager so I installed it using the tools built into my IDE (Visual Studio 2015). You can do this by executing the following command in the NuGet Package Manager console.

PM> Install-Package Irony

Building the Language Grammar

Note: This is an incredibly “dumbed down” overview of the incredibly complex area of language definition; for more details I suggest reading up on BNF, or Backus-Naur Form.

Irony makes setting up the grammar for a language quite easy. The elements that make up the language are split into two categories, terminals and non-terminals. These pieces are used to break the expression into a tree structure. Terminals are the pieces which can be found at the ends of branches, while everything else is non-terminal.

For example, 1 + 2 – 3 * 4 / 5 becomes:

       -
   +       /
  1 2    5   *
            3 4

As you can see from the above example, the only terminal we have is constant numerical values. Setting up number literals is particularly easy in Irony:

public class ExpressionGrammar : Grammar {
    public ExpressionGrammar() : base(caseSensitive: false) {
        var number = new NumberLiteral("number");
        number.DefaultIntTypes = new TypeCode[] { TypeCode.Int32 };
        number.Options = NumberOptions.AllowSign | NumberOptions.AllowLetterAfter;

        var rollOperatorTerminal = ToTerm("d");
        rollOperatorTerminal.AllowAlphaAfterKeyword = true;

Here I tell Irony we have a number literal, I want to call it “number,” it will be internally represented as a 32 bit integer value and can have preceding sign and be followed by a letter. The following letter becomes important later, when we implement the die roll operator. the roll operator terminal is just used later for the roll operator so it can figure out where to break things apart.

We have rather more non-terminals. The top level is an expression, which is either a term, binary expression, or roll expression. A term is either a number literal or a parenthetical epression. Binary, roll, and parenthetical expressions are all exactly what they sound like.

        var expression = new NonTerminal("Expression");
        var term = new NonTerminal("Term");
        var parentheticalExpression = 
            new NonTerminal("ParentheticalExpression");
        var binaryExpression =
            new NonTerminal("BinaryExpression", typeof(BinaryOperationNode));
        var binaryOperator = new NonTerminal("BinaryOperator", "operator");
        var rollExpression =
            new NonTerminal("RollExpression", typeof(RollOperationNode));
        var rollOperator = new NonTerminal("RollOperator", "operator");

You may have noticed that the binary and roll expressions have a type specified. This type is used to evaluate those nodes in the tree when we get to that portion of the process.

Once we’ve defined what all the non-terminals are we need to define what they look like. We do this using the | and + operators, which have been overloaded by Irony to simplify this process. The | operator is used for OR, as in either of the terms provided can be matched, while the + operator is used for concatination, as in the first term followed by the second matches.

        expression.Rule = term | binaryExpression | rollExpression;
        term.Rule = number | parentheticalExpression;
        parentheticalExpression.Rule = "(" + expression + ")";
        binaryExpression.Rule = expression + binaryOperator + expression;
        binaryOperator.Rule = ToTerm("+") | "-" | "*" | "/";
        rollExpression.Rule = expression + rollOperator + expression;
        rollOperator.Rule = rollOperatorTerminal;
    
        Root = expression;

The first block of code here sets the pattern matching for each non-terminal. The last line tells the system what the root term should be when the abstract syntax tree (AST) is built. Normally you have multiple expressions/commands in a language, but I only need to handle one expression at a time.

Next we need to do some cleanup. I start by setting the order of operations. For the most part this is the same as basic arithmetic. However I also have a new operator for rolling. For this I decided to give it the highest precedent. 3D6 * 2 usually means roll 3, six-sided dice, and multiply the total by 2, not roll three, twelve-sided dice.

        RegisterOperators(1, "+", "-");
        RegisterOperators(2, "*", "/");
        RegisterOperators(3, "d");

Finally, there are some characters which need to be treated in a unique way. In particular, parenthesis are punctuation, and need to match up. (1+2)-(3 shouldn’t work. Also, when we build the tree we don’t need all the nodes. The nodes we can skip are called “transient.” This is not strictly necessary, but it makes navigating the tree easier when a branch is just the operator with the values as children, rather than the expression with the operator as a child, which in turn has several term nodes with the actual values even deeper in the tree. I also enable AST generation here.

        MarkPunctuation("(", ")");
        RegisterBracePair("(", ")");
        MarkTransient(term, expression, binaryOperator, parentheticalExpression, rollOperator);

        LanguageFlags = LanguageFlags.CreateAst;
    }
}

Building the Custom AST Node

In the previous section we told the system to use a custom AST node class for evaluating the roll operator. The class we need to provide here has to be based on the AstNode class, and override a few methods therein. First, we need an empty constructor, and an initialization method.

public class RollOperationNode : AstNode {
    public AstNode Left, Right;
    public int LastRoll;

    public RollOperationNode() { }
    
    public override void Init(AstContext context, ParseTreeNode treeNode) { 
        base.Init(context, treeNode);
        var nodes = treeNode.GetMappedChildNodes();
        Left = AddChild("Arg", nodes[0]);
        Right = AddChild("Arg", nodes[2]);
        var operatorToken = nodes[1].FindToken();
        ErrorAnchor = operatorToken.Location;
    }

This is mostly boilerplate code. We trigger the base init first, then pull in the node data. We store the children for later evaluation, and find the operator so that, in the event of an error, the parser can tell the user where the problem originated.

Next we need to tell the system how to evaluate these rolls. This is a relatively simple process, as I have factored the actual rolling logic into a utility class we can take a look at later. First I evaluate the child nodes (can’t roll the dice until I know how many and what kind to roll). Next I get the random result, store it in case I need it later, and return the result. The thread changes are for Irony.

    protected override object DoEvaluate(ScriptThread thread) {
        thread.CurrentNode = this;
        var leftValue = (int)Left.Evaluate(thread);
        var rightValue = (int)Right.Evaluate(thread);
        var result = new Dice().Roll(leftValue, rightValue);
        thread.CurrentNode = Parent;
        LastRoll = result;
        return result;
    }
}

Building the Utility Classes

Finally, we need a few utility classes to fill in some blanks and make using the parser a bit easier. We will start with the Dice class I just used in the operator evaluation logic. It is definitely not good practice to be initializing the Random instance each time, and there are far better alternatives, but this will suffice for testing.

public class Dice {
    private Random random;

    public Dice() {
        random = new Random();
    }

    public int Roll(int numberOfDice, int sidesPerDie) {
        int total = 0;
        for(int i=0; i<numberOfDice; ++i) {
            total += random.Next(1, sidesPerDie + 1);
        }
        return total;
    }
}

Pretty simple. We need to add a couple easy methods to the grammar class as well. First we need to override the BuildAst method so that the tree will build properly, and spit out errors if it fails. Obviously it would be better here to spit out more useful errors, but it will work for now. We also add a method to retrieve the runtime, which lets us shorten the next bit of code.

    public override void BuildAst(LanguageData language, ParseTree parseTree) {
        var opHandler = new OperatorHandler(language.Grammar.CaseSensitive);
        Util.Check(!parseTree.HasErrors(), "ParseTree has errors, cannot build AST.");
        var astContext = new InterpreterAstContext(language, opHandler);
        var astBuilder = new AstBuilder(astContext);
        astBuilder.BuildAst(parseTree);
    }

    public LanguageRuntime CreateRuntime(LanguageData language) {
        return new LanguageRuntime(language);
    }

Finally we need a class to help with triggering the roll. This is again mostly boilerplate code, so we don’t want to be re-typing it every time we evaluate an expression.

public class RollHelper {
    public void Roll(string expression) {
        var grammar = new ExpressionGrammar();
        LanguageData language = new LanguageData(grammar);
        Parser parser = new Parser(language);
        ParseTree parseTree = parser.Parse(expression);

        grammar.BuildAst(language, parseTree);
        ParseTreeNode root = parseTree.Root;
        var astNode = root.AstNode as AstNode;

        var runtime = grammar.CreateRuntime(language);
        var scriptApp = new ScriptApp(runtime);
        var thread = new ScriptThread(scriptApp);
        var value = astNode.Evaluate(thread);
    }
}

Summary

With all this we should be able to evaluate the expressions I used as examples at the beginning of this post. I hope to later expand this system to allow for basic variable evaluation so users could embed character stats, weapon/armor specs and so on into their rolls, but that will be a challenge for another time.

Advertisements




Git and Unity

14 04 2015

Over the course of the last semester my capstone team has explored the use of Git revision control with Unity3D, both in theory and in practice. In this post I will outline the solutions we’ve come up with. The first part of this post examines the steps required to utilize Git reversion control with Unity3D. The second part outlines a work-flow for small teams based on feature-branching. At the end I will outline several potential pitfalls.

Setting Up Revision Control

Unity

Unity hides a great deal of meta-data for the asset files, so before we can set up the repository we first need to adjust some settings in Unity. For the following steps I assume you have already opened the project you wish to place under source control in Unity.

First we need to set the repository mode. If you are using Unity version 4.5 or later you can skip this step. Navigate to Unity → Preferences → Packages and setRepository to External.

Next we need to set the version control mode. This allows Git to see the meta files, so that it can track them. Navigate to Edit → Project Settings → Editor and setVersion Control Mode to Visible Meta Files.

Next we’ll set the asset serialization mode. Git does not handle binary files nearly as well as it does text (more on that later), so we’ll force it to use plain-text to store all assets. Navigate to Edit → Project Settings → Editor and set Asset Serialization Mode to Force Text.

Finally, save the scene and project to store the changes.

Git

Now that we have Unity’s files prepared, we can set up the repository. There are two decisions to make at this stage: which client to use (if any) and which host to use (if any). We’ll start local, with the client, and shift to the host once we have the local repository initialized.

The Client

The first thing to do is decide on a client. I’ll leave this choice to the reader but I use SourceTree. Other popular options include the GitHub app and the git command line. Other tutorials exist for each of the aforementioned applications, so I won’t be going into detail here. Ensure you have a client installed before moving forwards.

Once we have a client ready we need to create the repository. The exact steps to do so vary based on the client, but all should execute the command git init. Do this in the root folder for your Unity project, the one with the Assets subfolder.

Before we commit any changes to the repository we’ll want to adjust the .gitignore file. The format of that file is beyond the scope of this document, but details can be found in the Git documentation. In particular, we want to prevent git from tracking the Library and Temp folders. We also want to ignore any .sln, .csproj,.unityproj, .orig, and .userprefs files. All of these are re-generated when Unity builds the project, and will show as updating for every commit if included.

Once the ignore file is updated we’re ready to make the first commit to the local repository. Do this using your client of choice, or the command line git commit.

The Server

If you’re working alone, and only from one machine you can skip this step, although I would advise maintaining a server repository as a backup regardless.

First you’ll need to decide where to host your repository. I use BitBucket, but GitHub and many other locations also offer free repository hosting. Alternatively, if you own a server of your own you can install Git there. Wherever you host, create an empty repository.

Make note of the repository URL, this is usually provided in a copy-paste box, and often ends with .git. Return to your client of choice, open your local repository (if you haven’t already), and add a new remote. (The command for this should be something like git remote add origin yoururl) While not mandatory, the primary remote is conventionally named origin.

First Push and Additional Contributers

Once you’ve set up your remote you’re ready to push the commit you made earlier. You’ll want to push to the remote repository you set up earlier, and to a new branch. (The command for this should be something like git push origin master) Again, while not mandatory, conventionally this main branch is named master.

Once you’ve made the push ensure the changes have appeared on the host you chose previously. If so, you’re ready to bring in any additional contributers for the project.

Each additional contributer should start by acquiring a client of their choosing. Ensure each additional contributer has a client installed before continuing, and give them all the URL for the remote repository.

Instead of creating a local repository they will be cloning the remote you set up earlier. How this is done will vary from client to client, but all should execute the command git clone yoururl. The code from the remote should be downloaded and a local repository created. At this point git and Unity are set up, and you can begin work on your project with whatever work-flow you may choose.

A Unity-Git Work-Flow

Overview

While having local and remote copies is, on its own, quite useful, perhaps the greatest value in using Git (or alternative source control systems) comes from the support for parallel work by multiple team members. For this to work smoothly, however, the team should decide on a work-flow in advance of beginning work on the project.

For the project which inspired this post I worked on a game in Unity with a team of two (2) other developers. Each of us had separate weekly milestones, with presentations every Monday with the combined changes. As such, we made heavy use of Git’s branching and merging features to allow each of us to make changes without interfering with the work of the others, and to merge as a group.

Details

For the work-flow we settled on the master branch served as a release branch, with each commit representing a release-version of the software. While we didn’t do so, I would suggest using tags to label each commit with a release number.

Feature Branching Image

Because of the decision to use the master branch for releases no changes are permitted to be made directly to it. Instead every week we would each create a branch off of master named for the features we intended to add, or with a user-name and date. What you use is relatively unimportant so long as they can be kept distinct and consistent.

At the end of each week would sat down and worked together to merge all the changes back into master one at a time. As a result, we were all given the opportunity to examine each other’s code, offer suggestions for later improvements, and ensure everything fit together properly.

Caveats and Common Problems

As mentioned at the beginning of this post, Git doesn’t work as well with binary files as it does with text. Since it cannot identify changes, it will instead store a new copy of the file every time it changes. For large assets this can quickly eat though the space allotted for many free Git hosts. As such, it may be prudent to maintain a DropBox or other file-sharing solution in addition to the Git repository, and store any binary assets undergoing regular modifications there.

Additionally, while converting the meta-files to text helps Git keep track, they, and most asset files, are hard to read and modify by hand. As such, merging them can be an absolute nightmare. Our solution was to each maintain a separate scene for working in, then combine the changes by hand at the end of each week. The merged scene could then be copied into each individual scene to keep everything up to date.

Alternatively, there are several tools in the Unity store which propose to simplify the process with a graphical interface for merging such files. I have not had the opportunity to try them, but it may be worth the investment if a project is likely to be more asset heavy than code-heavy.

Finally, for teams new to Git ensure before each merge that everyone is certain of which branch is being merged into which; the interface for merging can be confusing, and it is easy to accidentally merge the master into a feature branch instead of vice-versa, and harder to fix it.





Custom Field-Of-View

27 03 2015

While working on a rogue-like project I’m not quite ready to announce, the need arose for a customized grid-based FoV solution. In particular, one that would permit fractional opacity values. In this blog post I aim to outline the solution I found, and open it up for discussion.

First, some requirements. The FoV would be used for an overworld map with a height-map element and varried terrain types. While this means there will be few if any elements that totally block the player’s view, there are some I want to obscure it. For example, the player should be able to see farther over an open plain or the ocean than they can into a dense forest or over rolling hills.

Representation

I intend to codify the information for this feature in two numbers, opacity, and visibility. Both will fall between zero (0) and one (1).

Opacity will be set once at startup on a per-tile (or per-tile-type) basis, and will determine how badly the tile obscures the player’s vision. It will be based on factors such as height (possibly relative height in a later iteration, adding the necessity to recalculate these values), and the type of terrain. A dense forest might use 0.4 while an open plain might have a zero (0).

Visibility is calculated each frame, and is defined as one (1) minus the sum of the opacity values of all tiles between it and the player. An opacity of one (1) means the tile is completely blocked. A visibility of zero (0) means the tile is completely obscured from where the player is standing, while a visibility of one (1) means the tile is perfectly visible. A factor of the distance between the player and the tile is also be subtracted to simulate fog.

The Algorithm

While considering how best to design my own solution I realized each tile only needed to consider the closest tile in a direct line from the tile in question to the player. If it was aware of the visibility and opacity of that tile, it could calculate its own visibility from that information. For such a system to work the algorithm would have to consider the tiles closer to the player first, and then work its way outward. As such, the algorithm I’ve designed first considers the eight (8) tiles surrounding the player, then the tiles surrounding those, and expands outwards from there.


Diagram 1 – Order in which tiles are checked. Zero (0) represents the player.
Tiles labeled with a one (1) are checked first, then two (2), and so on.

When considering a given tile, and assuming the tiles between it and the player have already been considered, the next step is to determine which tile would be next in a line drawn from it to the player. In my current iteration I’m using Atan2() to give me an angle, and checking which of the eight (8) forty-five (45) degree wedges that angle falls into. Something based on the slope would likely be faster, as it wouldn’t involve calling a trigonometric function.

Once I know what that inner tile is, the visibility of this tile is set to be the visibility of the inner tile minus the opacity of the inner tile. This is done instead of using the opacity of the current tile to allow tiles with an opacity of one (1) to still be visible. When considering a tile diagonally, the opacity to add is multiplied by 1.4f, a rough approximation of the square root of two (2), to give the effect of a more circular view.

The Code

The project I’m building makes use of the Libtcod library for input and rendering, as well as a few other features. In this particular section of the code base you will see me reference a TCODConsole class, and call methods on it which place characters and strings onto a console. I also adjust the foreground colors using the TCODColor class and an Interpolate method which blends them. Finally, there is a hand-written Vector2 class used to track positions.

At the moment I’m building the system in a test scene; the scene interface in this project contains methods explaining how to update each tick, how to render, and how to respond to input. Here I’m specifying a location for the player, a list of locations for the walls, and a temporary location for use later in the algorithm, pre-allocated for performance. Angle is also being pre-allocated for later. Range determines how far out the FoV algorithm will look, and fog is as explained earlier. Visibility and opacity are 2D float arrays used to store the values defined earlier.

public class TestScene  : IScene {
    private Vector2 player, temp;
    private float angle
    private int range = 100;
    private float fog;
    private float[,] visibility;
    private float[,] opacity;
    private List<Vector2> walls;
}

The constructor is fairly self-explanatory. I’m setting the player’s position and creating the arrays, and setting the opacity based on the presence of the walls I define.

public TestScene() {
    player = new Vector2(20, 20);
    fog = 0.1f;
    visibility = new float[Game.Settings.ScreenWidth, Game.Settings.ScreenHeight];
    opacity = new float[Game.Settings.ScreenWidth, Game.Settings.ScreenHeight];
    walls = new List<Vector2>();
    walls.Add(new Vector2(10, 10));
    walls.Add(new Vector2(11, 10));
    walls.Add(new Vector2(11, 11));

    for (int x = 0; x < Game.Settings.ScreenWidth; ++x) {
        for (int y = 0; y < Game.Settings.ScreenHeight; ++y) {
            opacity[x, y] = 0.0f;
            if (walls.Any(w => (int)w.X == x && (int)w.Y == y)) {
                opacity[x, y] = 1.0f;
            }
        }
    }
}

Inside the update method I clear the visibility array with zeros (0), save at the player’s location, which starts at one (1) so that the player can always see where they’re standing.

public void Update(float deltaTime) {
    for (int x = 0; x < Game.Settings.ScreenWidth; ++x) {
        for (int y = 0; y < Game.Settings.ScreenHeight; ++y) {
            visibility[x, y] = 0;
            if (x == (int)player.X && y == (int)player.Y) {
                visibility[x, y] = 1;
            }
        }
    }
}

The render method draws the FoV at increasing ranges, then layers the player and walls over them. I’m doing this because the visibility is rendered as arrows pointing at the square they came from, with a color based on visibility. Ideally the system wouldn’t “render” the FoV at all, but would instead simply darken less visible tiles.

public void Render(TCODConsole console) {
    for (int i = 1; i <= range; ++i) {
        RenderFovAtRange(console, player, i);
    }
    foreach (var wall in walls) {
        console.putChar((int)wall.X, (int)wall.Y, "#");
    }
    console.putChar((int)player.X, (int)player.Y, "X");
}

After this I have some code in place to handle input and allow the player to move around and adjust the range of the FoV algorithm. I’m testing it in a 50×50 area, so the range of 100 is more than enough to cover all of it.

The next block of code, RenderFovAtRange(), loops through all the tiles in a square at the defined distance from a center point. The corners are handled afterwards at the end, as they would otherwise be calculated twice. This isn’t problematic in the current version, but if I were to later add multiple sources for theFoV (as might happen to simulate lights) the addition would cause problems.

private void RenderFovAtRange(TCODConsole console, Vector2 source, int range) {
    Vector2 coords = new Vector2();
    for (int x = -range + 1; x <= range - 1; ++x) {
        coords.X = (int)source.X + x;
        coords.Y = (int)source.Y - range;
        PutFovArrow(console, source, coords);
        coords.Y = (int)source.Y + range;
        PutFovArrow(console, source, coords);
    }
    for (int y = -range + 1; y <= range - 1; ++y) {
        coords.X = (int)source.X - range;
        coords.Y = (int)source.Y + y;
        PutFovArrow(console, source, coords);
        coords.X = (int)source.X + range;
        PutFovArrow(console, source, coords);
    }
    coords.X = (int)source.X - range; coords.Y = (int)source.Y - range;
    PutFovArrow(console, source, coords);
    coords.Y = (int)source.Y + range;
    PutFovArrow(console, source, coords);
    coords.X = (int)source.X + range; coords.Y = (int)source.Y - range;
    PutFovArrow(console, source, coords);
    coords.Y = (int)source.Y + range;
    PutFovArrow(console, source, coords);
}

Finally, for each tile it considers the above method calls PutFovArrow(). Each call to this method considers where the tile is in respect to the source, determines the next closest tile, calculates the visibility, and draws an arrow pointing at the tile it referenced with a color based on the visibility it calculated. In final form it will likely do no drawing at all, instead just filling the visibility array for later use when rendering.

private void PutFovArrow(TCODConsole console, Vector2 source, Vector2 pos) {
    // No point checking if we're outside the bounds of the map anyways.
    if(pos.X < 0 || pos.X >= Game.Settings.ScreenWidth ||
        pos.Y < 0 || pos.Y >= Game.Settings.ScreenHeight) {
            return;
    }

    temp = source - pos;
    temp.Normalize();
    angle = 180.0f + (float)(Math.Atan2(temp.Y, temp.X) * (180.0 / Math.PI));
    if (angle >= 360.0f) angle -= 360.0f;

    char c = '?';
    float op = 0.0f,
            vis = 0.0f;
    if (angle != float.NaN) {
        if (angle > 337.5f || angle <= 22.5f) {
            op = 1.0f * (fog + opacity[(int)pos.X - 1, (int)pos.Y]);
            vis = Math.Max(0.0f, visibility[(int)pos.X - 1, (int)pos.Y] - op);
            c = (char)TCODSpecialCharacter.ArrowWest;
        } else if (angle > 22.5f && angle <= 67.5f) {
            op = 1.4f * (fog + opacity[(int)pos.X - 1, (int)pos.Y - 1]);
            vis = Math.Max(0.0f, visibility[(int)pos.X - 1, (int)pos.Y - 1] - op);
            c = (char)TCODSpecialCharacter.NW;
        } else if (angle > 67.5f && angle <= 112.5f) {
            op = 1.0f * (fog + opacity[(int)pos.X, (int)pos.Y - 1]);
            vis = Math.Max(0.0f, visibility[(int)pos.X, (int)pos.Y - 1] - op);
            c = (char)TCODSpecialCharacter.ArrowNorth;
        } else if (angle > 112.5f && angle <= 157.5f) {
            op = 1.4f * (fog + opacity[(int)pos.X + 1, (int)pos.Y - 1]);
            vis = Math.Max(0.0f, visibility[(int)pos.X + 1, (int)pos.Y - 1] - op);
            c = (char)TCODSpecialCharacter.NE;
        } else if (angle > 157.5f && angle <= 202.5f) {
            op = 1.0f * (fog + opacity[(int)pos.X + 1, (int)pos.Y]);
            vis = Math.Max(0.0f, visibility[(int)pos.X + 1, (int)pos.Y] - op);
            c = (char)TCODSpecialCharacter.ArrowEast;
        } else if (angle > 202.5f && angle <= 247.5f) {
            op = 1.4f * (fog + opacity[(int)pos.X + 1, (int)pos.Y + 1]);
            vis = Math.Max(0.0f, visibility[(int)pos.X + 1, (int)pos.Y + 1] - op);
            c = (char)TCODSpecialCharacter.SE;
        } else if (angle > 247.5f && angle <= 292.5f) {
            op = 1.0f * (fog + opacity[(int)pos.X, (int)pos.Y + 1]);
            vis = Math.Max(0.0f, visibility[(int)pos.X, (int)pos.Y + 1] - op);
            c = (char)TCODSpecialCharacter.ArrowSouth;
        } else if (angle > 292.5f && angle <= 337.5f) {
            op = 1.4f * (fog + opacity[(int)pos.X - 1, (int)pos.Y + 1]);
            vis = Math.Max(0.0f, visibility[(int)pos.X - 1, (int)pos.Y + 1] - op);
            c = (char)TCODSpecialCharacter.SW;
        }
    }
    visibility[(int)pos.X, (int)pos.Y] = vis;
    console.setForegroundColor(TCODColor.Interpolate(TCODColor.black, TCODColor.white, vis));
    console.putChar((int)pos.X, (int)pos.Y, c);
    console.setForegroundColor(TCODColor.white);
}

The Result

When I walk next to the walls I get a view like this:


Diagram 2 – View with wall opacity of one (1)

If I go into the code and alter the walls to have an opacity of 0.3 instead of one (1) I get:


Diagram 3 – View with wall opacity of 0.3




Time Management

6 08 2012

Tower of Azari now has a time management/turn system which is almost entirely handled in scripts (with one exception which I’ll get to in a moment).  Each mob has an AP stat and a speed stat (as defined in the data files) – actions subtract AP (though you can take an action for which you don’t have the AP – an excellent example of a “feature” which could be changed with just a couple lines in a script).  The mob with the highest AP is always the one to act, and once all mobs run out of AP they all have their speed stat added to it (note, added, so if an action put a mob into the negative, they’ll not have a full set the following round).
I’ve also added the ability to pick up items, and examine them through a very, very simply inventory screen. They can’t be dropped, and it’s all hard-coded, so no script access yet.  I plan, for a future release, to add support for “Abilities”, which can either be tied to a letter-key or chosen from a list (if no key is assigned).  Once these roll around, there will also be a way to flag certain abilities as starting abilities, so the player can always do things like pick up items, but spells might have to be unlocked, for example.

However, for the next release my goals aren’t quite so ambitious.  I just plan to get dropping items in (again, hard-coded and hacked together for now), and equipment, which will involve the addition of slots, item types, etc to mobs and items.  Once that’s done, I’ll bundle it all up in a clean release, and stick it up somewhere for the public.





Rogue-like Timing

10 09 2009

Normally I’d be asleep by 11:30, but I just had an idea for a timing system in rogue-likes, although it would work pretty well in any turn-based game.

Each mob (and the character) has action points.  Taking an action uses up action points (AP from now on), which regenerate automatically at a rate that determines the creatures speed.  Each loop the creature with the highest AP acts first, and only then if they have at least a certain number of AP, and can act as many times as nessecary to bring them beneath that number.
For example, let’s say the player has 100 AP, and regenerates 100 AP per turn, and there is a monster that is exactly the same.  We’ll assume movement and attacks require 100 AP, but simpler actions like droping an item take only 50.  In this case the required AP to act would be 100.  An example list of actions might be…
PC(100) – Moves (0)
NPC(100) – Moves (0)
– NEXT ROUND – (+100AP EACH)
PC(100) – Drops something (50)
NPC(100) – Attacks (0)
– Next ROUND –
PC(150) – Attacks (50)
NPC(100) – Moves (0)
– NEXT ROUND –
PC(150) – Picks Something Up (100)
PC(100) – Attacks (0)
NPC(100) – DIES
A more interesting example with a slower monster with say, 75 regen.
PC(100) – Moves (0)
NPC(75) – Skipped
– NEXT ROUND – (+100AP for PC, +75AP for NPC)
PC(100) – Attacks (0)
NPC(150) – Attacks (50)
– NEXT ROUND –
PC(100) – Attacks (0)
NPC(125) – Attacks (25)
– NEXT ROUND –
PC(100) – Moves(0)
NPC(100) – Moves(0)
– NEXT ROUND –
PC(100) – Attacks(0)
NPC(75) – Skipped
This results in the player getting an extra action against the creature every 4th round.
Actions requiring more than the required AP would be handled by allow the AP to go negative, and not letting them act until they have enough AP.  The above example with a spell that takes 150AP from the player.
PC(100) – CASTS SPELL (-50)
NPC(75) – Skipped
– NEXT ROUND –
PC(50) – Skipped
NPC(150) – Attacks (50)
And so on and so forth.

Each creature/unit has action points… Read the rest of this entry »