Viliam Búr

2008/08/25

Platform Game in Four Days

I was invited to be a juror in a programming competition Špongia. The competition is about programming computer games in several days. To understand how contestants feel, I challenged the organizer Dano Lovásko for a programming battle. One against one, time limit 4 days, more precisely 3½, topic "platform game", programming language of own choice. We have not found yet an independent jury to judge our results, but this is not so important. I am happy to have tried this, and here is my experience.

First a few words about the competition. Špongia is subtitled "sport-like programming of graphical interactive applications that don't repel the eyes", which means programming of computer games with emphasis on originality, playability, and aesthetic impression. The competition was established in year 2006 by students of Grammar school for extremely gifted children in Bratislava. In year 2007 students of two other grammar schools joined. (If you are a student of primary or secondary education, and would like to try this, please write me a comment.)

Typical programming competitions are focused on writing effective algorithms, and for a layman they are uninteresting and unintelligible. In Špongia the algorithm is only a tool for achieving the goal, and evaluation criteria are comprehensive for a layman (gamer). But this does not mean easier work for the programmer. Contrariwise, s/he must additionally process input from a keyboard and mouse, and display nice animated graphics. Participants come in teams up to 6 members. Larger team is not necessarily at advantage; it is more difficult to coördinate coöperation. Nonprogrammers can contribute by graphics and music. In the previous year the participants had 18 days to create a game. The exact settings are published at the beginning of the competition, in October. The previous years' topics were "water" and "puzzle".

(The homepage of Špongia is only in Slovak language.)

My advantage compared with the participants is 10 years of professional programming, preceded by another maybe ten years of amateur programming. On the other hand, I wrote a computer game last time maybe twelve years ago in Turbo Pascal programming language under MS DOS. Since then I have been mostly writing scipts, libraries, and websites. It was not a problem to study some graphical interface, but I had few time and lot of work. And I knew I would spend one day by some extra-computer activities; this is not an excuse, also the participants are normally attending school during the competition.

I chose the Java programming language. I wanted to write in a language I am very familiar with, so as the technical details would not hinder my coding. This could have been Java or Perl, but I never wrote an interactive graphical application in Perl, and Java has a great development environment Eclipse. I do not know how to make sound in Java, but because I cannot compose music and I did not want to waste time by looking for a nice free music on the internet, I decided that the game would be without sound. It decreases the overall impression, but I would have to balance it by something else.

As first step I created a screen 800×600 pixels large, which could be run in an application window or in a web page, and it would display a colored rectangle. Just for a good feeling that I already have a first advantage against my opponent coding in C++, and because it is relatively easy to do it in Java.

Then I painted a simple stick figure in graphical editor MS Paint. In game I loaded the figure from a file and displayed in on the screen. I added movement using keyboard arrows, so the figure was floating in the air. The screen with figure was repainted 50 times per second; the figure was blinking, so I added a virtual screen, where it was always removed and painted at another place, and only then was the result copied on the real screen. To add some content I painted a blue-green picture as a background, and some brick walls built from square tiles. The figure was still freely floating above the walls.

Before painting more pictures I wanted to decide how large should the figure be, and how quickly should it move. This decision would be easy to change later in program, but it would require painting new pictures and designing new rooms, which would be a waste of time; it was already a middle of the second day of four. When I was programing games twelve years ago, I always moved the figure by one pixel per 1/50th of second, which produced a nice fluent movement. But to move 800 pixels it would take 800÷50= 16 seconds, which seemed too much. But when I tried speeds larger than one pixel, the movement seemed like below a stroboscope. What should I do?

If the figure will move quickly, I probably cannot paint it using one pixel wide lines. So I used vector editor Inkscape, I constructed the figure from curves, and exported it as a bitmap. And despite using only the most simple shapes again, it looked much better than its previous version. It even had semitransparent pixels at the edges of curves, so it looked smooth on any background. It was obvious that I would paint all pictures like this.

(C) 2008 Viliam Búr viliambur.blogspot.com

I decided the dimensions of the figure to be 40×80 pixels, and I built the terrain as a mosaic of 40×40 pixels large tiles, which divided the area 800×600 pixels to a grid of 15×20 parts. In horizontal movement I let the figure gradually accelerate up to 7 pixels per 1/50th of second, which seemed fast enough and relatively smooth. Vertically it jumped at 16 pixels speed, to let it jump up three rows of the grid under gravitational acceleration of 1 pixel. I gained these values by experimenting until the movement of the figure seemed right.

In addition to walls I made a grassland. I had to completely change the collision algorithm. The wall is impermeable for the figure; it cannot fall through it, pass it from the side, nor penetrate it by head. The grassland is semipermeable; if the figure is above it, it cannot fall down through it, but if it is standing lower, it can walk in front of it, or jump on it from below. The wall right above the head is like a ceiling, which cannot be passed and must be circumvented; the grassland right above the head is like a step of the ladder, which can be jumped on.

I was pleasantly surprised that the whole screen can be refreshed 50 times per second. My computer could do it even 65 times per second, and this is a two years old cheaper machine (1 GB memory, 1.6 GHz processor). I knew that Java was a slow language, and I was planning to improve the program so that each 1/5th of second only the modified parts of the screen would be painted; but I postponed the improvement, and was surprised that the program works quickly anyway. Later I modified the program, just for the good feeling that I was not wasting computer power. Only after the programming battle was over and the results were sent, I have discovered that there was a logical mistake in that modification, so the whole screen kept always repainting yet. No problem, at least it worked. ;-)

I added an enemy snail, which moves using the same algorithm as the figure, only slower, and it cannot jump. I added apples collectable by the figure. I planned to add more enemies and collectable items later, but then I had no time for it. I also added a flying platform, which functions like a grassland, but it is moving horizontally, so the figure must walk on it carefully not to fall down.

During the last day I had less success, because I was afraid of the coming deadline, I was tired of programming, and I was looking forward to rest after the end. I extended the screen to 820×660 pixels; the inner 800×600 pixels contained the game, and there was a frame around with bottom part where I planned to display collected keys and other special items. If the figure collected all items, another room started; if it was touched by a snail, the room restarted. I added the commencing and final screens. It took me a few hours, so then I did not have enough time for programming the planned keys and unlockable doors, so some of these adaptations were useless for the competiton. The texts at the start and finish were saved like pictures; it is a waste of disk space, but it was the fastest way to do it. Originally they had to be in Slovak and English languages, but when Inkscape failed to display Slovak characters correctly, I wrote only the English text, because I had no time to find a fix.

The night started, so I quickly designed five rooms; in the first four I quickly introduce the game elements, and the last fifth one is difficult, to make some troubles to player, not to make the game seem too short. The rooms were edited directly into the source code; I did not have time to write a room editor. I zipped the result and sent it by e-mail.

So the programming battle is over, but I hope I will later find some time to finish this game, and possibly make some other games. When I consider how much have I learned during those years, and how powerful are computers today, it is a great challenge. (Today you could write in Java without problems a game like Warcraft 2.) And I am looking forward to Špongia 2008, where I cannot participate officially, but I will try to find some graphic designer to help me with an unofficial extracompetitive contribution.

I think that every individual or team who wants to create a game should try something like this. There are too many game projects on the internet which started by creating a web page, writing a story, and painting some example pictures, followed only by messages like "we are working on it, wait a moment", and possibly a few years later final message like "we are not working on the project anymore, it was postponed indefinitely". This sport-like style of programming will mercilessly check your team abilities, find your weaknesses, and show you what kind of result can you expect in what time. Regardless of what kind of game you are trying to make, try spending the first week making a complete microdemo. It is worth to sacrifice one week to find out if the following years will not be wasted.

The game "Platform 2008" (I could not find better name) can be downloaded from SourceForge.net under license GNU GPLv3 including the source code.

2008/08/10

Water Battle Map for Wesnoth, Improved

In previous two articles we have designed a multiplayer scenario for water battle, called "2p - Pool", where one player can recruit only Merfolk units, and other player can recruit only Naga and Saurian units; the map was published on Wesnoth add-on server. In the last article we will move the choice between Merfolk faction and Naga faction inside the scenario. Each player will decide by moving his/her leader into one of two available keeps; this will allow also Merfolk vs Merfolk and Naga vs Naga combats.

Please note: A more "natural" way of choosing Merfolk faction or Naga faction would be to create two multiplayer factions, and then to create a multiplayer era containing these two factions. When starting a multiplayer game, the host could choose this (or any other) era for playing this (or any other) scenario. But we will do it differently, because we want to use these factions only for this scenario, and because this is a scenario logic tutorial, not an era tutorial.

Scenario logic is implemented using events. Events are commands in form: "when this happens, do that". On Wesnoth wiki pages you can find a list of possible commands, in this article only the ones used will be explained.

Let's start by putting labels on the keeps, saying "this is a Merfolk keep" and "this is a Naga keep". We will use the scenario file "2p_Pool.cfg" created in the previous article, and add events to the end of the file.

[multiplayer]

 # ...here is what we wrote in the previous article...

 [event]
  name=prestart
  [label]
   x,y=4,5
   text=_"Merfolk"
  [/label]
  [label]
   x,y=4,9
   text=_"Nagas"
  [/label]
  [label]
   x,y=30,5
   text=_"Merfolk"
  [/label]
  [label]
   x,y=30,9
   text=_"Nagas"
  [/label]
 [/event]

[/multiplayer]

The "[event]" and "[/event]" marks mean "this is the beginning / the end of event description". The "name" specifies when should this event happen; value "prestart" means "before the start of scenario, before anything is displayed". This is the right moment to do last-minute map changes. The remaining marks are the commands executed when the event is triggered, that is before the scenario starts.

The "[label]" and "[/label]" marks define command "put a text label on a hex on map". The "x" and "y" are coordinates of the hex. (The coordinates are displayed at the top right corner of screen, both in map editor and in game.) The "text" is a text of label. The text is preceded by "_" symbols to allow translating it to other languages later.

This is the most simple way to write the script. But it may not be so easy to maintain, especially when the script becomes longer. It is not obvious that "4,5" are the coordinates of player 1 Merfolk keep; and if map designer would choose to move the keep to another place, it would not be obvious which parts of the script should be updated. This can be solved by creating a macro called "KEEP_P1M", which will represent the location of player 1 Merfolk keep, but can be defined in one place and used repeatedly. Macros can be defined in any part of the script before their first use, so we can put this macro before the events, or at the beginning of the file. The version with macros will look like this:

#define KEEP_P1M
x,y=4,5
#enddef

#define KEEP_P1N
x,y=4,9
#enddef

#define KEEP_P2M
x,y=30,5
#enddef

#define KEEP_P2N
x,y=30,9
#enddef

[multiplayer]

 # ...here is what we wrote in the previous article...

 [event]
  name=prestart
  [label]
   {KEEP_P1M}
   text=_"Merfolk"
  [/label]
  [label]
   {KEEP_P1N}
   text=_"Nagas"
  [/label]
  [label]
   {KEEP_P2M}
   text=_"Merfolk"
  [/label]
  [label]
   {KEEP_P2N}
   text=_"Nagas"
  [/label]
 [/event]

[/multiplayer]

Scenario start screenshot

The next event will be the selection of the keep. Let's write the code for player 1 selecting the Merfolk keep. When a unit belonging to player 1 goes to the player 1 Merfolk keep, player 1 will be allowed to recruit Merfolk units, and the player 1 Naga keep will be destroyed.

[multiplayer]

 # ...here is what we wrote previously...

 [event]
  name=moveto
  [filter]
   side=1
   x,y=4,5
   [filter_location] 
    terrain=Khw
   [/filter_location]
  [/filter]
  first_time_only=yes

  [set_recruit]
   side=1
   recruit=Merman Fighter,Merman Hunter,Mermaid Initiate
  [/set_recruit]
  [label]
   x,y=4,9
   text=""
  [/label]
  [terrain]
   x= 3, 3, 4, 4, 4, 5, 5
   y= 9,10, 8, 9,10, 9,10
   terrain=Wo
  [/terrain]
  [terrain]
   x,y=4,7
   terrain=Ww
  [/terrain]
 [/event]

[/multiplayer]

The event type "moveto" is triggered after a unit is moved into a situation described by "[filter]". (The unit must end its move there, not only move through.) Because such situation can possibly happen many times, the "first_time_only" specifies if the event is triggered only in first such situation, or in each such situation. We want to select the keep only once.

The "[filter]" and "[/filter]" marks specify the situation when the event is ready. The "side" means that is must be after move of unit belonging to player 1. The "x,y" means that the unit must have moved into hex 4,5 (the player 1 Mefolk keep). The "[filter_location]" specifies further conditions for the terrain where the unit ended its move; it must be a "Khw" terrain, which is code for a water keep. (Why do we check the presence of the keep after we wrote that the hex must be 4,5? This is a trick to prevent selecting already destroyed keep. If player 1 selects the Naga keep, the Merfolk keep will be destroyed; if later a unit goes to hex 4,5, there will be no keep terrain, so this event will not be triggered.)

The "[set_recruit]" and "[/set_recruit]" marks define command to change the recruitment list; player 1 will be able to recruit only Merfolk units. The "[label]" and "[/label]" marks put a text label on the map; it this situation they rewrite already existing label ("Nagas" at player 1 Naga keep) to an empty string, this means removing the label.

The "[terrain]" and "[/terrain]" marks define command to change map terrain; the "x,y" is the position of hex which should be changed, and the "terrain" is the code of the new terrain. It is also possible to specify multiple positions by providing a list of x-coordinates and a list of corresponsing y-coordinates; so the changed hexes in this example are 3,9, 3,10, 4,8, 4,9, 4,10, 5,9, and 5,10. The terrain code "Wo" means deep water; we will sink the other castle deeply. The terrain code "Ww" means shallow water; we will just remove the bridge at the starting location.

Now again, this piece of script would be easier to maintain using some macros:

[multiplayer]

 # ...here is what we wrote previously...

 [event]
  name=moveto
  [filter]
   side=1
   {KEEP_P1M}
   [filter_location] 
    terrain={TERRAIN_KEEP}
   [/filter_location]
  [/filter]
  first_time_only=yes

  [set_recruit]
   side=1
   recruit={UNITS_MERFOLK}
  [/set_recruit]
  [label]
   {KEEP_P1N}
   text=""
  [/label]
  [terrain]
   {CASTLE_P1N}
   terrain={TERRAIN_WATER_DEEP}
  [/terrain]
  [terrain]
   {START_P1}
   terrain={TERRAIN_WATER}
  [/terrain]
 [/event]

[/multiplayer]

We need 4 such events: player 1 selecting Merfolk keep, player 1 selecting Naga keep, player 2 selecting Merfok keep, and player 2 selecting Naga keep. We could make 4 copies of this event and make the necessary changes. Or we could change it to macro (called SELECT_FACTION) with parameters. Macros can be written once and used repeatedly. The parameters are the parts of macro which are changing.

In this example the changing parts of macro are: the number of player, 1 or 2 (let's call it SIDE); the coordinates of the selected keep (let's call it KEEP); the list of recruitable units (UNITS); the coordinates of the other keep for removing the label (OTHER_KEEP); the coordinates of the other castle and keep to be sunk (OTHER_CASTLE); and the starting location of the player (START). So the macro version of this event will look like this:

#define SELECT_FACTION SIDE KEEP UNITS OTHER_KEEP OTHER_CASTLE START
 [event]
  name=moveto
  [filter]
   side={SIDE}
   {KEEP}
   [filter_location] 
    terrain={TERRAIN_KEEP}
   [/filter_location]
  [/filter]
  first_time_only=yes

  [set_recruit]
   side={SIDE}
   recruit={UNITS}
  [/set_recruit]
  [label]
   {OTHER_KEEP}
   text=""
  [/label]
  [terrain]
   {OTHER_CASTLE}
   terrain={TERRAIN_WATER_DEEP}
  [/terrain]
  [terrain]
   {START}
   terrain={TERRAIN_WATER}
  [/terrain]
 [/event]
#enddef

And we will call it like this:

#define UNITS_MERFOLK
Merman Fighter,Merman Hunter,Mermaid Initiate#enddef

#define UNITS_NAGAS
Naga Fighter,Saurian Augur,Saurian Skirmisher#enddef

#define TERRAIN_KEEP
Khw#enddef

#define TERRAIN_WATER
Ww#enddef

#define TERRAIN_WATER_DEEP
Wo#enddef

#define START_P1
x,y=4,7
#enddef

#define START_P2
x,y=30,7
#enddef

#define KEEP_P1M
x,y=4,5
#enddef

# ...here are the other keeps...

#define CASTLE_P1M
x= 3, 3, 4, 4, 4, 5, 5
y= 5, 6, 4, 5, 6, 5, 6
#enddef

#define CASTLE_P1N
x= 3, 3, 4, 4, 4, 5, 5
y= 9,10, 8, 9,10, 9,10
#enddef

#define CASTLE_P2M
x=29,29,30,30,30,31,31
y= 5, 6, 4, 5, 6, 5, 6
#enddef

#define CASTLE_P2N
x=29,29,30,30,30,31,31
y= 9,10, 8, 9,10, 9,10
#enddef

[multiplayer]

 # ...here is what we wrote previously...

#define SELECT_FACTION SIDE KEEP UNITS OTHER_KEEP OTHER_CASTLE START
 # ...you know what...
#enddef

{SELECT_FACTION 1 {KEEP_P1M} {UNITS_MERFOLK} {KEEP_P1N} {CASTLE_P1N} {START_P1}}
{SELECT_FACTION 1 {KEEP_P1N} {UNITS_NAGAS}   {KEEP_P1M} {CASTLE_P1M} {START_P1}}
{SELECT_FACTION 2 {KEEP_P2M} {UNITS_MERFOLK} {KEEP_P2N} {CASTLE_P2N} {START_P2}}
{SELECT_FACTION 2 {KEEP_P2N} {UNITS_NAGAS}   {KEEP_P2M} {CASTLE_P2M} {START_P2}}

[/multiplayer]

(C) 2008 Viliam Búr viliambur.blogspot.com

If you got lost in the "..." notes, you can see the complete code at the end of this article. (I am trying to keep the article short, not very successfully.) The essense is that macro without parameters is called like this: "{MACRO_NAME}", and the macro with parameters is called like this: "{MACRO_NAME PARAM1 PARAM2 PARAM3}". You can use a macro as a parameter for another macro like this: "{MACRO_NAME {OTHER_MACRO}}.

Some macros have the "#enddef" written immediately after the text. This way the "end of line" symbol is not included in the macro. I use this for macros which are only a part of line in the script; then I could put more such macros in one line, like this: "recruit={UNITS_MERFOLK},{UNITS_NAGAS}". If a macro contains full lines of script, it does not matter if there are too many "end of line" symbols, because the empty lines are ignored.

Merfolk keep selected screenshot

At this moment, the scenario is ready. The only problem is that AI (artificial intelligence, also called "Computer Player" in Wesnoth) does not understand our system, so from its point of view our keeps are just ordinary keeps same to each other; and because of some technical details, it always selects the northern keep. Therefore we will add some AI logic.

We cannot explain the details of our design to AI and expect that it will understand the consequences and develop the correct strategy. (If anyone is capable of programming such smart AI, please contact the Wesnoth development team.) Instead we will use a trick: we will decide for some keep and destroy the other one, and then the AI will naturally "select" the remaining keep (which will trigger the event as usually).

The decision algorithm will be like this: choose the keep randomly. Oh, did you expect something smarter? So let's do this: if the computer player is player 1, it will choose randomly. If the computer player is player 2, it will choose the opposite of player 1. However if the player 1 did not choose any keep during the first turn (which is strange, but possible), the computer player 2 will choose randomly, too. Now let's rephrase this differently: The computer player will choose randomly, but the computer player 2 will take the opposite of player 1 instead, if player 1 has already chosen.

We will need the "ai turn" event, which is triggered at the beginning of AI side turn. However we will use random numbers, and for some technical reasons it is not allowed to generate random numbers during "ai turn" event, so we will also use the "side turn" event; this is triggered at the beginning of any side turn (before "ai turn"). In "side turn" event we will generate the random number and put it into variable, then "ai turn" event we will use it; or it will be ignored for human players.

The random number will be stored in a variable called "choice"; technically it will be a number 1 or number 2, but to make the script more legible, we can create macros CHOOSE_MERFOLK and CHOOSE_NAGAS with values 1 and 2, so we do not have to remember which number means which choice.

There will be new tags "[set_variable]" for storing a value in a variable; "[if]" and "[then]" for decisions; "[variable]" for checking if a variable contains the specified value; "[have_location]" for checking if a location contains (or does not contain, if we use the "!" symbol) the specified terrain. We will also use event variables "turn_number" and "side_number" to determine if this is the first turn, and if this is player 2.

In other programming languages the following algorithm would look somehow like this. (This is just an imaginary pseudocode based on some existing languages.)

on SideTurn (turn) {
 if turn == 1 {
  choice := random(1..2)
 }
}

on AITurn (turn, side) {
 if turn == 1 {

  if  side == 2  and  terrain(KEEP_P1M) != TERRAIN_KEEP  {
   choice := CHOOSE_MERFOLK
  }
  if  side == 2  and  terrain(KEEP_P1N) != TERRAIN_KEEP  {
   choice := CHOOSE_NAGAS
  }

  sink_keep(side, not choice)
 }
}

And this is the full scenario script:

#define UNITS_MERFOLK
Merman Fighter,Merman Hunter,Mermaid Initiate#enddef

#define UNITS_NAGAS
Naga Fighter,Saurian Augur,Saurian Skirmisher#enddef

#define TERRAIN_KEEP
Khw#enddef

#define TERRAIN_WATER
Ww#enddef

#define TERRAIN_WATER_DEEP
Wo#enddef

#define START_P1
x,y=4,7
#enddef

#define START_P2
x,y=30,7
#enddef

#define KEEP_P1M
x,y=4,5
#enddef

#define KEEP_P1N
x,y=4,9
#enddef

#define KEEP_P2M
x,y=30,5
#enddef

#define KEEP_P2N
x,y=30,9
#enddef

#define CASTLE_P1M
x= 3, 3, 4, 4, 4, 5, 5
y= 5, 6, 4, 5, 6, 5, 6
#enddef

#define CASTLE_P1N
x= 3, 3, 4, 4, 4, 5, 5
y= 9,10, 8, 9,10, 9,10
#enddef

#define CASTLE_P2M
x=29,29,30,30,30,31,31
y= 5, 6, 4, 5, 6, 5, 6
#enddef

#define CASTLE_P2N
x=29,29,30,30,30,31,31
y= 9,10, 8, 9,10, 9,10
#enddef

#define CHOOSE_MERFOLK
1#enddef

#define CHOOSE_NAGAS
2#enddef

[multiplayer]

 id=multiplayer_2p_Pool
 name= _ "2p - Pool"
 description= _ "Water battle between merfolk and nagas."
 map_data="{@campaigns/Examples_by_Viliam/maps/multiplayer/2p_Pool.map}"

 {DEFAULT_SCHEDULE}
 {DEFAULT_MUSIC_PLAYLIST}

 [side]
  side=1
  controller=human
  canrecruit=yes

  team_name=west
  user_team_name= _ "teamname^West"
 [/side]

 [side]
  side=2
  controller=human
  canrecruit=yes

  team_name=east
  user_team_name= _ "teamname^East"
 [/side]

 [event]
  name=prestart
  [label]
   {KEEP_P1M}
   text=_"Merfolk"
  [/label]
  [label]
   {KEEP_P1N}
   text=_"Nagas"
  [/label]
  [label]
   {KEEP_P2M}
   text=_"Merfolk"
  [/label]
  [label]
   {KEEP_P2N}
   text=_"Nagas"
  [/label]
 [/event]

#define SELECT_FACTION SIDE KEEP UNITS OTHER_KEEP OTHER_CASTLE START
 [event]
  name=moveto
  [filter]
   side={SIDE}
   {KEEP}
   [filter_location] 
    terrain={TERRAIN_KEEP}
   [/filter_location]
  [/filter]
  first_time_only=yes

  [set_recruit]
   side={SIDE}
   recruit={UNITS}
  [/set_recruit]
  [label]
   {OTHER_KEEP}
   text=""
  [/label]
  [terrain]
   {OTHER_CASTLE}
   terrain={TERRAIN_WATER_DEEP}
  [/terrain]
  [terrain]
   {START}
   terrain={TERRAIN_WATER}
  [/terrain]
 [/event]
#enddef

{SELECT_FACTION 1 {KEEP_P1M} {UNITS_MERFOLK} {KEEP_P1N} {CASTLE_P1N} {START_P1}}
{SELECT_FACTION 1 {KEEP_P1N} {UNITS_NAGAS}   {KEEP_P1M} {CASTLE_P1M} {START_P1}}
{SELECT_FACTION 2 {KEEP_P2M} {UNITS_MERFOLK} {KEEP_P2N} {CASTLE_P2N} {START_P2}}
{SELECT_FACTION 2 {KEEP_P2N} {UNITS_NAGAS}   {KEEP_P2M} {CASTLE_P2M} {START_P2}}

 [event]
  name=side turn
  first_time_only=no

  [if]
   [variable]
    name=turn_number
    numerical_equals=1
   [/variable]
   [then]
    [set_variable]
     name=choice
     rand=1..2
    [/set_variable]
   [/then]
  [/if]
 [/event]

 [event]
  name=ai turn
  first_time_only=no
  [if]
   [variable]
    name=turn_number
    numerical_equals=1
   [/variable]
   [then]

    [if]
     [variable]
      name=side_number
      numerical_equals=2
     [/variable]
     [have_location]
      {KEEP_P1M}
      terrain=!,{TERRAIN_KEEP}
     [/have_location]
     [then]
      [set_variable]
       name=choice
       value={CHOOSE_MERFOLK}
      [/set_variable]
     [/then]
    [/if]

    [if]
     [variable]
      name=side_number
      numerical_equals=2
     [/variable]
     [have_location]
      {KEEP_P1N}
      terrain=!,{TERRAIN_KEEP}
     [/have_location]
     [then]
      [set_variable]
       name=choice
       value={CHOOSE_NAGAS}
      [/set_variable]
     [/then]
    [/if]

#define PRESELECT_FACTION SIDE CHOICE OTHER_KEEP
    [if]
     [variable]
      name=side_number
      numerical_equals={SIDE}
     [/variable]
     [variable]
      name=choice
      numerical_equals={CHOICE}
     [/variable]
     [then]
      [terrain]
       {OTHER_KEEP}
       terrain={TERRAIN_WATER}
      [/terrain]
     [/then]
    [/if]
#enddef

{PRESELECT_FACTION 1 {CHOOSE_MERFOLK} {KEEP_P1N}}
{PRESELECT_FACTION 1 {CHOOSE_NAGAS}   {KEEP_P1M}}
{PRESELECT_FACTION 2 {CHOOSE_MERFOLK} {KEEP_P2N}}
{PRESELECT_FACTION 2 {CHOOSE_NAGAS}   {KEEP_P2M}}

   [/then]
  [/if]
 [/event]

[/multiplayer]

This scenario (with some improvements not mentioned in these articles) can be downloaded as a Wesnoth add-on "Examples by Viliam".

Unfortunately in Wesnoth 1.4 there is a bug which causes AI problems with recruiting if it has some nontypical recruitment list, such as here. So the computer player will not recruit units, or just not recruit enough. This should not discourage you, it is just a reminder that the game is always developing (I have reported the bug and hope it gets fixed someday in 1.6), and if you do something unusual, you may need to get in contact with the game developers. If your scenarios are more similar to the existing ones, the chance of finding a bug is smaller. At least this scenario should work correctly when played human against human.

Related articles:

Labels:

2008/08/05

Real Numbers in Java

In programming language Java there are two primitive types designed for work with real numbers: float and double. Their sizes are 4 or 8 bytes, which contain 1 sign bit, 8 or 11 exponent bits, and 23 or 52 mantissa bits. The real numbers are rounded after certain number of valid digits; that means that greater numbers have greater rounding errors. We need to make a difference between the ranges of these types (smallest and largest values) and their precisions (number of valid digits).

Each of these primitive types corresponds to an object type: java.lang.Float and java.lang.Double. Their static variables "MIN_VALUE" and "MAX_VALUE" are the smallest and the largest finite positive real numbers which can be expressed using this type.

System.out.println("Float " + Float.MIN_VALUE + " " + Float.MAX_VALUE);
// Float 1.4E-45 3.4028235E38
System.out.println("Double " + Double.MIN_VALUE + " " + Double.MAX_VALUE);
// Double 4.9E-324 1.7976931348623157E308

The formula "1.4E-45" means "1.4 × 10-45", which is 0.000 000 000 000 000 000 000 000 000 000 000 000 000 000 001 4. The formula "3.4028235E38" means "3.4028235 × 1038", which is 340 282 350 000 000 000 000 000 000 000 000 000 000; the trailing zeroes only meaning that the digits on these places have been rounded. Also the formulas "4.9E-324" and "1.7976931348623157E308" mean "4.9 × 10-324" and "1.7976931348623157 × 10308", which I will not expand, because the resulting numbers are over 300 digits long.

What happens when the result of computing is a too large or too small number, outside the limits of this type? Both real types have helper constants, which allow to return at least some results even beyond the type boundaries. Too large positive and negative numbers can be expressed by constants "POSITIVE_INFINITY" and "NEGATIVE_INFINITY". Numbers too close to zero are rounded to zero (there is also a negative zero "-0.0f", which is usually equal to normal zero, except some specific situations like division by zero). If we cannot say anything about the result, such as an infinity minus infinity or zero divided by zero, this can be expressed by a constant "NaN". (Unlike the object constant "null", these constants belong to the types "float" and "double", so they can be stored in primitive type variables too.)

If we want to find out if our calculation has returned one of these values, we can use methods "isInfinite" and "isNaN".

Why do we use these special constants for real numbers, when integer numbers in similar circumstances just overflow or throw an "ArithmeticException"? One reason is that the real numbers are rounded. If we divide by zero in an integer calculation, it usually means that the programmer has forgot to check some special case; and it is good if the program gives him/her a warning. If we divide by zero in a real calculation, it often means that we wanted to divide by some nonzero value, which was rounded to zero during the calculation. Checking for all such situations would be difficult, and essentially useless. A good programmer already knows that the real numbers are rounded, so the results of calculations are always unreliable to some degree; how much exactly can the rounding error grow depends on the specific formula. Another reason is the the way the real numbers are stored in memory allows to add these constants "naturally"; using a similar method for integer numbers would be "unnatural", and by the way it would make all programs much slower.

System.out.println(Float.MAX_VALUE * 2);  // Infinity
System.out.println(Float.MIN_VALUE / 2);  // 0.0
System.out.println(0.0f /  0.0f);  // NaN
System.out.println(1.0f /  0.0f);  // Infinity
System.out.println(1.0f / -0.0f);  // -Infinity
System.out.println(1.0f / Float.POSITIVE_INFINITY);  //  0.0
System.out.println(1.0f / Float.NEGATIVE_INFINITY);  // -0.0

(C) 2008 Viliam Búr viliambur.blogspot.com

Basic mathematical operations are done using symbols: "+" addition, "-" subtraction, "*" multiplication, "/" division. If we use both integer and real numbers, the "/" symbol between two integer numbers means integer division, between two real numbers or between a real and integer numbers it means real division.

System.out.println(1    / 3   );  // 0
System.out.println(1    / 3.0f);  // 0.333...
System.out.println(1.0f / 3   );  // 0.333...
System.out.println(1.0f / 3.0f);  // 0.333...

We can compare numbers using operators: "==" is equal, "!=" is not equal, "<" is less, ">" is more, "<=" is less or equal, ">=" is more or equal. During calculations the real numbers are rounded, so the result of "==" operator may depend on rounding errors. With real numbers we usually do not check whether they are equal, but whether their difference is in some given tolerance.

float a = 123456789f;
float b = 123000123f;
float c = 987987987f;
float x = a * b * c;
float y = c * b * a;
System.out.println(x);  // 1.5002796E25
System.out.println(y);  // 1.5002795E25

Class java.lang.Math provides many methods for calculations with integer and real numbers: absolute value "abs" and sign "signum"; smaller or greater of two numbers "min" and "max"; rounding "floor", "ceil", "round"; power "exp" and "pow", root "sqrt" and "cbrt", and logarithm "log" and "log10"; changing angles from degrees to radians "toRadians" and vice versa "toDegrees"; goniometric functions "sin", "cos", "tan", "asin", "acos", "atan", "atan2" and hyperbolic functions "sinh", "cosh", "tanh". It provides also mathematical constants "E" and "PI".

There is also a class java.lang.StrictMath, which provides the same methods, with additional guarantee that these methods will give exactly the same results when used on any platform.

We can receive random numbers using object java.util.Random. Methods "nextFloat" and "nextDouble" return random real numbers distributed uniformly between 0 and 1. Method "nextGaussian" returns random real numbers according to Gaussian normal distribution with mean 0 and standard deviation 1; this means that approximately 68% of values will not be further from the mean (in any direction) than the standard deviation, 95% of values will not be further than two standard deviations, and 99.7% of values will not be further than three standard deviations. For example the IQ tests have mean 100 and standard deviation 15, so here is a simulator of IQ tests of randomly selected individuals in population:

java.util.Random r = new java.util.Random();
for (int i = 0; i < 100; i++) {
  System.out.println(Math.round(100 + 15 * r.nextGaussian()));
}

Related articles:

Labels:

2008/08/03

Integer Numbers in Java

In programming language Java there are four primitive types designed for work with integers: byte, short, int, and long. These are signed integer numbers with size 1, 2, 4, 8 bytes, that is 8, 16, 32, 64 bits. (Also char type designed for work with characters is a 16-bit unsigned integer, and we can use it for mathematical operations, but for the sake of program legibility I do not recommend doing this needlessly.)

Basic mathematical operations are done using symbols: "+" addition, "-" subtraction, "*" multiplication, "/" integer division, "%" integer division reminder.

int a = 100;
int b = 6;

System.out.println(a + b);  // 106
System.out.println(a - b);  //  94
System.out.println(a * b);  // 600
System.out.println(a / b);  //  16
System.out.println(a % b);  //   4

The "+" symbol means not only addition but also string concatenation, and it has the same priority as mathematical "+" and "-". If you display calculated results accompanied by text description, take care about the operator priority, and use parentheses around the calculation.

int x = 2;
int y = 3;

System.out.println("Sum is " + x + y);    // Sum is 23
System.out.println("Sum is " + (x + y));  // Sum is 5

We can compare numbers using operators: "==" is equal, "!=" is not equal, "<" is less, ">" is more, "<=" is less or equal, ">=" is more or equal.

int x = 2;
int y = 3;

if (x == y) {
 System.out.println("X is equal to Y");
}
if (x != y) {
 System.out.println("X is not equal to Y");
}
if (x < y) {
 System.out.println("X is less than Y");
}
if (x > y) {
 System.out.println("X is more than Y");
}
if (x <= y) {
 System.out.println("X is less than or equal to Y");
}
if (x >= y) {
 System.out.println("X is more than or equal to Y");
}

Each of these primitive types corresponds to an object type: java.lang.Byte, java.lang.Short, java.lang.Integer, java.lang.Long; these types are subtypes of abstract type java.lang.Number. Using object types we can use integers in contexts where objects are required, such as sets or lists. The object types provide also some useful static methods. A primitive value can be converted to object using static method valueOf; an object can be converted to primitive value using methods byteValue, shortValue, intValue, longValue.

int i = 5;
Integer j = Integer.valueOf(i);
int k = j.intValue();

System.out.println(i);  // 5
System.out.println(j);  // 5
System.out.println(k);  // 5

Java supports autoboxing, that is automatical insertion of methods converting primitive values to objects and vice versa. The previous example could have been written more simply, but it would be the same thing.

int i = 5;
Integer j = i;
int k = j;

System.out.println(i);  // 5
System.out.println(j);  // 5
System.out.println(k);  // 5

Be careful about autoboxing. It is better to use the methods explicitly, because it helps us avoid errors which are very difficult to spot. An "Integer" variable can contain null value, an "int" variable always contains some number; if we forget this, the automatic conversion may throw a NullPointerException.

When using "Integer" and "int" types the meaning of "==" symbol is different. For primitive types the "==" symbol compares the numbers. For object types the "==" symbol tests if it is the same object instance. So if we have two different objects containing the same number, the "==" symbol will return false, because they are different; to compare their contents we have to use "equals" method. With two different primitive types, for example "int" and "long", the "==" symbol compares if they contain the same number; for "Integer" and "Long" objects the "equals" method always returns false. When using objects for numbers, we have to be very careful about what exactly are we comparing.

(C) 2008 Viliam Búr viliambur.blogspot.com

int i = 5;
int j = 5;
if (i == j) {  // yes
 System.out.println("I and J are equal");
}

Integer I = new Integer(5);
Integer J = new Integer(5);
if (I == J) {  // no
 System.out.println("I and J are the same object");
}
if (I.equals(J)) {  // yes
 System.out.println("I is same as J");
}

int x = 5;
long y = 5;
if (x == y) {  // yes
 System.out.println("X and Y are equal");
}

Number X = new Integer(5);
Number Y = new Long(5);
if (X == Y) {  // no
 System.out.println("X and Y are the same object");
}
if (X.equals(Y)) {  // no!
 System.out.println("X is same as Y");
}
if (X.longValue() == Y.longValue()) {  // yes
 System.out.println("X and Y are equal");
}

We can calculate the range of each integer type from their number of bytes, or we can use their static variables "MIN_VALUE" and "MAX_VALUE".

System.out.println("Byte " + Byte.MIN_VALUE + " " + Byte.MAX_VALUE);
// Byte -128 127
System.out.println("Short " + Short.MIN_VALUE + " " + Short.MAX_VALUE);
// Short -32768 32767
System.out.println("Integer " + Integer.MIN_VALUE + " " + Integer.MAX_VALUE);
// Integer -2147483648 2147483647
System.out.println("Long " + Long.MIN_VALUE + " " + Long.MAX_VALUE);
// Long -9223372036854775808 9223372036854775807

If we want to write the number into string, we can use the "toString" method (it has also a static version) or use the "+" symbol to append the number to a string (for example, an empty string). If we want to use other than decimal numeral system, we can use static method "toString" with two parameters. The numeral system base cannot be larger than "Character.MAX_RADIX", but we usually need bases from 2 to 16, and these are allright. For frequently used bases there are also static methods "toBinaryString", "toOctalString", "toHexString" which convert an unsigned number as a parameter.

int x = 123456789;
System.out.println(Integer.toHexString(x));   //  75bcd15
System.out.println(Integer.toString(x, 16));  //  75bcd15

int y = -123456789;
System.out.println(Integer.toHexString(y));   // f8a432eb
System.out.println(Integer.toString(y, 16));  // -75bcd15

If we want to read number from a string, we can use static method "parseInt" ("parseByte",...) or "valueOf", depending on whether we need a primitive type or an object. (If the string contains unsigned number, for example returned by method "toHexString", there can be a range overflow and a "NumberFormatException".)

System.out.println(Integer.parseInt("cafe", 16));  // 51966

We can also perfrom bit operations on integers: "&" conjunction (both bits), "|" disjunction (at least one bit), "^" exclusive disjunction (exactly one bit). We can shift bits using operators: "<<" left shift, ">>" sign preserving right shift, ">>>" zero filling right shift.

int i = Integer.parseInt("0011", 2);
int j = Integer.parseInt("0101", 2);

System.out.println(Integer.toBinaryString(i & j));  // 0001
System.out.println(Integer.toBinaryString(i | j));  // 0111
System.out.println(Integer.toBinaryString(i ^ j));  // 0110

int k = -100;

System.out.println(Integer.toBinaryString(k));
// 11111111111111111111111110011100
System.out.println(Integer.toBinaryString(k << 1));
// 11111111111111111111111100111000
System.out.println(Integer.toBinaryString(k >> 1));
// 11111111111111111111111111001110
System.out.println(Integer.toBinaryString(k >>> 1));
// 01111111111111111111111111001110

We can receive random numbers using object java.util.Random. Here is a dice throwing simulation:

java.util.Random r = new java.util.Random();
for (int i = 0; i < 100; i++) {
 System.out.println(1 + r.nextInt(6));
}

Related articles:

Labels:

2008/07/27

Water Battle Map for Wesnoth, Published

In previous article we have designed a map for water battle, called "2p_Pool.map". Now we will specify that players can only recruit Merfolk and Naga units. And then we will publish the map on the Wesnoth add-on server, so that other people can download it and play against each other online.

In map editor we can only place terrains and starting positions. For anything else we must create a configuration file. It is a text file. And by "text file" I mean text file created with plain text editor such as Notepad or KWrite. Not Word document, nor RTF document, just good old "*.txt" file. And it looks like this:

[multiplayer]

 id=multiplayer_2p_Pool
 name= _ "2p - Pool"
 description= _ "Fight in water between merfolk and nagas."
 map_data="{@campaigns/Examples_by_Viliam/maps/multiplayer/2p_Pool.map}"

 {DEFAULT_SCHEDULE}
 {DEFAULT_MUSIC_PLAYLIST}

 [side]
  side=1
  controller=human

  team_name=west
  user_team_name= _ "teamname^West"

  canrecruit=yes
  recruit=Merman Fighter,Merman Hunter,Mermaid Initiate

 [/side]

 [side]
  side=2
  controller=human

  team_name=east
  user_team_name= _ "teamname^East"

  canrecruit=yes
  recruit=Naga Fighter,Saurian Augur,Saurian Skirmisher

 [/side]

[/multiplayer]

Do not be afraid. It is easy to understand, when you try. The configuration file is basicly a list of "keyword=value" pairs. Sometimes they are surrounded by marks like "[multiplayer]" and "[/multiplayer]", or "[side]" and "[/side]". You can learn their meanings (keep a cheat sheet at hand), or just copy this example and try to change the ones you understand.

The "[multiplayer]" and "[/multiplayer]" marks mean "this is the beginning / the end of multiplayer scenario description". The "id" is an internal scenario identifier; it means that nobody will read this text, but it has to be different from the "id"s of other scenarios. The "name" is a name under which the scenario will appear in a map list. Tradition says that the name should also show how many players can play the scenario, therefore "2p - Pool". The "description" will appear when you move the mouse cursor above the map preview. The "map_data" is a link to map file created in map editor.

(C) 2008 Viliam Búr viliambur.blogspot.com

The symbols "{DEFAULT_SCHEDULE}" and "{DEFAULT_MUSIC_PLAYLIST}" are just shortcuts (called macros) for something more complicated. This means that we will use standard day/night schedule, and standard music playlist. We have no reason to change this now.

The "[side]" and "[/side]" marks mean "this is the beginning / the end of side description". We have two sides, and we have put two starting positions in the map. The "side" is just an order of side, 1 or 2. The "controller" means that this side can be played by human player (but we can choose computer player instead). The "team_name" is a team identifier; the "user_team_name" is how the team name will be displayed to player, so that s/he knows s/he will play for the West team or the East team. The "canrecruit" says that players can recruit units at keeps. And the "recruit" is a comma-separated list of recruitable units. (You will find the units in "data/core/units" directory, where "data" means "C:\Program Files\Wesnoth 1.4.4\data" or something similar. Look at the text files and find the "id".)

The symbol "_" before some texts means that these texts are translatable to other languages. We will not do it now, but it is a good habit to write the "_" symbol before all strings which will be displayed to player. The part of texts before symbol "^" in translated strings will not be displayed; it is just a hint for translators.

Now we need to put the map file "2p_Pool.map" and the scenario configuration file "2p_Pool.cfg" somewhere where the game finds them, and make them link to each other correctly. Because I want to publish my examples in one package, I have created a directory "userdata/data/campaigns/Examples_by_Viliam". (This is not a campaign, but the game add-on server supports campaigns, so we are going to cheat a little.) Inside this directory I put the map file as "maps/multiplayer/2p_Pool.map" and the scenario file as "scenarios/multiplayer/2p_Pool.cfg". Please note how the "map_data" value in scenario configuration file points to the map file. If you want to use different directories, you have to change the link.

To make this package visible to game, we need another configuration file "userdata/data/campaigns/Examples_by_Viliam.cfg". It will be like this:

#ifdef MULTIPLAYER
{@campaigns/Examples_by_Viliam/scenarios/multiplayer}
#endif

The package configuration file will include the subdirectory "scenarios/multiplayer" if the game is in the multiplayer mode. No need to waste memory if the player is doing something different. And we will need a publishing file "userdata/data/campaigns/Examples_by_Viliam.pbl" like this:

title="Examples by Viliam"
version="0.0.1"
author="Viliam Bur"
passphrase="i_will_not_show_you_my_password__make_your_own"
description="WML examples. I hope this works..."

Now when we start the game and select the "Add-ons" menu, there will be a list of downloadable packages, and also an option to publish this package. I did it... so if anything in this tutorial was not clear, you can just download "Examples by Viliam" and look at the files on your own disk.

If you have any questions about this example, or if you found it useful, please write me a comment here. If you have more questions about creating Wesnoth content, please register into Wesnoth forum and ask there. There are many people in the forum willing to help, if you behave politely and try to search the web page before asking.

Related articles:

Labels:

2008/07/26

Water Battle Map for Wesnoth

Do you know Battle for Wesnoth? It is a turn-based strategy game with a fantasy theme. You can freely download it from the website and start playing right now. (Really, do it! Then return to read the rest of this article.) There is even more. You can create your own maps, design your own military units, and write your own stories; and then you can share them on the Internet. Let's try it now!

This is my first Wesnoth map. I started playing the game a few years ago, in 2003. Then, there was only one campaign with maybe 5 or 10 scenarios, which were followed by a message saying "To be continued..." or something like this. Yeah, those old times... ;-) Do not worry, today all official campaigns in game are completed and tested. So I was a few years contemplating an idea of making my own campaign, but there was always something else to do (for example translating the game to Slovak language). But now I decided that I am going to learn WML (Wesnoth markup language, this game has its own scripting language) and make a few maps, maybe even a full campaign. And while I am doing this, I will turn all my gained knowledge into web tutorials, so that more people can try it and contribute to the game. So this is the first map, hopefully many more will follow.

When you install the game, a map editor is also installed. You can find it (on Windows) in Start menu, as "Wesnoth | Map Editor". Start it!

The maps in Wesnoth consist of hexagonal pieces of terrain (called hexes). When playing game, the borders of hexagons blend to look nicer, more realistic. But in map editor I prefer them visibly separated. The border between hexes can be emphasised by menu option "Settings | Settings | Show Grid". You can also turn on/off the grid using a toolbar button. Now the edges are clearly visible, but in the game they will look normally. There is a similar option in game settings too.

To change map dimensions, select the menu option "Edit | Change map size". Do not worry about setting them right, you can change the dimensions later.

When you move the mouse cursor above the map, you can see two small hexagonal pictures above the selected hex. These are the selected terrains associated with left and right mouse buttons. On the lower right part of editor you can click on various terrains; when you click with left or right mouse button, you associate the terrain with this button. This means that clicking this button on the map will draw this terrain on the selected hex. The terrains in palette are divided into groups, so you can choose if you want to see all terrains or just water terrains, for example.

Most of battles in Wesnoth happen on the ground; the second most frequent place is underground. There are some flying or swimming units, but most units can only walk, so the maps reflect this. You know what? This is not fair! Let's make a map full of water, where the Merfolk and Nagas can enjoy their great water battles. We can use water villages, and water castles...

(C) 2008 Viliam Búr viliambur.blogspot.com

...and then I added some grassland around the lake, patches of swamp, and bridges across, because the big blue area was kind of boring. There are not many different water terrains. We could add new terrains, but not now. Let's keep it simple. It is the first map.

When the map is finished, the last necessary step is to place starting locations. (There is a toolbar button for this. When you are finished, there is another toolbar button to return the terrain painting mode.) Make one starting position for player 1, and another for player 2. The starting location is a place where the leader of this side will appear at the beginning of the game. It is usually in a keep, otherwise s/he would not be able to recruit units. I decided to give each side two castles and put the leader between them. The reasons for this will be explained in another article; now just consider it a harmless whim.

Map Editor screenshot

When everything is ready, we can save the map as "2p_Pool.map" and quit the map editor. The "2p" at the beginning means that it is a multiplayer map for 2 players. By the way, you should sometimes save your map during editing, because you know, bugs exist, and editor may unfortunately crash or freeze, losing your work.

So where is my map saved and how can I play it?

You can play the map by starting Wesnoth, selecting "Multiplayer | Local game" and finding your map in the list of maps. Maps created in map editor are automatically included in the game. You cannot play this map over internet yet, because it does not exist on the opponent's computer. We will get to publishing later. But now you can test the map against the computer, or just let two computer players fight against each other.

All files you create for Wesnoth should be placed in your "userdata" directory. The exact location depends on your operating system and version of Wesnoth; on Windows it is like "C:\Program Files\Wesnoth 1.4.4\userdata". From now on I will just write "userdata" and you will know what I mean. So the maps created in map editor are saved in directory "userdata/editor/maps", unless specified otherwise.

There are some things left to do. For example I want to allow recruiting only Merfolk and Naga units. But these things cannot be done in the map editor, where we can only place terrains and starting positions.

Labels:

Site Map

Welcome, this is my English blog. There are not many pages yet. Hopefully there will be more later. If the pages become too many, here is a list to find what you want: