Welcome, Guest. Please login or register. Did you miss your activation email?

Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - Tenry

Pages: 1 2 [3] 4 5 ... 8
31
There are some other language aspects, I would like to discuss more. In SilverScript, a Class is the same as an Object, Namespace or Structure. An object is just a copy of a class (function code internally keeps referenced) and could be used a sub class which could also be instanciated (so, copied).
Do you somewhere differ between the 4 terms?

If an instance is just a copy of a class, can you change the original class? In your example, write foobar.x = 5?

Consider the following:
class foo
  x = 5

  function func
    # ...
  end
end

bar = foo.new()
# bar.x = 5 (it was copied)
# bar.func = function ... (it is internally referenced, because of possible huge code size)

foo.x = 20
# bar.x is still 5

foo.func = function
  # ...
end
# bar.func is still the old one

bar.x = 1
# does not affect foo.x


I'll think about the function call syntax carefully, but at there moment there are also other things I have to work with.


EDIT:
I've go an idea: Functions are always called, no matter whether there are parantheses or not, but they are needed it parameters are passed. If one would like to give a function an alias (another variable pointing to the function), one can use the "ref" keyword.

# old syntax
music = sf.music.new()
music.play
...
window.draw sprite
... ... ...
func_ref = myfunction # func_ref points to the function of myfunction


# new syntax
music = sf.music.new # parantheses are optional, because there are no parameters
music.play
...
window.draw(sprite)
... ... ...
func_ref = ref myfunction

32
I think that function calling without parentheses is a bad idea.
Well, worked in Basic, too. But I agree, it could be misleading/harder to read and you don't gain anything making the parenthesis optional, so I'd just force them, especially when supporting some kind of pointers or references (to distinct between wanting the address or return value for example). Otherwise things like "x = functionname" will be misleading, especially for beginners, cause most would expect the return value to be assigned.

As far as I know, parentheses in function calls in Ruby aren't needed, either, am I right?

Most of you are just too used to C style languages, where parentheses around function parameters are needed. Maybe I will get another idea for function calls, because... those parentheses are also used in simple expressions to group certain operations together...
There is one simple rule to remember my syntax: when not expecting some function return value by a function call, the parentheses are not needed. If you are unsure whether you should put parentheses or not, to call a function, you can always put them, especially, if you are used to C style languages.


There are some other language aspects, I would like to discuss more. In SilverScript, a Class is the same as an Object, Namespace or Structure. An object is just a copy of a class (function code internally keeps referenced) and could be used a sub class which could also be instanciated (so, copied). The usual way to implement a class is the following:

class foobar
  # attributes
  x = 0
  y = 0
 
  # methods
  function do_something
    # function code...
  end
end

instance = foobar.new()
instance.do_something()
...


I call this type of class implementation explicit implementation/conversion/casting. As types are dynamic in SilverScript, the following (for example for a vector structure) could also work:

# 'pos' is not set, it is nil

# by accessing any attribute of pos, it is automatically converted to an empty class
# I call this 'implicit implementation' or '- conversion' or '- casting'
pos.x = 0
pos.y = 0


In Lua you always have to "declare" a table like pos = {} before you can set any attributes.
The same rule applies for arrays: when accessing/setting an index with the []-operator, the variable is internally converted to an array, and any previous data (e.g. class attributes) is deleted.

33
Parentheses around function parameters are optionally. At least, if a function call (or definition/implementation) is not in an expression, i.e. you are not taking a function's return value for something.
Do you see an advantage in making parentheses optional? Especially if you want to keep it simple, enforcing parentheses for function calls, and therefore avoiding the ambiguity, might be a better idea. At least, I find it quite unintuitive when an expression behaves completely different just because it is assigned to an object...

I made the parentheses in general optionally, because I find it nicer to read or to understand in some cases, especially if the function doesn't have any parameters. Consider the following two examples:

update_screen # actually a function, should be clear because of the name

mytime = current_time
# current_time should give you the system's current time (e.g. as Integer),
# but it almost behaves like a usual variable.
# Unfortunately (current_time is a function) it's not clear whether mytime
# shall accept the return value of current_time() or point to the function,
# hence I made it mandatory in assignments.
# In simple statements (non-assignments) the return value is not retreived,
# so in such a case it can only be a function call (like the update_screen example above).

34
That strikes me as confusing. No more confusing as some of the things other languages to do with functions as 1st class objects, but confusing none-the-less.
New languages might always be confusing :P

As soon as the interpreter works well I will release the source code and detailed tutorials. Then SilverScript might be quite easy to understand, I hope.

35
Cool! The syntax reminds me of Lua, but also a bit of Matlab. Why do function calls sometimes have parentheses and sometimes not? Do you already have plans on how to interfere with C++?

And have you implemented a garbage collector? I'm interested in how you manage resources and memory :)
Yeah, the syntax is very oriented to Lua, but there are still significantly differences in the language design and the inner workings behind.
I don't know what Matlab is like, but I might take a look at it, just to compare :)

Parentheses around function parameters are optionally. At least, if a function call (or definition/implementation) is not in an expression, i.e. you are not taking a function's return value for something.

function foobar
  # function body...
end

foobar # calls the function
foobar() # also calls the function
f = foobar # f now points to the function
f() # call to foobar()
f = foobar() # f has got the return value of the function call


An interface between SilverScript and C++ is mandatory, but I'm still thinking of what that API should look like.

At the moment SilverScript uses reference counting, although I've already read that this kind of garbace collection might not be the best.

36
SFML projects / SilverScript: New scripting language for game development
« on: December 16, 2012, 05:12:59 pm »
Hi, I hope it's okay to post something like this here in the SFML forums. Once it's working, it could be useful for your game development ;) So:

I'm currently working on a new scripting language named SilverScript. I'm going to use it in my SFML projects once the interpreter is fully working. Before anybode of you says, why a new scripting language? there are already bunches of them out there, let me depict you my main reason: getting experience. I wanted to learn to write a parser and interpreter, and what would be better than a new scripting language I can design for the best fit in my planned projects? So here it is, SilverScript! And I'd like to know what you think of it, unless you think it's useless.

I've written the SFML example code in SilverScript so that you get an idea what it looks like (as at July 2013):
# import external modules
use sfml as sf # make the sfml module available (as sf)


function main
  # note: new variables are declared with the @-character, which is similar
  # to the 'local' keyword in other languages

 
  # create the main window
  @window = new sf.renderWindow(new sf.videoMode(800, 600), "SFML window")
 
  # load a sprite to display
  @texture = new sf.texture()
  if not texture.loadFromFile("cute_image.jpg")
    return 1
  end
  @sprite = new sf.sprite(texture)
 
  # create a graphical text to display
  @font = new sf.font()
  if not font.loadFromFile("arial.ttf")
    return 1
  end
  @text = new sf.text("Hello SFML", font, 50)
 
  # load a music to play
  @music = new sf.music()
  if not music.openFromFile("nice_music.ogg")
    return 1
  end
 
  # play the music
  music.play()
 
  while window.isOpen()
    # process events
    @event = new sf.event()
    while window.pollEvent(event)
      # close window: exit
      if event.type == sf.event.closed
        window.close()
      end
    end
   
    # clear screen
    window.clear()
   
    # draw the sprite
    window.draw(sprite)
   
    # draw the string
    window.draw(text)
   
    # update the window
    window.display()
  end
 
  return 0
end # end of main function



SilverScript is actually not going to be designed to develop whole game engines (like in the example above) but rather for smaller tasks such as enemy behavior.

If you want to keep up to date, visit http://silverscript.org/, but downloads are not yet available.

37
SFML projects / Re: Custom Information in PNG Files (Source Code)
« on: October 16, 2012, 08:36:44 pm »
It looks really interesting though it's a problem that the image has to go trough another step in the content pipeline before it can be used in the application. I would recommend that you take a look at GIMP, it can save a comment to PNG images and that you get it to work with that. Would remove one step and only force the user to do an extra step to read the image instead.
Yeah, comments can be used to store information in text form, but then you have to write some code that interprets the comment text. I personally prefer the binary form, and the comment chunk might still be used for any information.
Anyway, I might also provide some code to read and interpret text comments instead later.

38
SFML projects / Re: Custom Information in PNG Files (Source Code)
« on: October 16, 2012, 07:18:15 pm »
Interesting, but isn't it easier to use multiple texture rects in an image, considering that image editors usually don't provide access to private chunks?

Or what is the main advantage? That there is no need to specify position and size of the sub-image (but a tag instead)?
I was thinking of a png image that has got a strip of multiple frames (either horizontally or vertically laid out). After reading the private chunks you know which rect to use with sub-images and you don't need to do a png file for every frame, only one png per animation/sprite.

By the way, you should think about returning std::vector<char> or writing into some output parameter buffer, so that the user doesn't have to struggle with memory management. And exceptions are probably exaggerated, as they are not propagated outside the function.
Thanks. I know my code isn't really nice yet as I did it just within a few hours of trouble. I'm going to improve the code more and more.

39
SFML projects / Re: Custom Information in PNG Files (Source Code)
« on: October 16, 2012, 06:28:10 pm »
You should rather put it on the wiki (unless you create a real open-source library with it, of course).
Yeah, I was going too, but I thought, there would be some response first if I put it in the forum, I'm not sure whether one can comment directly on wiki post or get noticed of new pages at all.

40
SFML projects / Custom Information in PNG Files (Source Code)
« on: October 16, 2012, 05:39:08 pm »
Games usually don't have static sprites. Usually, they are animated, and some other information are also linked with the sprites such as the animation speed or the texture origin. I'm sure most of you have hard coded those extra values or put them in extra files. If you are using PNG images, you don't need to!

The PNG file is chunk based and designed to have so called private chunks besides the public chunks which contain the actual image information. So how to use your own chunks?
PNG chunks always have a 4-letter-identifer, some of the public ones are IHDR with header information or IDAT with the actual image. Those are both always needed, hence all letters are capital. Some ancillary are for example bKGD or pHYs.
The case of each letter significates something:
  • critical (X) or not (x): those chunks are needed to read the image
  • public (X) or not (x): we must use lower case letters for out private chunks
  • always capital (X): might be used in the future of PNG
  • save to copy (x) or not (X): indicates whether the chunk depends on other information (such as the image itself) or not

Typical image editors don't allow you to write your own custom chunks, neither there is a way to read the custom chunks with the SFML library itself. We need some library so read and write such chunks, and whick library should be better than the original PNG library libpng? It's quite complicated and the documentation poor (in my opinion), but I tried it and finally have a solution. To spare you the same trouble I had I tell you how to use private chunks. This assumes you having installed libpng already, I'm not going to help with that because I had my own trouble there too and I'm unsure about the "correct" installation.


Writing Private Chunks
I assume you already having the original PNG files which you want to "tag" with your custom chunk(s). First of all you should have planned which information you want to store. I created my own struct for that:
struct isPR_struct
{
  sf::Uint16 nFrames; // only positive values are allowed. 16-bit can store a number up to 65535
  sf::Int16 xOrigin; // might be positive or negative. values from -32768 - 32767 are allowed
  sf::Int16 yOrigin;
} __attribute__((packed));
// isPR: i = not critical; s = private; P must be capital; R = depends on image data
Most systems pad variables so that they fit to 16 or 32 bit, no matter what the actual type size is. If one system writes 32 bit for even just a byte value (char) and another one pad it to 16 bit, there will be incompatibilities if you are reading your chunks from a different system. Hence you should add __attribute__(packed)). But I'm not sure whether it works for any compiler or not, I'm using gcc.

Now I'm going to write a program that reads the necessary data from the source file and copies it to the destination along with our custom chunk.
#include <png.h>

...

png_bytepp loadPng(const char *fname, unsigned &width, unsigned &height)
{
  png_structp png_ptr = 0;
  png_infop info_ptr = 0;
  png_bytepp data = 0; // arrays of rows containing the image data

  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
                                               0, 0, 0);

  if(!png_ptr) return 0; // return NULL-pointer: error
  FILE *fp = fopen(fname, "rb");

  try
  {
    info_ptr = png_create_info_struct(png_ptr);
    if(!info_ptr) throw 1; // just throw anything, you might use your own exception class

    png_init_io(png_ptr, fp); // connect libpng with our fp

    png_read_info(png_ptr, info_ptr); // fill info_ptr

    width = png_get_image_width(png_ptr, info_ptr);
    height = png_get_image_height(png_ptr, info_ptr);

    png_uint_32 bitdepth = png_get_bit_depth(png_ptr, info_ptr); // depth per channel, not pixel!
    png_uint_32 channels = png_get_channels(png_ptr, info_ptr);
    //png_uint_32 color_type = png_get_color_type(png_ptr, info_ptr);

    printf("Image: %dx%d\n", width, height); // you can discard this if it works
    printf("Bitdepth: %d\nChannels: %d\n", bitdepth, channels); // this too

    // allocate the data
    data = new png_bytep[height];
    for(size_t i=0;i<height;i++) data[i] = new png_byte[width*4];

    // fill the data
    png_read_image(png_ptr, data);

    png_destroy_read_struct(&png_ptr, &info_ptr, 0); // don't forget to free the pointers

    fclose(fp); // and to close the file
  }
  catch(int &e)
  {
    fclose(fp);

    png_destroy_read_struct(&png_ptr, &info_ptr, 0);
    return 0; // return NULL-pointer: error
  }

  return data; // finally return the filled data
}
The code assumes 32-bit RGBA images. If you are going to use any format or are unsure about the actual format of your images, do some checking with the given values (bitdepth, etc.).
Note that we have to care about deleting the returned data later with delete.
loadPng() is only needed where you are going to tag your png. You might have a separated tool for that, you don't need it in your actual game later.

// overwrites your file!
bool saveAttributes(const isPR_struct &isPR, const char *filename)
{
  unsigned width, height;
  png_bytepp data = loadPng(filename, width, height); // width and height are filled

  png_structp png_ptr;
  png_infop info_ptr;

  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
                                    0, 0, 0);
  if(!png_ptr) return false;

  FILE *fp = fopen(filename.c_str(), "wb");

  try
  {
    info_ptr = png_create_info_struct(png_ptr);
    if(!info_ptr) throw 1; // like in loadPng()

    png_init_io(png_ptr, fp);

    png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA,
                 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
                 PNG_FILTER_TYPE_DEFAULT); // set the header information

    png_write_info(png_ptr, info_ptr);
    png_write_image(png_ptr, (png_bytepp)data); // write the actual image data

    // tell libpng about our custom chunks
    png_unknown_chunk chunks[1]; // 1 custom chunk
    strcpy((char*)chunks[0].name, "isPR"); // fill in the name
    chunks[0].data = (png_byte*)&isPR; // data points to the chunk data
    chunks[0].size = sizeof(isPR);

    // it doesn't write your chunks without the following lines
    png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, 0, 0);
    png_set_unknown_chunks(png_ptr, info_ptr, chunks, 1);
    png_set_unknown_chunk_location(png_ptr, info_ptr, 0, PNG_AFTER_IDAT);

    // this might actually write your chunks
    png_write_end(png_ptr, info_ptr);

    png_destroy_write_struct(&png_ptr, &info_ptr);

    fclose(fp);

    // don't forget to delete our data which was allocated in loadPng()!
    // you might provide another function named unloadPng() instead...
    for(size_t i=0;i<height;i++) delete data[i];
    delete data;
  }
  catch(int &e)
  {
    png_destroy_write_struct(&png_ptr, 0);

    fclose(fp);

    for(size_t i=0;i<height;i++) delete data[i];
    delete data;
    return false;
  }
 
  return true;
}

Note that the following code will discard any additional information such as the author or comments. You need to change the code if you want to keep additional information.


Reading Private Chunks
I think loading your custom chunks is the easiest step of them. First some code again:
bool loadAttributes(isPR_struct &isPR, const char *filename)
{
  DataStream ds(filename); // I use my own sf::InputStream

  png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
                                               0, 0, 0);

  if(!png_ptr) return false;

  try
  {
    png_infop info_ptr = png_create_info_struct(png_ptr);
    if(!info_ptr) throw 1;

    png_infop end_ptr = png_create_info_struct(png_ptr);
    if(!end_ptr) throw 1; // don't know why it is needed...

    // set our user chunk handler and tell libpng not to discard them
    png_set_read_user_chunk_fn(png_ptr, &isPR, &chunkReader);
    png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS,
                                (png_const_bytep)"isPR", 1);

    // instead of png_init_io() if using custom file stream
    png_set_read_fn(png_ptr, &ds, &readFunction);

    png_read_info(png_ptr, info_ptr);
    png_read_update_info(png_ptr, info_ptr);

    // you need to read the rows to avoid CRC error... strange
    for(unsigned i=0;i<png_get_image_height(png_ptr, info_ptr);i++)
    {
      png_read_row(png_ptr, 0, 0);
    }

    // this might actually read your chunks
    png_read_end(png_ptr, end_ptr);

    png_destroy_read_struct(&png_ptr, &info_ptr, &end_ptr);
  }
  catch(int &e)
  {
    png_destroy_read_struct(&png_ptr, &info_ptr, &end_ptr);
    return false;
  }
 
  return true;
}

void readFunction(png_structp png_ptr, png_bytep data, png_size_t size)
{
  DataStream *ds = (DataStream*)png_get_io_ptr(png_ptr);

  // some operations on our data stream... (read size bytes into data)
  // in standard C it would be: fread(data, 1, size, fp);
}

int chunkReader(png_structp png_ptr, png_unknown_chunkp chunk)
{
  // todo: you should first check whether:
  //  - chunk->name=="isPR"
  //  - chunk->size==sizeof(isPR_struct)

  isPR_struct *isPR_input = (isPR_struct*)chunk->data;
  isPR_struct *isPR_output = (isPR_struct*)png_get_user_chunk_ptr(png_ptr);

  *isPR_output = *isPR_input;

  // return value < 0 if the chunk had an error (e.g. mismatching size),
  // return 0 if the chunk is unknown and
  // value > 0 on success
  return 1;
}


I hope this helps you. I don't guarantee that the could would really works and wouldn't crash. I had a lot of crashes myself before arriving the final code you see above.
Please give me comments and error reports. I'm going to make the code better if something doesn't work well. Maybe I will write a small library which makes the tasks much easier.

Hope you like it :)

41
General / Re: Jumping Formulas
« on: May 16, 2012, 08:00:25 pm »
Most important, you should declare what the variables mean. And if I were you, I'd only provide the linearly independent equalities, i.e. not both T0=k/g and g=k/T0. People should be able to rearrange the terms. Once, you write k = 2h / (1 + T0), the units in the denominator are inconsinstent. Generally, I think your formulas are incorrect...


Let's find the formulas. First, we define some variables:
h(t) = height of the object at time t
T = total jump time
H = maximal height

When we assume h(t) to be a parabola, it has a quadradic equation:
h(t) = a*t2 + b*t + c

Some relations at begin, middle and end of the jump are known:
h(0) = 0
h(T/2) = H
h(T) = 0

By inserting them in the equation, we can compute a, b and c:
a = -4H/T2
b = 4H/T
c = 0

Therefore, we have:
h(t) = -4H/T2 * t2 + 4H/T * t

A constantly accelerated object with acceleration g and initial velocity v0 travels the following distance during time t:
s(t) = g/2 * t2 + v0 * t

Thus, comparing the coefficients of h(t) and s(t) yields:
g = -8H/T2
v0 = 4H/T

Note however that I'm talking about the continuous case. In a game, you have to consider a certain time step, to which your gravity and velocity need to be adapted.

I've checked my formulas and they fully worked.
But it might depend on how people interpret the variables, about like you said I think.

For me: T0 (it's about like your T/2) is the time the object is at the highest point.
k (your v0, yeah it's a better name for it) is always positive and means the velocity to the upwards.
g is always positive as well but means the acceleration downwards.

If I for example want the object reaching a height of 6 pixels (H=6) in 3 frames (T0=3), I get v0=3 and g=1, and what's calculated then is:
3 + 2 + 1
Result is 6 (height) and there are 3 summands (T0=3).


You are taking the total time being in air, which might be easier to use. I think our both formulas are correct, but we are interpreting the variables differently. Is it like that?

42
General / Jumping Formulas
« on: May 16, 2012, 06:23:25 pm »
If you are developing a platformer game, you typically need some gravity on your character and the start velocity on jumping. I think people often test some values out until they have a jump (duration and height) they like. But I've worked on a more efficient way:
You decide the jump duration and maximum height the character can reach and calculate the needed gravity and initial velocity.
I've worked on some formulas you can easily use. Tell me whether you find it useful or not, or if there are any mistakes or confusion.

And I hope this toptic fits here.


EDIT:
Here are the formulas without the need of any download.

For a jump'n run alias platformer you might want the protagonist jumping a very certain height and time, but you don't know which gravity and jump velocity to set. Here are some useful formulas with that you can calculate the needed parameters for your wished jump.

T0 is the duration from jump start till maximum height, where v (vertical velocity) is 0 and changes sign. Example: 10
T0 = k / g

hmax is the max height the player jumps. Example: 55
hmax = (k + k * T0) / 2

k is the jump velocity (should be > 0). That is the initial velocity on jump. Example: 10 in the attachment it's -10. that's wrong!
k = (2 * h) / (1 + T0)

g is the gravity (should be > 0). That is the value, that is subtracted from your current velocity. Example: 0.5
g = k / T0

[attachment deleted by admin]

43
SFML projects / Re: sfMidi 1.1 - Play MIDI in SFML
« on: April 12, 2012, 01:59:29 pm »
Have you tried following the instructions here?
http://sourceforge.net/apps/trac/fluidsynth/wiki/BuildingWithCMake
Okay, thanks. I haven't found that page before, I think.
But I will not try it now, as I have to download several things, changing the system paths etc.
I have to think about all that carefully later, and I'm sure, then everything will work.
Otherwise, I should contact the fluidsynth community, and not spam an SFML thread :P

44
SFML projects / Re: sfMidi 1.1 - Play MIDI in SFML
« on: April 11, 2012, 10:02:06 pm »
Oh, I just remembered, after clicking configure in CMake for the 1st time, when the red options appear, you'll have to change certain settings, I unchecked everything except for enable-aufile (and BUILD_SHARED_LIBS of course), that's pretty much the bare minimum, which means you don't have anything to play the sound (since we have SFML for that), and enable-aufile allows fluidsynth to load MIDI files, which is all we need.
Trying using that same setting and see if it works.
I tried now, too, but when making I get a lot of error messages.

F:\Benutzer\Simon\Desktop\fluidsynth-1.1.5\build>make
Scanning dependencies of target libfluidsynth
[  2%] Building C object src/CMakeFiles/libfluidsynth.dir/fluid_dll.c.obj
[  5%] Building C object src/CMakeFiles/libfluidsynth.dir/drivers/fluid_dsound.c
.obj
f:/Benutzer/Simon/Desktop/fluidsynth-1.1.5/src/drivers/fluid_dsound.c:30:20: err
or: dsound.h: No such file or directory
f:/Benutzer/Simon/Desktop/fluidsynth-1.1.5/src/drivers/fluid_dsound.c:46: error:
 expected specifier-qualifier-list before 'LPDIRECTSOUND'
f:/Benutzer/Simon/Desktop/fluidsynth-1.1.5/src/drivers/fluid_dsound.c: In functi
on 'fluid_dsound_audio_driver_settings':
f:/Benutzer/Simon/Desktop/fluidsynth-1.1.5/src/drivers/fluid_dsound.c:93: error:
 'LPDSENUMCALLBACK' undeclared (first use in this function)
f:/Benutzer/Simon/Desktop/fluidsynth-1.1.5/src/drivers/fluid_dsound.c:93: error:
 (Each undeclared identifier is reported only once

and many, many more :(

45
SFML projects / Re: sfMidi 1.1 - Play MIDI in SFML
« on: April 11, 2012, 05:21:49 pm »
yes, you'll have to compile fluidsynth as well if there's no precompiled one available. but if I recall correctly, fluidsynth uses cmake as well, so you shouldn't have problems compiling.
But as far as I remember I had trouble somehow :(
Cmake worked fine for SFML-2.0, don't know why I trouble with fluidsynth. Maybe it has got additional dependencies I have to install or something. But for that I should ask in the fluidsynth community, and not here ;)

Pages: 1 2 [3] 4 5 ... 8