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.
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
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
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. |
⬇ 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. |
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
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.
There are several ways to make the different types of food to appear. For this mechanism, the foods are divided in two classes.
Snakes=1, StdFoods=1
is the definition of the original Snake game.
Snakes=2, StdFoods=1
are two snakes fighting for just one food each time.
Snakes=2, StdFoods=2
are two snakes fighting for two foods, that may be eaten by any snake.
Snakes=2, ResFoods=1
are two snakes, each one eating its own food. No fight for food.
Snakes=2, StdFoods=1, ResFoods=1
are two snakes, each one with one food reserved for it, plus one common food to fight for.
Snakes=3, StdFoods=1
are three snakes fighting for just one food each time.
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.
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.
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.
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.
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. |
eggSize | Size used to create eggs for Snake reproduction. |
exit | Specify when exit a game and what to do with crashed snakes. |
exit = | Exit the game when: | The crashed snakes: |
---|---|---|
0 | The first snake crashes. | |
1 | The last snake crashes. | Are removed from board. |
2 | The last snake crashes. | Just the last crashed snake of each color is kept. |
3 | The last snake crashes. | All previously crashed snakes are kept. |
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)"
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.
set "StdBoost=0"
Is the same as not include it: the Standard-foods will always appear with a value of 1.
set "StdBoost=6"
All Standard-foods will appear with a value of 6.
set "StdBoost=^!random^!%10"
Standard-foods will appear with a random value between 1 and 9.
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.
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:
n1=1
Initialize: the value of n1 will enable/disable the creation of new foods.
c1=^^^!(seconds%180)
Condition: is 1 every time multiple of 3 minutes.
c2=n1&c1
Condition: is 1 when a new food must be created, that is, when new foods are enabled (n1=1) and the time is multiple of 3 minutes (c1=1). At same time, the creation of new foods must be disabled via n1=0
.
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:
n1=1
Initialize: the value of n1 will enable/disable the creation of new foods.
c1=((maxSize-minSize)-10>>31)+1
Condition: is 1 when the difference in size is greater than 10.
c2=n1&c1
Condition: is 1 when a new food must be created, that is, when new foods are enabled (n1=1) and the difference in size is greater than 10 (c1=1). At same time, the creation of new foods must be disabled via n1=0
.
c3=^^^!(((maxSize-minSize)-8>>31)+1)
Condition: is 1 when the difference in size is less than 8.
c4=^^^!n1&c3
Condition: is 1 when the creation of new foods must be enabled via n1=1
, that is, when new foods are disabled (n1=0) and the difference in size is less than 8 (c3=1).
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:
n1=0
.
n1=1
.
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
A snake may reproduce using the following procedure:
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
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:
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"
.
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.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.SET /A "headPos+=headMov, tailPos+=tailMov"
.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.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.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