Thalassicus
Bytes and Nibblers
There's a basic tradeoff in programming:
As processor power rises and memory prices drop, the bottom two aspects of programming become less important. It's increasingly vital to create code that's easy to write, read, and maintain. I'm going to discuss some basics of the human factor here.
You should be able to come back to your work some time later and still understand what the heck you did. If your code looks like gibberish, you waste time figuring it out again.
For example, I've copied below some real code from a popular Civ 5 mod. Code like this is difficult to read. The variables have cryptic names, which are hard to understand and difficult to search for with find-replace tools. The formatting is also inconsistent. Some "if" statements are on multiple lines, while others are on one line. Writing code better than this makes it easier for you (and fellow modders!) to use or alter the code later.
These are two important aspects of writing readable code:
The common naming standard in the game industry is camel case:
SomeFunctionName()
someVariableName
CONSTANT_VALUE
Join words (without spaces) into the name. Capitalize the first letter of functions, while leaving the first letter of variables lowercase. Constants are all caps with an _ underscore separating words.
Variable names are the most obvious part of Civ 5's code with poor design. Some parts of the code use camel case, while others use a variant called Hungarian notation. Pick one style and stick to it! This inconsistency makes it difficult to work with the vanilla Firaxis code. I'd recommend avoiding Hungarian notation in Lua/C++ for the following reasons:
When you read your code out-loud it should somewhat make sense. Variable names are usually a noun, and function names are a verb. These are the most common function name prefixes:
For further information about good coding practices, search Google for "coding standard" or check out the link below. Most standards of C++ also apply to similar languages like Lua and Java.
www.possibility.com/Cpp/CppCodingStandard.html
Below is an example of easily readible Civ 5 Lua code to get plots within a circular area. Variables names are descriptive, and tasks are separated with blank lines. The tasks are:
As processor power rises and memory prices drop, the bottom two aspects of programming become less important. It's increasingly vital to create code that's easy to write, read, and maintain. I'm going to discuss some basics of the human factor here.
- Why?
- Guidelines
- Further Details
- Example
Why?
You should be able to come back to your work some time later and still understand what the heck you did. If your code looks like gibberish, you waste time figuring it out again.
For example, I've copied below some real code from a popular Civ 5 mod. Code like this is difficult to read. The variables have cryptic names, which are hard to understand and difficult to search for with find-replace tools. The formatting is also inconsistent. Some "if" statements are on multiple lines, while others are on one line. Writing code better than this makes it easier for you (and fellow modders!) to use or alter the code later.
Code:
if cT ~= "" then
repeat
local t1, t2; local more = false;
s, e, c, d = findToken( cT, e );
if s ~= nil then t1 = deserialize( c ); end
if d == "=" then
s, e, c, d = findToken( cT, e+2 );
if s ~= nil then t2 = deserialize( c ); end
end
if d == "," then e = e+2; more = true; end
if t2 ~= nil then r[t1] = t2;
else table.insert( r, t1 );
end
until e >= len and not more;
end
Guidelines
These are two important aspects of writing readable code:
- Use good formatting, with spaces and indentation. The earlier code example is cramped and hard to read.
- Give variables descriptive names so they are instantly recognizable.
Code:
((centerY+maxRadius) % mapH) or Constrain(0, centerY+maxRadius, mapH-1)
SomeFunctionName()
someVariableName
CONSTANT_VALUE
Join words (without spaces) into the name. Capitalize the first letter of functions, while leaving the first letter of variables lowercase. Constants are all caps with an _ underscore separating words.
Variable names are the most obvious part of Civ 5's code with poor design. Some parts of the code use camel case, while others use a variant called Hungarian notation. Pick one style and stick to it! This inconsistency makes it difficult to work with the vanilla Firaxis code. I'd recommend avoiding Hungarian notation in Lua/C++ for the following reasons:
Spoiler :
Hungarian notation is a naming convention in which the name of a variable or function indicates its type or intended use.
"Building the type of an object into names simply complicates and minimizes abstraction."
~ Bjarne Stroustrup, C++ creator
"Encoding the type of a function into the name (so-called Hungarian notation) is brain damaged—the compiler knows the types anyway and can check those, and it only confuses the programmer."
~ Linus Torvalds, Linux creator
- When names are sufficiently descriptive, the additional type information can be redundant. E.g. firstName is most likely a string. So naming it sFirstName only adds clutter to the code.
- If a variable's type is changed, either the decoration on the name of the variable will be inconsistent with the new type, or the variable's name must be changed.
- Modern integrated development environments display variable types on demand, and automatically flag operations which use incompatible types, making the notation largely obsolete.
- Compilers for languages providing type-checking ensure the usage of a variable is consistent with its type; checks by eye are redundant and subject to human error.
"Building the type of an object into names simply complicates and minimizes abstraction."
~ Bjarne Stroustrup, C++ creator
"Encoding the type of a function into the name (so-called Hungarian notation) is brain damaged—the compiler knows the types anyway and can check those, and it only confuses the programmer."
~ Linus Torvalds, Linux creator
- Do : perform some action without a return value
- Is : returns a true/false condition
- Get : retrieve a value
- Set : store a value
Further Details
For further information about good coding practices, search Google for "coding standard" or check out the link below. Most standards of C++ also apply to similar languages like Lua and Java.
www.possibility.com/Cpp/CppCodingStandard.html
Example
Below is an example of easily readible Civ 5 Lua code to get plots within a circular area. Variables names are descriptive, and tasks are separated with blank lines. The tasks are:
- Initialize variables.
- Check for world wrap.
- Initialize variables dependent on world wrap.
- Loop through each row and column of adjacent plots, and store plots with a distance between minRadius and maxRadius into plotList.
PHP:
function GetPlotsInCircle(plot, minRadius, maxRadius)
local plotList = {}
local mapW, mapH = Map.GetGridSize()
local isWrapX = Map:IsWrapX()
local isWrapY = Map:IsWrapY()
local centerX = plot:GetX()
local centerY = plot:GetY()
x1 = isWrapX and ((centerX-maxRadius) % mapW) or Constrain(0, centerX-maxRadius, mapW-1)
x2 = isWrapX and ((centerX+maxRadius) % mapW) or Constrain(0, centerX+maxRadius, mapW-1)
y1 = isWrapY and ((centerY-maxRadius) % mapH) or Constrain(0, centerY-maxRadius, mapH-1)
y2 = isWrapY and ((centerY+maxRadius) % mapH) or Constrain(0, centerY+maxRadius, mapH-1)
local x = x1
local y = y1
local xStep = 0
local yStep = 0
local rectW = x2-x1
local rectH = y2-y1
if rectW < 0 then
rectW = rectW + mapW
end
if rectH < 0 then
rectH = rectH + mapH
end
local adjPlot = Map.GetPlot(x, y)
while (yStep < 1 + rectH) and adjPlot ~= nil do
while (xStep < 1 + rectW) and adjPlot ~= nil do
if IsBetween(minRadius, Map.PlotDistance(x, y, centerX, centerY), maxRadius) then
table.insert(plotList, adjPlot)
end
x = x + 1
x = isWrapX and (x % mapW) or x
xStep = xStep + 1
adjPlot = Map.GetPlot(x, y)
end
x = x1
y = y + 1
y = isWrapY and (y % mapH) or y
xStep = 0
yStep = yStep + 1
adjPlot = Map.GetPlot(x, y)
end
return plotList
end