Monday 24 March 2014

Strategy games

Twine has the potential for creating strategy games; this is a quick guide to getting started. Strategy games can be as complicated as you like, we will build a very basic one involving running a small kingdom.

This is written for SugarCube, but should work in other formats too.

Variables

A strategy game is going to involve a lot of variables, tracking the game state. There are 2 ways to handle a variable in twine. Say we have a variable for tracking monery called $gold, we could set it and print it like this:

<<set $gold = 100>>
<<print $gold>>


If you want to change the value in a function, you might do this (in Sugarcube):

state.active.variables.gold += 50;

We are going to be doing a lot of coding, and I would really rather not have to type "state.active.variables." every time, so for this game we will be using variables that are local to the script passage. Setting the value and modifying might look like this:

var gold = 100;
gold += 50;


Note that you do need to declare it before it is used, so the "var" is necessary (that said, Sugarcane seems not to mind if it is missed sometimes).

To display the value, you then need a macro or a function, which is not so convenient, but only has to be set up once.

macros.gold = {
  handler: function(place, macroName, params, parser) {
    new Wikifier(place, "" + gold);
  },
};


Note that Wikifier expects a string; if I sent it just a number, nothing would get added. By adding the gold to an empty string first, the number is converted to a string, and gets displayed properly.

In fact, a better way to handle this is a macro that displays all the important statistics:

macros.summary = {
  handler: function(place, macroName, params, parser) {
    var s = '';
    s += "Turn: " + turn;
    s += "\nKnights: " + knights;
    new Wikifier(place, s);
  },
};


Passages

We will have relatively few passages. A "Start" to introduce the game will lead to the "Main" passage, and this will have links to a number of passages where the player can control different aspects of the game. A link to this very page for moving on time, and a link to a help page will be about it. All the real work will be done in JavaScript.

Here is the "Start" passage:

<<set init()>>Manage the Kingdom of Gathnia, lead it to properity.

[[Main]]


Besides the introductory text, there is a macro that invokes a function called "init", and a link to the "Main" passage. We will get back to the "init" function in a bit.

The "Main" passage looks like this:

<<summary>>

[[Manage Knights]]
[[Next Turn|Main][nextTurn()]]


So far all it does is give a summary report, then give a link to the knight management section, and a link to this page. Linking back to the same page is something we will do a lot. Note that the link calls a function, "nextTurn".

Here is the passage for managing knights; the same sort of thing.

[[Recruit|Manage Knights][recruitKnight()]]
[[Main]]


Strategy games are great for competitions that limit the word and passage count!


The Script

Here is the script passage, where the variables are declared and the three functions are defined:

var turn, knights;

window.init = function() {
  turn = 0;
  knights = 0;
}

window.nextTurn = function() {
  turn++;
}

window.recruitKnight = function() {
   knights++;
}


The "init" function sets up the initial values (it is best to do this in a function and call the function so they get re-initialised on a restart). The other two functions increase the value of a certain variable by one.

You should now have a working game where you can increase the turn and recruit knights. And nothing else.

Peasants and gold

Let us introduce peasants and gold. Pretty much any strategy game will use some form of money as it is a way of limiting what the player can do and measuring success. Let us say it costs 50 gold to recruit a knight, and you have to pay them 20 gold each turn. However, peasants make you 2 gold each turn. Here are some updated functions to reflect that.

var turn, knights, peasants, gold;

window.init = function() {
  turn = 0;
  knights = 0;
  gold = 100;
  peasants = 50;
}

window.nextTurn = function() {
  turn++;
  gold += (state.active.variables.peasants * 2);
  gold -= (state.active.variables.knights * 20);
}

window.recruitKnight = function() {
  knights++;
  gold -= 50;
}

macros.summary = {
  handler: function(place, macroName, params, parser) {
    var s = '';
    s += "Turn: " + turn;
    s += "\nKnights: " + knights;
    s += "\nPeasants: " + peasants;
    s += "\nGold: " + gold;
    new Wikifier(place, s);
  },
};


Bandits!

Wait... What is the point of recruiting knights? They cost money, so they need to have value. Let us say that knights protect the land from bandits and also give prestige, which in turn could give further bonuses. We can modify the Main passage like this:

<<summary>>

<<if banditFail() >>
GAME OVER
<<else>>
[[Manage Knights]]
[[Next Turn|Main][nextTurn()]]
<<endif>>


Now we can modify the summary macro so the player will get a report on the bandit situation, and if banditFail is true, that is game over. Clearly we need to modify the scripts. I am going to break that up somewhat to explain the changes. First we need a variable to track the situation, banditRisk, and that must be initialised. We also need some phrases to inform the player.

var turn, knights, peasants, gold, banditRisk;

window.init = function() {
  turn = 0;
  knights = 0;
  gold = 100;
  peasants = 50;
  banditRisk = 50;
}

var banditRisks = [
  "-",
  "Some concern among the peasants about bandits in the hills",
  "Some peasants are claiming there are bandits in the hills",
  "Bandits have been seen in the area",
  "The bandits are becoming bolder",
  "The bandits have killed a couple of peasants; your people are demanding action",
  "The bandits have killed a large number of peasants, and are threatening the castle",
  "The bandits have overrun the castle",
];


The nextTurn function needs to be modified to update banditRisk. The more peasants you have, the more income and so the greater the risk of bandits. Having knights makes it go down. The value of banditRisk can never be below zero.

window.nextTurn = function() {
  turn++;
  gold += (state.active.variables.peasants * 2);
  gold -= (state.active.variables.knights * 20);
  // Knights quit if there is no gold for them
  if (gold < 0) {
    knights += (gold / 20);
    if (knights < 0) {
      knights = 0;
    }
  }

  // handle bandits
  banditRisk += (peasants * 2);
  banditRisk -= (knights * 5);
  if (banditRisk < 0) banditRisk = 0;
}


The next function returns true when banditRisk is high. How high depends on the number of risk phrases available. Note that I use banditRisks.length; we can add more phrases to the list without having to change anything here.

window.banditFail = function() {
  return (Math.round(banditRisk / 60) >= (banditRisks.length - 1));
}


Then I need to update the summary macro. This just adds one of the phrases defined earlier to the text, depending on how high banditRisk is. Just in the case the risk is sky-high, the last phrase is used whenever banditRisk is high, and we can use the banditFail function to test that.

macros.summary = {
  handler: function(place, macroName, params, parser) {
    var s = '';
    s += "Turn: " + turn;
    s += "\nKnights: " + knights;
    s += "\nPeasants: " + peasants;
    s += "\nGold: " + gold;
    new Wikifier(place, s);
    if (banditFail()) {
      new Wikifier(place, banditRisks[banditRisks.length - 1]);
    } else {
      new Wikifier(place, banditRisks[Math.round(state.active.variables.banditRisk / 60)]);
    }
  },
};




What next?

From here on you can make the game as complicated as you like. You probably need at least three variables that the player has to juggle and can only indirectly control. So far we have gold and banditRisk, but happiness would be a good addition. We can add more demographics to add more options, allow the player to set tax rates, etc..

Using JavaScript

Some general advice when coding in Twine:

1 Test the game often. At least do Build - Test play, even if you do not play through, as this will quickly check the JavaScript code can be understood. If you write three lines of code and Twine throws an error, you only have thee lines to check through. If you have written 20 lines since the last Build - Test play, you have a lot more lines to search through.

2 If there is a problem, scatter alert instructions though the code, each giving a different message. Play the game, and see what messages are displayed when to track down where the code is faulty.

No comments:

Post a Comment