VIBORAS.bat - User's Manual

This is an entirely new version of the Snake game that includes a lot of new features. This program is based on the PowerShell engines to read keys and show text in color fully described at this thread; you should review such a topic for any detail related to the input and output parts of this program.

Although the most obvious point of this program is that the game is now displayed in color, the most important feature is that now there may be several snakes in the board! The new game may be played by up to four players that controls a different snake each: #1-Yellow snake, #2-Green snake, #3-Magenta snake and #4-Cyan snake. The keys used to control each snake are evenly distributed on the keyboard, so up to four hands can freely press them with no interference: the four arrow keys (Up,Left,Down,Right) for snake #1, "WASD" keys for snake #2, "GVBN" keys for snake #3 and "OKL;" keys (in English keyboards) for snake #4, although this key assignment may be easily changed.

This feature converts the old (boring) single-player game in a very interesting one with up to three opponents and several aspects that improves the competition. You really want to play the game with more than one snake to realize how different and fun it is when compared vs. the original game. Anyway, even a single-player game can be greatly improved using the additional features of this new program.


Table of Contents

  1. The multi-player snakes game
  2. Manual adjustments
  3. Movement control/modes
  4. Types of food
  5. Ways to show the foods
  6. Food expressions
    1. Conditional expressions
    2. Interface variables
    3. Some examples
  7. Snake reproduction
  8. Technicall description of VIBORAS.bat

The multi-player snakes game

I called "VIBORAS.bat" this game ("snakes" in Spanish) in order to avoid any variation of the same "snake" word that may confuse it with any other program, and also to indicate that this is a different game. The inter-relation of several víboras in the same board allows a new set of game possibilities. For example, when a food appear in the board, the snake that is closer will eat it and this food will give a speed advantage to reach the next food, excepting if it appears much closer to other snake. When the game begins, the winning (larger) snake most depends on luck (the random positions where the foods appear), but when the snakes are larger and faster, the player that choose the shorter paths to new foods will ultimately win the game. If a non-intrusive mechanism that compensate the loosing snake is introduced, the game may be really fun!

If a snake unexpectedly turns into the path of another one, the other snake will crash and die if the player that controls it is not ready for this sudden change. If two snakes arrives to the same position at same time, the program logic moves the lower numbered one first; this detail may produce different results depending on other factors. For example, in a frontal collision of two snakes, if the number of positions between the snakes is odd, the lower numbered snake will arrive first to the center/collision point and the other snake will crash into it and die, but if the number of separating positions is even, then the lower numbered snake will crash into the other one and it will die instead. A large snake may kill another one if encloses it in a trap, so the other snake have not any free space to keep moving. All these situations may be further enriched or avoided introducing additional features in the game. I selected my own ideas for the method used to implement these features, like the possibility to stop or reverse the snake movement, or introducing different types of food that provides the snakes with new abilities.
Top

Manual adjustments

The environment in which VIBORAS.bat program run requires a few manual adjustments in order to get the best experience. Although some adjustments could programatically be completed via code, the final result could not be satisfactory, so it's not worth to write such a code.

The main adjustments are the font selected in the cmd.exe window and the size of the playing board, but these adjustments depends on each other. Smaller fonts allows to use larger boards, but it could be difficult to visually identify individual lines and columns with such a small characters. Larger fonts are easier to view, but they limits the size of the board. The maximum possible size for the board is 128 columns and 63 lines. Besides, these adjustment also depends on the maximum resolution of the screen in pixels. The cmd.exe window must not have scroll bars.

You should always select a square font, like the preinstalled 8x8 raster font. You may also use "Terminal 12x12.FNT" or "Terminal 16x16.FNT" raster font files included in the VIBORAS.zip game package. To install these files, copy them into "\Windows\Fonts" folder and answer Yes to "Terminal font is already installed. Replace it?" question, but before doing that be sure to unselect in the cmd.exe window any font that will be replaced.

The definitions of the keys that control the snakes can be easily changed; see the :Input section in the code for details. The key placed at right of "L" letter may require an adjustment in your keyboard. This key is ";" semicolon (code=188) in English keyboards or "Ñ" letter (code=192) in Spanish keyboards. To know the code of any key in your keyboard, use the VirtualKeyCode.bat program included in the VIBORAS.zip game package.
Top

Movement control/modes

The basic snake movement control is turn its direction to right or left when the Righ-Arrow or Left-Arrow keys are pressed, respectively, like in the original game. The snake's speed is slightly incremented each time that a food is eaten, like in the original game. Other movement control is possible via Up-Arrow and Down-Arrow keys that selects different movement modes, as described below:

Turbo: twice the Normal speed ("Turbo" indicator appears).
Down - Up (this up key requires a Turbo-food, see below)
Normal: current speed, no indicator. <- The game start here.
Down - Up  
Slow: half the Normal speed ("Slow" indicator appears).
Down - Up  
Stop: snake don't moves ("Stop" indicator appears).
Down (this down key requires a Reverse-food, see below)
Reverse: snake moves in reverse direction at Slow speed, and continue.

Each press of Up-Arrow key change the movement mode in this order:

Stop Slow Normal Turbo; the last one only works when the snake have a Turbo-food.

Each press of Down-Arrow key change the movement mode in this order:

Turbo Normal Slow Stop Reverse; the last one only works when the snake have a Reverse-food.

When a snake is in Stop mode, each press of Left-Arrow or Right-Arrow keys moves the snake one position in such a direction. If any food is eat in Turbo, Slow or Stop modes, the movement mode automatically reverts to Normal.
Top

Types of food

This is a description of the different types of food designed for the current version of VIBORAS.bat game.

NOTE: The following types of food are not yet implemented in the current version of VIBORAS.bat program.


Top

Ways to show the foods

There are several ways to make the different types of food to appear. To do that, a series of values must be placed in a game specification file following some rules and then such a file must be used to start the game. However, there is another method to create a specification file in line via a menu that show the game options in a simpler way. All you need to do is answer "Yes" to "Do you want to define the game specification parameters?" question and continue from that point on.

Of course, it is convenient to know the meaning of the menu options; however, you may just create a specification file via the menu and start the game, and then create another specification file with different values until you understand how each of the menu options work. After the first specification file was created, it is much simpler to directly modify it via Notepad Windows text editor instead of repeat the whole menu again. To enable the values of a specification file, just drag and drop it over VIBORAS.bat program file.

If you use this "change-and-test" method, then you may omit the reading of the rest of this manual. The following is a detailed description of the meaning and way to define each one of the values in a game specification file.

For the value definition mechanism, the foods are divided in two classes.

  1. Constant Foods. This class, comprised of StdFoods and ResFoods (Standard and Reserved foods), are enabled via a number of foods that remains constant; this means that each time that a food of this class is eaten, a new food of the same type will appear. For example:
    1. Snakes=1, StdFoods=1 is the definition of the original Snake game.
    2. Snakes=2, StdFoods=1 are two snakes fighting for just one food each time.
    3. Snakes=2, StdFoods=2 are two snakes fighting for two foods, that may be eaten by any snake.
    4. Snakes=2, ResFoods=1 are two snakes, each one eating its own food. No fight for food.
    5. Snakes=2, StdFoods=1, ResFoods=1 are two snakes, each one with one food reserved for it, plus one common food to fight for.
    6. Snakes=3, StdFoods=1 are three snakes fighting for just one food each time.
    7. Snakes=3, ResFoods=1 are three snakes, each one eating its own food; no fight for food.

    Et cetera: any other combination in the number of Snakes, Standard and Reserved foods works in the same way.

  2. Variable Foods. This class is comprised of the rest of foods, including StdBoost and ResBoost (Standard-Boost and Reserved-Boost) that are options of Standard-foods and Reserved-foods, respectively.

    The foods in this class appear on the screen at variable time intervals that are specified based on other conditions. The way to define these conditions is via a SET /A arithmetic expression, called food expression, that may use some dynamic variables like the predefined random one or other interface variables designed for this purpose.

    The number returned by these expressions work as indicator for the foods: a value greater than zero means Enable and any other value means Disable, but in certain cases the expression also gives the value of the food, like in Lapse and Wall foods.

    Normally the created food is a common white-color one, but if the food expression include a value greater than zero in justFor variable, then the created food will be reserved just for such a snake and it will be displayed in the same color. If the value of justFor variable is greater than 4, a reserved food will be created for each snake, but Lapse food can not be of this type.

    If a snake try to eat a reserved food that don't belongs to it, it will be considered an obstacle and the snake will crash into it.

    Normally the created food will remain in the board until a snake eats it, but if the food expression include a value greater than zero for timeOut variable, then such a value indicate the number of seconds that the food will remain in the board.


Top

Food expressions

To enable the other types of foods and features you should assign to the desired variable a SET /A arithmetic expression, that is, the part placed after SET /A, enclosed in quotes. In this expression a single % percent-sign means the modulo (remainder) operator, but the ! exclamation-mark character requires a special treatment due to the method used to inject these expressions in the running program. If you want to expand the value of a variable use a delayed expansion in this way: ^!variable^!, although this is only required in certain cases like the Batch predefined dynamic variables. To use the ! (boolean not) operator in an expression, use this construct: ^^^!.

The food expression may use other features, like conditional expressions and interface variables.

Conditional expressions

These are numeric expressions that gives an equivalent result of individual IF conditional commands.

Conditional command Equivalent expression
IF (A equ B) R=then ELSE R=else cond=^^^!(A-B), R=cond*then+^^^!cond*else
IF (A neq B) R=then ELSE R=else cond=^^^!^^^!(A-B), R=cond*then+^^^!cond*else
IF (A geq B) R=then ELSE R=else cond=(A-B>>31)+1, R=cond*then+^^^!cond*else
IF (A lss B) R=then ELSE R=else cond=^^^!((A-B>>31)+1), R=cond*then+^^^!cond*else

To perform the gtr and leq missing conditions, adjust one operand and use another given condition. For example, to get a IF (A gtr 10) condition, just use the equivalent IF (A geq 11) expression.

You may also combine two or more conditions via & (AND) and | (OR) bitwise operators. For example, this condition: IF (A equ B and C geq D) can be written in this way: c1=^^^!(A-B), c2=(C-D>>31)+1, cond=c1&2 or the simpler cond=^^^!(A-B)&(C-D>>31)+1.

For further details on the operators that can be used in these expressions, review the description of SET /A Batch command.

Interface variables

Besides random and time predefined Batch variables, you may use the following VIBORAS.bat inteface variables in the food expressions:

Variable Description
init Initialize persistent variables. See below.
seconds Game elapsed time in seconds.
size[1..4] Array with the size of each snake.
del[1..4] Array with the delay (1/speed) of each snake.
maxSize Size of the largest snake.
minSize Size of the smallest snake.
maxSnake Index of the largest snake.
minSnake Index of the smallest snake.
T[1..4] Array with the number of Turbo-foods of each snake.
W[1..4] Array with the number of Wall-foods of each snake.
R[1..4] Array with the number of Reverse-foods of each snake.
justFor Reserve the food just for a certain snake.
timeOut Number of seconds that the food will remain in the board.
eggSize Size used to create eggs for Snake reproduction.
exit Specify when exit a game and what to do with crashed snakes.

Values of exit variable:

exit = Exit the game when: What to do with crashed snakes:
0 The first snake crashes.  
1 The last snake crashes. Remove all from board.
2 The last snake crashes. Preserve the last crashed snake of each color.
3 The last snake crashes. Preserve all in board.

If the expression uses additional auxiliary variables, it is suggested to select a1,a2,a3,... names for temporary variables and n1,n2,n3,... names for persistent variables that will keep their values between program cycles; you may use init interface variable to initialize persistent variables when the game begins. For example: set "init=n1=0, n2=10, n3=0".

Note that when several variables are assigned in a long expression, the final result is the value of the first variable; for example: set "AnyVar=a1=10,a2=20,a3=a1+a2" sets AnyVar to 10. To get the value of the last variable, just enclose the whole expression in parentheses: set "AnyVar=(a1=10,a2=20,a3=a1+a2)" sets AnyVar to 30; note also that the last variable is not needed: set "AnyVar=(a1=10,a2=20,a1+a2)"

Some examples

StdBoost is an option of Standard-foods. When a Standard-food appear, it may appear as a Boost-food instead, that is, with a value greater than one; this behavior is controlled by the value given by StdBoost food expression.

  1. set "StdBoost=0" Is the same as not include it: the Standard-foods will always appear with a value of 1.
  2. set "StdBoost=6" All Standard-foods will appear with a value of 6.
  3. set "StdBoost=^!random^!%10" Standard-foods will appear with a random value between 1 and 9.
  4. set "StdBoost=(a1=^!random^!%10, cond=(a1-5>>31)+1, cond*a1)" This condition uses the IF (A geq B) equivalent expression described above. Half of the Standard-foods will have a value of 1, and the other half will have a random value between 5 and 9. Note that cond variable is not needed: set "StdBoost=(a1=^!random^!%10,((a1-5>>31)+1)*a1)" do the same.
  5. set "StdBoost=seconds/60" The boost value increases every minute of playing time.

Remember that any boost value will be automatically limited to 9.

IMPORTANT: Note that, unlike StdBoost and ResBoost variables that are evaluated just when a new food is needed, the rest of variables are re-evaluated once in each program cycle, that is, several times per second. This means that it is necessary to include some kind of repeat control in the expression of those variables; otherwise a lot of new foods will be created until the initial condition change.

For example, suppose that a Turbo-food should be created every 3 minutes. Apparently, this is simple: set "Turbo=^^^!(seconds%180)", that is, get the seconds%180 remainder of the division of current seconds by 3 minutes (that is zero every exact multiple of 180 seconds) and apply ^^^! boolean NOT operator to it, so it gives 1 in this case and 0 with any other remainder. Easy! Isn't it? ;).

However, this expression will create a new food in each program cycle that is executed in the same second! As a matter of fact, this is a very simple way to know how fast a computer is: just count the number of foods that were created in one second (in my old and slow computer this number was 36).

The way to fix this point is via an additional condition that prevents to create more foods until a reset condition occurs; the additional condition should be managed in a persistent variable and should not conflict with the original condition. In this case, the reset condition could be the opposite one of the original condition. This is a way to assemble such expression:

In this way, after a new food was created, the value of n1 must stay in 0 until the remainder be not zero again. One way to express such a condition is this: n1=n1-c2+^^^!n1*^^^!c1, that is, after n1=1 was initialized, this formula keep such a value for 3 minutes. Then, the first time that c2 is 1, this formula changes the value of n1 to 0. After that, the value of c2 will be 0 and the value of ^^^!n1*^^^!c1 will also be zero all the times that this formula be repeated in the same second. When the second changes, c1 changes to zero, so ^^^!n1*^^^!c1 part will give 1. At that moment, the formula changes n1 to 1 again.

The final food expression must be written in this way:

set "init=n1=1"
set "Turbo=( c1=^^^!(seconds%180), c2=n1&c1, n1+=^^^!n1*^^^!c1-c2, c2 )"


Now suppose that a new Turbo food must be created just for the smallest snake when the difference in size between the smallest and the largest snakes be 10 or greater; the creation of new foods must be cancelled until this difference falls below 8. Remember that the value assigned to justFor variable specify to create a reserved food just for that snake. The expression to do that can be assembled based on the following subexpressions:

Note that c2 and c4 may be both 0 or just one can be 1; they can not be both 1 at same time. In this way, the updated value of n1 is given by: n1=n1-c2+c4; that is:

The final food expression for this reserved food can be written in this way:

set "init=n1=1"
set "Turbo=( a1=maxSize-minSize, c1=(a1-10>>31)+1, c2=n1&c1, c3=^^^!((a1-8>>31)+1), c4=^^^!n1&c3, n1+=c4-c2, justFor=minSnake, c2 )"

The way to activate all these features is placing the desired food expressions in a file with .txt extension and then run the VIBORAS.bat program with the name of such a file as parameter, or just drag such a file and drop it on VIBORAS.bat program. There are some examples of *.txt game specification files included in the VIBORAS.zip game package; note that most files are not intended to play games, but to teach the way to write common features.

The simplest way to understand how all these features work is just modify the value of some expressions in a game specification .txt file and play the game again until the purpose of each feature be understood. In this way, you may write your own game specification files that fulfills your desires or needs.
Top

Snake reproduction

A snake may reproduce using the following procedure:

  1. The reproduction process can start after the snake have a size larger than eggSize+5.
  2. When the player press the key at right of Up key (PageUp in standard keyboards) the snake lay an egg at the tail position, its body is reduced by eggSize size and its speed is reduced to half. The egg is displayed as just one segment that points to the direction that the daugther snake will have.
  3. The daughter snake will hatch out from the egg after its mother dies, that is, immediately after the mother crashes or until its whole body was removed accordingly to the value of exit variable.
  4. The daughter snake will have half the speed and size of the segment that created the egg, and the same direction.
  5. If a Coral snake reproduces, the daugther snake will also be a Coral one.
  6. A snake may create just one egg.
  7. A snake may eat its own egg; the snake recovers half the size used to create the egg.
  8. If a snake try to eat a not own egg it will crash into it, excepting a Coral snake that can eat any egg.

Snake reproduction is enabled giving a value greater than zero to both eggSize and exit variables in a game specification file. If a value is giving just to eggSize the game will end when the first snake crashes even if there are eggs in the board, so it is necessary to also give a value to exit in order to give the eggs a chance to hash out.

The bodies of crashed snakes will be obstacles for all remaining snakes. If a snake crashes into itself, its body will be removed just from the tail until the colision point, so the rest will enclose a closed area in the board. If exit=3 this point can be used as a game strategy; for example, to permanently enclose the food or egg of another snake.
Top

Technicall description of VIBORAS.bat

This program was developed with the goal of made the code and logic as short as possible, making good use of complex data structures. These are the relevant points in the program design:

  1. The maximum size for the board is: width=128 and height=63. Board positions are managed in a single number with both coordinates combined: horizontal coordinate in bits 6..0 (7 bits: 0-127 range) and vertical coordinate in bits 12..7 (6 bits: 0-63 range), as shown in this scheme:
    Coordinates:     VerticalHorizontal
    Bits:             12....76.....0      Vertical: 6 bits, Horizontal: 7 bits
    Last position:     1111101111111      Vertical=62, Horizontal=127
    Hexadecimal:       1   F   7   F      Decimal=8063
    

    This means that all board positions can be represented by a single number in the 0..8063 range. The first board position, at coordinates (0,0), is represented by number 0. The last board position, at coordinates (127,62), is represented by number 8063 as shown in previous scheme. The conversions formulae are: SET /A "position=(row<<7)+col" and SET /A "row=position>>7, col=position%%128".

  2. The contents of all positions in the board are stored in a string with up to 8064 characters, that are taken from this list: space = Free position, "X" letter = Frame, digit 1..9 = Standard/Boost food, "T" letter = Turbo-food, "R" letter = Reverse-food, "W" letter = Wall-food, "C" letter = Coral-food, "L" letter = Lapse food, anyone of "<>^V" characters = Egg, and "#" character = Snake body. This string variable, called "board", is used to know the contents of the next position where the snake head will move. The program change the contents of this variable each time that a new food appear or when the snake head/tail moves to a new position. A direct substring extraction is used for this management.
     
  3. A list of free board positions is stored in a string that can hold up to 1632 positions of 4 digits plus one space each. This string variable, called "free", is used to select a random position for the next food in a single operation. When the program start, this string is populated with a sample of 1632 evenly distributed positions taken from the total if the board have more than 1632 positions, or with all positions otherwise. Each time that the snake head is moved to a new position that exists in "free" variable, such a position is removed from it; substring replacement is used for this management. When the "free" variable have less than 1632 positions, the last position of the snake tail is inserted into it.
     
  4. The special cases of food or snakes are managed creating array elements with the position as subscript and the special type as contents. When a snake arrives to a position, the corresponding element of these arrays are tested via a IF defined array[%headPos%] ... command, and such an element is deleted after processed. For example, when a reserved food appears, an element of "resFood" array is created: SET "resFood[%position%]=%snake%"; the same is done with the other special types.
     
  5. The representation of a position in a single number allows to move it using just one operation, instead of two separate operations for ROW and COL parts. The four possible movements are +- 1 for horizontal coordinate, and +- 1<<7 (128) for vertical coordinate: SET /A "toRight=1, toLeft=-1, toDown=128, toUp=-128". This also means that any distance between two positions in the same line or row can be stored as a single number that includes the direction (vector). For example, 5 means "5 positions to right", -10 is "10 positions to left", 1280 is "10 positions to down", etc.
     
  6. Snake movement is managed via four variables: headPos, headMov, tailPos and tailMov; these variables contain single numbers as explained before. The snake can be moved in a single operation in this way: SET /A "headPos+=headMov, tailPos+=tailMov".
     
  7. Turns in snake direction are managed by a direct selection of an array element based on current movement and the key pressed; for example: SET /A "newMov=turn[%oldMov%,%keyPressed%]". Turn array is initialized this way: SET /A "turn[%toRight%,%RightKey%]=toDown, turn[%toRight%,%LeftKey%]=toUp,
    turn[%toUp%,%RightKey%]=toRight, turn[%toUp%,%LeftKey%]=toLeft", etc.
     
  8. The snake body is stored as a series of "nodes" that marks each direction change. Each time that the snake head change its direction, a new node is created at such a position with the new direction: SET "node[%headPos%]=%headMov%". Each time that the snake tail reaches a node position: IF defined node[%tailPos%], the tail movement is changed: SET /A "tailMov=node[%tailPos%]" and the node is deleted. This method allows to store a large snake in a few nodes.
     
  9. Each node also stores the distance to the previous node, so all nodes form a linked-list that starts at the head and advances toward the tail. This scheme allows to process all nodes in a very fast way when such a feature is required, like when change the snake movement to reverse direction via a Reverse-food.
     
  10. The eggs are managed as array elements that contain all the required information for the new daugther snake that is "inherited" from its mother at the moment the egg is created. When the daugther snake hatches out, the egg variable is deleted.

Note that all visual elements of the game are displayed on the screen in the proper colors using the PowerShell code as explained elsewhere. This means that it is not necessary to store the screen contents in a variable. There are several auxiliary variables that aids to complete this display, like the color of each snake, the characters used to display the snake bodies, etc.

Most of these points were already implemented in the first version of my Snake.bat program; you may review it above this post. These data structures allows a simpler management of one snake via a few single number variables, so it is just necessary to convert these variables into arrays in order to manage several snakes. As a matter of fact, this program is capable of animate many snakes with no problems (about 30-40 snakes if we take the Spiral in color program as comparison), so the real limitation in this case is the way to give input keys to control such an amount of snakes with no interference among them. The current version of this program responds with no problems to the simultaneous requests of up to four players as long as the keys are pressed and released in a quick and natural way.

Note that this is work in progress and that the current VIBORAS.bat program is a preliminary version. There are several planned features that are not yet implemented. Also, there are several obvious features that are missing, like the record of highest scores or the storage of a game in a disk file in order to continue it later. All these features should appear in future versions.


Antonio