Solving state machine problem for a centipede

Started by
30 comments, last by haegarr 10 years, 4 months ago


The problem is the other nodes should really follow the head, correct ?

Yes. See post #10 for a possibility.


How is it possible to let each node move down one and reverse the velocity ? and all of them should be synchronized ?

What is wrong with the approach in post #18? If the subsequent segments follow their predecessors, then it is enough to control the head. The head can be controlled like shown in post #18 or in a similar way (e.g. Paradigm Shifter has mentioned that some explicit state can be dropped in favor for some implicit state, but I'd suggest you to do so only if you've understood the mechanics entirely).

Advertisement

I tried the following approach, but there are a lot of in correct logic. I also would like to control the displacement between the nodes of the centipede. Kindly find the attached .swf, try to run it, also close it, then re-run it get it different mushrooms.


public function gameLoop(event:Event):void
{


var t:int = getTimer();
var dt:Number = (t - _t) * 0.001;
_t = t;


for (var i:int = m_nodes.length - 1; i >= 0; --i)
{
if (i != 0)
{


m_nodes[i].x = m_nodes[i - 1].x;
m_nodes[i].y = m_nodes[i - 1].y;
}
else
{
m_nodes[0].x += m_nodes[0].vx * (100) * dt;
m_nodes[0].y += m_nodes[0].vy * (100) * dt;
}
}


for (var i:int = 0; i < m_nodes.length; i++)
{


var savedDir:int = m_nodes[i].vx;


for (var b:int = 0; b < m_bricks.length - 1; b++)
{
if (m_nodes[0].hitTestObject(m_bricks[b]))
{
m_nodes[0].vx = 0;
m_nodes[0].vy = 1;


delayCounter++;
if (delayCounter > 5)
{
delayCounter = 0;


m_nodes[0].vx *= -1; 
m_nodes[0].vy = 0;
}


}


}


if (m_nodes[0].x > 750)
{
m_nodes[0].vx = 0;
m_nodes[0].vy = 1;


delayCounter++;
if (delayCounter > 5)
{
delayCounter = 0;
m_nodes[0].vx = -1;
m_nodes[0].vy = 0;


}


}


else if (m_nodes[0].x < 40 && m_nodes[0].y < 500)
{


trace("x < ");
m_nodes[0].vx = 0;
m_nodes[0].vy = 1;


delayCounter++;
if (delayCounter > 5)
{
delayCounter = 0;
m_nodes[0].vx = 1;
m_nodes[0].vy = 0;


}


}


if (m_nodes[0].y < 40 && m_nodes[0].x < 40)
{
trace("y <  x <");
m_nodes[0].vx = 0;
m_nodes[0].vy = 1;
delayCounter++;
if (delayCounter > 5)
{
delayCounter = 0;
m_nodes[0].vx = 5;
m_nodes[0].vy = 0;


}


}


else if (m_nodes[0].y > 560 && m_nodes[0].x > 40)
{
trace("y > x < ");
m_nodes[0].vx = -1;
m_nodes[0].vy = 0;


}


}
}
}

I debugged it a lot I still can't find where is the problem. The space between the nodes can not be adjusted correctly relative to the speed and the collision also dismisses at a lot of cases :/

1. Updating the segments could be made a bit simpler, so enhancing both efficient and readability:


    // updating segments: 1st the dependents in reverse order, then the head
    for (var i:int = m_nodes.length - 1; i > 0; --i)
    {
        m_nodes[i].x = m_nodes[i - 1].x;
        m_nodes[i].y = m_nodes[i - 1].y;
    }
    m_nodes[0].x += m_nodes[0].vx * (100) * dt;
    m_nodes[0].y += m_nodes[0].vy * (100) * dt;

2. Your loop following the update of the segments again iterates all segments. This is wrong. It has to be run for the head only (hence no loop needed at all), because only the head is able to initiate a change in movement. You can see that the loop variable i is never used again (with the exception of initializing savedDir which itself is never used again. So remove those lines:


    for (var i:int = 0; i < m_nodes.length; i++)
    {
        var savedDir:int = m_nodes[i].vx;
        ...
    }

If you don't, then collision detection and correction is done as often as segments are available. Together with your delayCounter this yields in more or less arbitrarily looking decisions of direction updates.

3. I've mentioned to use memorized directions (or velocities if you wish). Your code snippet instead operates without a memory, as can be seen here from brick collision handling code:


    m_nodes[0].vx = 0;
    m_nodes[0].vy = 1;

    delayCounter++;
    if (delayCounter > 5)
    {
        delayCounter = 0;
        m_nodes[0].vx *= -1; 
        m_nodes[0].vy = 0;
    } 

From a principle point of view: You change the movement to (0,1), i.e. downwards, in each pass. In each (!) fifth pass you then change the movement to (0*-1,0) == (0,0)! This is obviously not what you want.

I would try to use said memorized velocity. If you want to defer changing back velocity until after some passes, then can it be done the following way:

At collision detection, set the new downward direction immediately. Further set the next direction memory immediately. Further set a delay counter to e.g. 5 also immediately. Decrement the delay counter each pass through the simulation loop if it is still greater than zero. In that pass where the delay counter becomes 0 (i.e. set from 1 to 0) override the current velocity with the memorized one. You can use m_delayCounter>0 also to avoid repeated collision detection.

Maybe there is also an issue with checking the borders, but I think that can be seen better when the mentioned issues are corrected.

BTW: It would be nice if you could preserve indentation when posting code. I've also the impression that you mix several programming styles; e.g. some non local variables are not prefixed, one is prefixed with an underscore, and some are prefixed with m_ (should probably denote an object member, but is used inside the game loop).

Thank you so much!. will follow all things you mentioned.

I have another problem, which is setting an offset between the nodes, they are overlapping each other.

If your sections don't move 1 tile at a time, you are going to get either overlap or gaps. Try pushing several marbles around a maze with right angle walls where the marble snugly fits the corridor size, gaps will appear when you turn a corner. It's up to you whether you prefer gaps or overlap.

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

I'm not using any tilemap, why should I use a tile map to let the sections not overlap ? the current problem is they are like this xxxx instead of x x x x x x

When using the method of copying the former position of segments to their respective follower, then the speed of all segments is prescribed by the head. Any gaps between segments will be constant over time (until non horizontal or vertical directions are allowed). The actual gap is defined at initialization time as the difference between the (x,y) tuples of following segments. Of course, problems due to numerical imprecision may occur after some time if floating point numbers and/or rounding is used.

Thanks for the inputs.

I have initialized the nodes as follow. The gab can not be defined here, as whatever happens the node's prev position will be copied anyway. How would I set it ?


for (var i:int = 0; i < 5; i++)
{
 var sprite:Node = new Node();
 sprite.x = 100 + i * 90;
 sprite.y = 20;
 sprite.vx = -1;
 sprite.vy = 0;
 sprite.previousDirX = -1;
 m_nodes.push(sprite);
 addChild(m_nodes[i]);
}

Regarding the memorization technique, and the collision I have done that, it works but still a lot of flaws.

Is the algorithm correct ?


if (m_nodes[0].x > 750)
{
 delayCounter++;

 if (delayCounter == 1)
 {
  m_nodes[0].previousDirX = m_nodes[0].vx;
 }

 m_nodes[0].vx = 0;
 m_nodes[0].vy = 1;

if (delayCounter > 2)
{
 delayCounter = 0;
 trace("prev" + m_nodes[0].previousDirX);
 m_nodes[0].vx = -1 * m_nodes[0].previousDirX; 
 m_nodes[0].vy = 0;
}

 }
}

How did you ident your code that is posted here ? manually ?

My apologize, I haven't noticed that the speed is unrelated to the offset; instead I also meant you are dealing with tiles. So, a single step memory is not sufficient. Too avoid trapping into a similar pitfall another time, let me tell what I think...

The solution is in general to build a rail along the way the head is moving. While the head is free to move within the ruleset, any subsequent segment is constrained to be positioned onto the rail (can also be used for orientation). The question is how to model the rail.

Having an explicit rail allows for (a) any speed, including (b) acceleration of the centipede (i.e. speed can vary), and (c) fast paced movement direction changes. This would be the full blown solution from the standpoint of features. The rail could be stored as a sequence of straight line segments, i.e. each time the direct is changed puts a new part to the rail, and each time the head is moved the previous direction simply elongates the current part.

If any movement direction change cannot occur before the previous change has reached the subsequent segment, then it is sufficient to store rail direction changes within the centipede segments, because at most one memory per segment is needed. Right?

Well, greatest simplification is when the world and movement is tiled (the original game does so). But we now know that it isn't ...

What features do you need?


How did you ident your code that is posted here ? manually ?

If you paste the copy of code from your IDE here then indentation has already gone. Selecting and pressing <> puts the snippet into a code box but doesn't help with indentation (at least for me). But if you first press <> and paste the copy into the then opening dialog, indentation is preserved. That way further allows to switch on line numbering.

This topic is closed to new replies.

Advertisement