Why bother doing any of this?
So that together, we can build something very rare: a lasting game. From https://kronosaur.com/:
Building TranscendenceKronosaur Productions is dedicated to creating epic-scale games on an indie budget.
Whether ruling an empire of a thousand suns or journeying to the Galactic Core with the fate of the galaxy in your hands, you will experience games that are:
Enduring: We want our games to grow and evolve over decades; we are not interested in disposable games.
Elaborate: We aim for detail, depth, and richness in all our games. We want to build living universes for you to explore and get lost in.
Extensible: We design our games to be taken apart and augmented with your own creativity. We want you to be a participant, not just a player.
The README for the Transcendence repo on github now contains (more) detailed instructions on how to build Transcendence: https://github.com/kronosaur/Transcende ... ter/README
Visual Studio Community can be downloaded free of charge from Microsoft's website. If you are not sure which Visual Studio version to get, it's useful.
Please note that the README is a prerequisite for reading this guide, if you intend to be able to understand it fully, as I am assuming you have the relevant repositories on you computer, and have built Transcendence.
Once you have Transcendence built, now what?
A bird's eye view of the tip of the iceberg
You will have downloaded three repositories, if you followed the README's instructions.
Alchemy: A set of utility and foundational functions. All are generic—they are not specific to Transcendence. One thing that may or may not be of interest is the TArray.h file, which implements a basic dynamic array object. This is an example of a “template” in C++, which allows a type-safe object that can handle an arbitrary type. That is, we can create an array of integers as easily as an array of complex objects.
Another interesting piece of this repository is probably CodeChain, which is the implementation of the TransLisp/XML compiler. CodeChain implements the basic language (keywords like “if” “while” etc.). There is also a file called CCExtensions.cpp which implements Transcendence-specific functions (like “shpOrder”). Decoding this fully is an undergraduate-level course by itself, and probably not necessary, depending on your goals. Basically, CodeChain implements a scripting language (like Python, but much less sophisticated). We build up a datastructure representing a sequence of function calls; then we step through the datastructure executing each function.
XML will get parsed into a C++ object. Basically, each top-level XML element gets loaded into a C++ object. For example, <ShipClass> gets loaded into a CShipClass object. <StationType> gets loaded into a CStationType object (other names don’t always match up, but the principle is the same). All of these objects are known as “design types” and they inherit from the CDesignType base class. Look at CDesignCollection.cpp for more.
Transcendence: Contains the heart of the Transcendence related code.
Mammoth: Transcendence is meant to be one game out of an environment of related games (for example, CSC America, or future multiplayer games). These games (probably) will be related not only in terms of the game universe, but also the basic pieces of code that they use. Mammoth contains code that is likely to be shared between games.
Landing on the tip of the iceberg
Much of the game is implemented as XML and TLisp scripts. These define the star systems, the enemies, missions, etc. Take a look at some of the stuff here.
The C++ parts implement the game engine, which load the XML and run everything. As mentioned before, the CodeChain file that parse the TLisp? The XMLUtil files handle parsing the XML.
The core animation loop is here.
30 times per second we call this function, which (conceptually) does two things: 1: paint a frame, 2: update the state of the game one tick.Most of the update happens here. This code updates all objects in a star system. It deals with collisions (missiles/beams hitting things), AI, and player controls.
The CSpaceObject class is an abstract class from which the relevant classes derive. CShip, CStation, and CMissile are all descendants of CSpaceObject. For example, in this code we loop over every object in the system (ships, station, missiles, etc.) and update them. The call to Update ends up in a different place depending on the type of object. For example, for ships, it goes here, while for stations it goes here.
The sequence of events for calling OnUpdate for an object is: CTranscendenceWnd::Animate calls Update
CSystem::Update which calls one of (CIntroShipController::Behavior, IShipController::Behavior, CBaseShipAI:Behavior, etc.) depending on the "type" of pObj (hence pObj->Behavior, since -> is the dereference operator). The Behavior functions set the "attitude" (attack or not) for "ship type" objects, but the "Update", which subsequently calls "OnUpdate". "OnUpdate" is where the AI rules are gotten, for objects that have an AI. Ships have a “Behavior” call and an “Update” call. Behavior is where most of the AI work is done. Behavior is in charge of following the ship’s orders as best as possible. The Behavior function will then set flags and variables in the object to direct the ship. For example, it might set the maneuver variable to “turn right”. The Update code is then responsible for actually turning right.
Painting (graphically displaying) is actually spread out over a couple of places. Each object knows how to paint itself, but CTranscendenceWnd paints the user interface for player (scanners, armor/shields display, etc.).
First steps on the iceberg
TransLisp code, along with XML files, are used to instruct the "Transcendence Compiler" on how to set up the game world. So, it might be interesting for to learn how TransLisp works -- whether in order to write new TransLisp functions, or understand existing functionality. Assuming you have some version of Visual Studio installed on your computer, and that you know how to open up the Transcendence solution file (explained in the README):
1) Go to TransData and set it as the StartUp project (contrast with how you set Transcendence as the StartUp Project, if/when you followed the README instructions: the start up project sets which project execution will begin from, as there are at least a couple of different "main.cpp"s: Transcendence's and TransData's).
2) Right click on TransData, and go to Properties
3) Properties -> Configuration -> Debugging -> Command Arguments: "/run"
This specifies what command line arguments should be taken by TransData when it is run, in this case, "/run"
4) Functions are defined in DefPrimitives.h. Functions are implemeted either in CCExtensions.cpp or Functions.cpp.
5) Click "Start Debugging" or hit "F5"
6) A command line should have come up (Loading...done) etc. and you should be at a command prompt (of the TLisp Shell)
Code: Select all
TLisp Shell 3.2.2
Copyright (c) 2001-2014 by Kronosaur Productions, LLC. All Rights Reserved.
Loading...done.
(help) for function help.
\q to quit.
:
Code: Select all
TLisp Shell 3.2.2
Copyright (c) 2001-2014 by Kronosaur Productions, LLC. All Rights Reserved.
Loading...done.
(help) for function help.
\q to quit.
: (add 2 2)
4
:
8) Return to the command prompt, and type in "(add 2 2)" -- program execution should now halt at Eval in CodeChain (where you set the breakpoint)
9) Put on your detective hat (which can also be interpreted as more to come soon), and see how the program flows! (press F10 to step over, press F11 to step into, and SHIFT+F11 to step out). If you haven't changed how Visual Studio is setup to display, you should be able to see a "Call Stack" window (In VS Community 2013, usually on the bottom right hand corner). The call stack lists the functions that have been called so far (one by the other), with the most recent call on top, and the oldest call at the bottom. This is helpful in knowing what has been done so far (for instance, if you were wondering *how* "(add 2 2)" became a pItem in the first place.
NOTE: typing "(help)" in TLisp Shell (without the breakpoint set, unless you want to see what happens when you put in the help command) might be of interest too.
NOTE: Setting Transcendence as the StartUp project, and having the breakpoint set when something is interesting is happening in the game could give you insight into how TransLisp functions while the game is running.
NOTE: The README has information on how to use git to keep your local code repositories up-to-date compared with the "master" repositories.
A bit more about TransLisp
TransLisp is a dynamic programming language based on [Lisp](http://en.wikipedia.org/wiki/Lisp_(prog ... _language)). A dynamic programming language is one where the type information of a variable is determined by the compiler at runtime (when the program is executed). Compare this with a static programming languages like C or C++, where one has to explicitly declare the type of variable when creating it.
For instance, in C++, if we want to create an integer variable named integerNumber, we would write:
Code: Select all
int integerNumber = 2
In TransLisp though, we would write something like:
Code: Select all
(setq integerNumber 2)
Essentially what this you don't have to worry about the specifying the type of a variable when you instantiate it, instead, other stuff in the background will take of figuring out what type of variable integerNumber has to be.
Just like Lisp, TransLisp uses "prefix notation", instead of the standard "infix notation", or the uncommon "postfix notation". Recall that "pre" and "post" means "before" and "end" respectively. They refer to the position of the operator symbol, with respect to the position of the arguments (or inputs) to the operator.
We are used to infix notation:
Code: Select all
2 + 1 = 3
Code: Select all
+ 2 1 = 3
===========================================
===========================================
Here are the types of objects a variable in TransLisp can store:
int32: This is a 32-bit signed integer. Some functions will automatically convert an integer to a string, if necessary. But not all do.
list: This is a list created by (list ...). Note however that empty lists are treated as nil (see below).
nil: Nil is a special type (a boolean, in order for doing boolean algebra -- which can be simply thought of as the algebra of "true" and "false"). Empty lists and empty structures are also considered nil. [Inconsistently, however, empty strings are not considered nil.]
string: This is a character string.
struct: This is a structure (a key-map, which is sort of like a list, except that instead of the objects being indexed by integers, they are indexed by strings (keys)) created with (struct ...).
error: Error is a special type with unique properties. Most of the time, if a function returns an error type, the execution of the caller (the function that executed the current function) is aborted and we return the error all the way up. The exception is (errblock ...) which traps the error.
function: This is a lambda expression, created by (lambda ...).
primitive: This is a built-in function (i.e., one implemented in C++). For example: (typeof typeof) -> primitive (-> is the "output" arrow, and this bit of code can be read as "the type of the function typeof is 'primtive'".
true: This is a special type (a boolean).
===========================================
===========================================
UNDER CONSTRUCTION
Here are the primitive functions:
< or ls:
Code: Select all
(ls a b)
Examples:
Code: Select all
: (ls 0 1)
True
Code: Select all
: (ls 0 0)
Nil
Examples:
Code: Select all
: (< 0 1)
True
----------------------------------------------------
<= or leq:
Code: Select all
(leq a b)
Examples:
Code: Select all
: (leq 0 1)
True
> or gr
Usage:
Code: Select all
(gr a b)
Examples:
Code: Select all
: (gr 0 1)
Nil
>= or geq
Usage:
Code: Select all
(geq a b)
Examples:
Code: Select all
: (geq 0 1)
Nil
add
Usage:
Code: Select all
(add x1 x2 ... xn)
Examples:
Code: Select all
: (add 0 1 2 3 4 5 6 7 8 9 10)
55
abs
Usage:
Code: Select all
(abs x)
Examples:
Code: Select all
: (abs -2)
2
----------------------------------------------------
and
Usage:
Code: Select all
(and exp1 exp2 ... expn)
Examples:
Code: Select all
: (and true (gr 1 0) true)
True
Code: Select all
: (and true (gr 1 0) (list))
Nil
append
Usage:
Code: Select all
(append L1 L2 ... Ln)
Code: Select all
: (append (list 1 2 3) (list 4 5 6) (list 8 9 0))
(1 2 3 4 5 6 8 9 0)
apply
Usage:
Code: Select all
(apply expr arg1 arg2 ... argn list)
Examples:
Code: Select all
: (apply add 2 2 Nil)
4
Code: Select all
: (apply add (list 2 2))
4
Code: Select all
: (apply add (list 2 2))
4
Code: Select all
: (apply add 2 2)
Last argument for apply must be a list ### (apply add 2 2) ###
atmAddEntry
Usage:
Code: Select all
(atmAddEntry)
Examples:
Code: Select all
n/a
atmAddEntry
Usage:
Code: Select all
(atmDeleteEntry)
Examples:
Code: Select all
n/a
atmAddEntry
Usage:
Code: Select all
(atmAddEntry)
Examples:
Code: Select all
n/a
atmAddEntry
Usage:
Code: Select all
(atmList)
Examples:
Code: Select all
n/a
atmLookup
Usage:
Code: Select all
(atmLookup)
Examples:
Code: Select all
n/a
atmAtomTable
Usage:
Code: Select all
(atmAtomTable)
Examples:
Code: Select all
n/a
block
Usage:
Code: Select all
(block (<possible local variables>) (expr1) (expr2) ... (exprn))
Code: Select all
(if foo
(do-something)
)
(if foo
(block ()
(do-something)
(do-another-thing)
)
)
(if foo
(switch
…
(more-stuff-in-the-switch)
…
) ; end of the switch
)
Examples: look for (block ...) statements inside the *.xml files under Transcendence/TransCore.
----------------------------------------------------
cat
Usage:
Code: Select all
(cat s1 s2 ... sn)
Examples:
Code: Select all
:(cat "hello" world" "!")
"helloworld!"
count
Usage:
Code: Select all
(count list)
Examples:
Code: Select all
:(count (list 0 1 2))
3
divide
Usage:
Code: Select all
(divide x y)
Examples:
Code: Select all
: (divide 1 2)
0
Code: Select all
: (divide 2 2)
1
enum
Usage:
Code: Select all
(enum list itemVar expr)
Examples:
Code: Select all
: (setq pot (list))
()
: (enum (list 1 2 3) x (setq pot (append pot x)))
( 1 2 3)
enumwhile
Usage:
Code: Select all
(enumwhile list condition itemVar expr)
Examples:
Code: Select all
: (setq pot (list) )
: (enum (list true true Nil) x (enumwhile (list "hello" " world!" "gobble-d-gook") x y (setq pot (append pot y) ) ) )
Nil
: pot
(hello " world!" gobble-d-gook hello " world!" gobble-d-gook)
errblock
Usage:
Code: Select all
(errblock )
Examples:
----------------------------------------------------
error
Usage:
Code: Select all
(errblock msgString)
Examples:
----------------------------------------------------
eq
Usage:
Code: Select all
(eq a b)
Examples:
Code: Select all
: (eq 0 1)
Nil
eval
Usage:
Code: Select all
(eval exprString)
Code: Select all
: (setq myIdentifier 100)
100
: (cat myIdentifier)
100
Code: Select all
: (cat myIdentifier 100)
100100
Code: Select all
: (cat ‘myIdentifier 100)
“myIdentifier100”
(cat (quote myIdentifier) 100) -> “myIdentifier100”
However for expressions, one must use (quoted ...), and not ' or " because:
Code: Select all
: (typeof (quote (add 2 2)))
list
: (eval (quote (add 2 2)))
4
: (eval "(add 2 2)")
(add 2 2)
Anyway, returning to our earlier example. What if we want a space between myIdentifier and 100? We need to specify a space character, so we use double-quotes:
(cat “myIdentifier “ 100) -> “myIdentifier 100”
Double-quotes automatically turns the contents into a literal (i.e., treat literally without evaluating). Double-quotes are also the way to create literals with embedded spaces.
The opposite of quote is eval:
myIdentifier -> 100
(quote myIdentifier) -> “myIdentifier”
(eval (quote myIdentifier)) -> 100
Examples:
Code: Select all
: (quote (add 2 2))
(add 2 2)
: (typeof (quote (add 2 2))
list
: (eval (quote (add 2 2)))
4
filter
Usage:
Code: Select all
(filter list var boolExpr)
Examples:
Code: Select all
: (filter (list 0 10 2 20) x (> x 3))
(10 20)
----------------------------------------------------
filter
Usage:
Code: Select all
(filter list var boolExpr)
Examples:
Code: Select all
: (filter (list 0 10 2 20) x (> x 3))
(10 20)
Complete the rest later
{ "find", fnFind, FN_FIND,
"(find source target ['ascending|'descending] [keyIndex]) -> position of target in source (0-based)",
"vv*", 0, },
{ "fncHelp", fnItemInfo, FN_ITEMINFO_HELP, "", NULL, 0, },
----------------------------------------------------
for
Usage:
Code: Select all
(for var start end expr)
Examples:
Code: Select all
: (setq pot (list) )
0
: (for x 1 3 (setq pot (append pot (add x 2) ) ) )
Nil
: pot
(3 5)
help
Usage:
Code: Select all
(help partial_name_string) or (help exact_name_string)
Examples:
Code: Select all
if
Usage:
Code: Select all
(if boolExpr expr1 expr2)
Examples:
Code: Select all
{ "int", fnItemInfo, FN_ITEMINFO_ASINT, "", NULL, 0, },
----------------------------------------------------
isatom
Usage:
Code: Select all
(isatom expr) or (isatom expr)
Examples:
Code: Select all
iserror
Usage:
Code: Select all
(iserror expr)
Examples:
Code: Select all
----------------------------------------------------
isint
Usage:
Code: Select all
(isint expr)
Examples:
Code: Select all
isfunction
Usage:
Code: Select all
(isfunction expr)
Examples:
Code: Select all
isprimitive
Usage:
Code: Select all
(isprimitive expr)
Examples:
Code: Select all
@
Usage:
Code: Select all
(@ list expr)
Examples:
Code: Select all
----------------------------------------------------
isprimitive
Usage:
Code: Select all
(lambda fnName (A1, ..., An) (expression))
Examples:
Code: Select all
{ "lookup", fnFind, FN_LOOKUP,
"(lookup source target ['ascending|'descending] [keyIndex]) -> found entry",
"vv*", 0, },
{ "loop", fnLoop, 0,
"(loop condition exp)",
NULL, 0, },
{ "link", fnLink, 0, "", "s", 0, },
{ "list", fnList, 0,
"(list i1 i2 ... in) -> list",
NULL, 0, },
{ "lnkAppend", fnLinkedListAppend, 0,
"(lnkAppend list item) -> list",
"uv", PPFLAG_SIDEEFFECTS, },
{ "lnkRemove", fnLinkedList, FN_LINKEDLIST_REMOVE,
"(lnkRemove list index) -> list",
NULL, PPFLAG_SIDEEFFECTS, },
{ "lnkRemoveNil", fnLinkedList, FN_LINKEDLIST_REMOVE_NIL,
"(lnkRemoveNil list) -> list",
NULL, PPFLAG_SIDEEFFECTS, },
{ "lnkReplace", fnLinkedList, FN_LINKEDLIST_REPLACE,
"(lnkReplace list index item) -> list",
NULL, PPFLAG_SIDEEFFECTS, },
{ "map", fnMap, 0,
"(map list ['excludeNil|'original|'reduceMax|'reduceMin] var exp) -> list",
"l*qu", 0, },
{ "match", fnMatch, 0,
"(match list var boolean-exp) -> first item that matches",
"lqu", 0, },
{ "max", fnMathList, FN_MATH_MAX,
"(max x1 x2 ... xn) -> z",
"v*", 0, },
{ "min", fnMathList, FN_MATH_MIN,
"(min x1 x2 ... xn) -> z",
"v*", 0, },
{ "modulo", fnMath, FN_MATH_MODULUS,
"(modulo ['degrees] x y) -> z",
"*", 0, },
{ "multiply", fnMathList, FN_MATH_MULTIPLY,
"(multiply x1 x2 ... xn) -> z",
"v*", 0, },
{ "neq", fnEquality, FN_EQUALITY_NEQ, "", NULL, 0, },
{ "not", fnLogical, FN_LOGICAL_NOT,
"(not exp) -> True/Nil",
NULL, 0, },
{ "or", fnLogical, FN_LOGICAL_OR,
"(or exp1 exp2 ... expn) -> True/Nil",
NULL, 0, },
{ "power", fnMathFractions, FN_MATH_POWER,
"(power x y) -> z",
"vv", 0, },
{ "regex", fnRegEx, 0,
"(regex source pattern ['offset|'subex]) -> result",
"ss*", 0, },
{ "quote", fnSpecial, FN_QUOTE,
"(quote exp) -> unevaluated exp",
"u", 0, },
{ "random", fnRandom, FN_RANDOM,
"(random from to)\n(random list)",
"*", 0, },
{ "randomGaussian", fnRandom, FN_RANDOM_GAUSSIAN,
"(randomGaussian low mid high) -> random number between low and high",
"iii", 0, },
{ "randomTable", fnRandomTable, 0,
"(randomTable chance1 exp1 chance2 exp2 ... chancen expn) -> exp",
NULL, 0, },
{ "seededRandom", fnSeededRandom, 0,
"(seededRandom seed from to)\n(seededRandom seed list)",
"i*", 0, },
{ "set", fnSet, FN_SET_SET, "", NULL, PPFLAG_SIDEEFFECTS, },
{ "[email protected]", fnItem, FN_SET_ITEM,
"([email protected] list-var index value) -> list\n"
"([email protected] struct-var key value) -> struct\n"
"([email protected] struct-var struct) -> merged structs",
"uv*", PPFLAG_SIDEEFFECTS, },
{ "setItem", fnItem, FN_SET_ITEM,
"DEPRECATED: Alias of [email protected]",
"uvv", PPFLAG_SIDEEFFECTS, },
{ "setq", fnSet, FN_SET_SETQ, "", NULL, PPFLAG_SIDEEFFECTS, },
{ "shuffle", fnShuffle, 0,
"(shuffle list) -> shuffled list",
"l", 0, },
{ "sort", fnSort, FN_SORT,
"(sort list ['ascending|'descending] [keyIndex]) -> sorted list",
"v*", 0, },
{ "split", fnSplit, 0,
"(split string [characters]) -> list",
"s*", 0, },
{ "sqrt", fnMath, FN_MATH_SQRT,
"(sqrt x) -> z",
"v", 0, },
{ "strCapitalize", fnStrCapitalize,0,
"(strCapitalize string) -> string",
NULL, PPFLAG_SIDEEFFECTS, },
{ "strFind", fnStrFind, 0,
"(strFind string target) -> pos of target in string (0-based)",
"ss", 0, },
{ "subset", fnSubset, 0,
"(subset list pos [count]) -> list",
"vv*", 0, },
{ "subst", fnSubst, 0,
"(subst string arg1 arg2 ... argn) -> string",
"s*", PPFLAG_SIDEEFFECTS, },
{ "subtract", fnMathOld, FN_MATH_SUBTRACT,
"(subtract x y) -> z",
NULL, 0, },
{ "switch", fnSwitch, 0, "", NULL, 0, },
//{ "symDeleteEntry", fnSymTable, FN_SYMTABLE_DELETEENTRY,"", NULL, PPFLAG_SIDEEFFECTS, },
{ "sysGlobals", fnSysInfo, FN_SYSINFO_GLOBALS, "", NULL, 0, },
{ "sysPoolUsage", fnSysInfo, FN_SYSINFO_POOLUSAGE, "", NULL, 0, },
{ "sysTicks", fnSysInfo, FN_SYSINFO_TICKS, "", NULL, 0, },
{ "typeof", fnItem, FN_ITEM_TYPE,
"(typeof item) -> type\n\n"
"type\n\n"
" error\n"
" function\n"
" int32\n"
" list\n"
" primitive\n"
" string\n"
" struct\n",
"v", 0, },
{ "vecVector", fnVecCreate, 0, "", NULL, 0, },
{ "vecSetElement", fnVector, FN_VECTOR_SETELEMENT, "", NULL, PPFLAG_SIDEEFFECTS, }
CodeChain
CodeChain is the project that contains the machinery to parse TransLisp code and then executing it (by calling the underlying C++ implementations corresponding with various TransLisp primitives).
* DefPrimitives.h
* CodeChain.h
* CodeChain.cpp
* Functions.cpp
A walk through Eval
The Eval function, in some ways, is the heart of CodeChain: all TransLisp code, when executed flows through Eval. Why is Eval so important? Eval is the bit of CodeChain that parses a given block of TransLisp and it decides what function to call and what arguments to supply that function call based on its analysis.
Implementing your own datatype for TransLisp
Put together the knowledge obtained in the last few sections to write our own datatype for TransLisp.
Please feel free to ask questions, and let me know what I can do to improve clarity!