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: