Click here to Skip to main content
15,867,835 members
Articles / Mobile Apps

Windows Phone Labyrinth

Rate me:
Please Sign up or sign in to vote.
4.95/5 (53 votes)
31 Jan 2012CPOL10 min read 128.2K   53.8K   115   40
A Windows Phone application using accelerometer emulator and Farseer physics engine

Image 1

Table of Contents

Introduction

Last month (September 2011) I received very great news: the Windows PHone SDK 7.1 had just been released. I opened the "What's New" session and it was full of nice new features. I was particularly interested in the new Windows Phone Emulator, which now has the ability to emulate device sensors, such as the Windows Phone Accelerometer.

This article is intended to show how to play with this new accelerometer emulation, accompanied by a simple application that use it.

System Requirements

To use the Windows Phone Labyrinth provided with this article, you must download and install the following 100% free development tool directly from Microsoft:

  • Visual Web Developer 2010 Express for Windows Phone
    Whether you’re familiar with, or new to, Silverlight and XNA Game Studio programming, Visual Studio 2010 Express for Windows Phone provides everything you need to get started building Windows Phone apps.

  • Windows Phone SDK 7.1
    The Windows Phone Software Development Kit (SDK) 7.1 provides you with all of the tools that you need to develop applications and games for both Windows Phone 7.0 and Windows Phone 7.5 devices.
  • What's new in Windows Phone SDK 7.1

    The new Windows Phone SDK 7.1 and the new documentation are now packed up with lots of new features:

    • Multi-targeting and App Compatibility
    • Multitasking
    • The Execution Model and Fast Application Switching
    • Alarms and Reminders
    • Background Agents
    • Background File Transfers
    • Background Audio
    • Media
    • Silverlight 4
    • Sensors
    • Sockets Support
    • Network Information
    • Push Notifications
    • Live Tiles
    • Silverlight and XNA Integration
    • Application Performance Analysis
    • Visual Basic Support
    • Advertising
    • WebBrowser Control
    • Device Status
    • Local Database
    • Isolated Storage Explorer
    • Launchers and Choosers
    • Contacts and Calendar
    • Encrypted Credential Store
    • User Experience Design Guidelines
    • Certification Requirements
    • Marketplace Test Kit
    • Camera
    • Pictures Extensibility
    • Search Extensibility
    • Application Bar
    • On-Screen Keyboard
    • System Tray and Progress Indicator
    • OData Client
    • Globalization and Localization
    • Creating Trial Applications

    From all these features, the one we will be dealing with in this article is Sensors. But not all kinds of sensors (since "sensors" include accelerometer, gyroscope sensors, combined motion and rotation rate). For now, we will play only with the accelerometer. Hopefully, in a near future we can focus on other features of WP7.

    The Accelerometer Emulator

    To access the new accelerometer emulator is quite easy. First, launch Windows Phone emulator. Then move the mouse over the emulator window. At this point, the same vertical icon bar you found in the previous version of the emulator will be shown. The difference is the ">>" icon that you can spot at the bottom of the icon bar. Click it and a new "Additional Tools" window will be opened:

    Image 2

    Only that first tab, "Accelerometer", matters to us. It has the 3D rendering of the device (notice that this rendering doesn't show the same screen of the emulator). It's a fake screen with static tiles. But hey, Microsoft, it would be very cool to have here the actual device screen, don't you think?

    Image 3

    The second thing to notice in the accelerometer emulator is the Orientation dropdown list. We need first to pick the correct orientation (portrait or landscape and standing or flat) to correctly perform the emulation. Our application will only work correctly if the device is in the horizontal position, so you must choose either Portrait Flat or Landscape Flat orientation.

    Image 4

    The second thing to notice in the accelerometer emulator is the little red circle at the center of the window. Drag it and move it around the screen in order to emulate the accelerometer.

    Image 5

    As you move the accelerometer control point around, the X, Y and Z coordinates change and are displayed on the screen. Also, this data is also passed to the Windows Phone Emulator in real time, so any changes you make can be read by the application.

    Image 6

    In code, the accelerometer functions are part the <class>Microsoft.Devices.Sensors namespace:

    C#
    using Microsoft.Devices.Sensors;

    As soon as the application begins, we start the accelerometer, which fires the update event every 20 millisecond:

    C#
    private void StartAccelerometer()
    {
        if (accelerometer == null)
        {
            // Instantiate the accelerometer.
            accelerometer = new Accelerometer();
    
            // Specify the desired time between updates. The sensor accepts
            // intervals in multiples of 20 ms.
            accelerometer.TimeBetweenUpdates = TimeSpan.FromMilliseconds(20);
    
            accelerometer.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<AccelerometerReading>>(accelerometer_CurrentValueChanged);
        }
    
        accelerometer.Start();
    }
    

    When the accelerometer sensor value is changed, we simply change the <class>gravity parameter of the <class>World object. The World object is used to manage all other objects in Farseer:

    C#
    void accelerometer_CurrentValueChanged(object sender, SensorReadingEventArgs<AccelerometerReading> e)
    {
        // Note that this event handler is called from a background thread
        // and therefore does not have access to the UI thread. To update
        // the UI from this handler, use Dispatcher.BeginInvoke() as shown.
    
        if (World != null)
        {
            World.Gravity = new Vector2(e.SensorReading.Acceleration.Y * -ACC_GRAVITY_COEF, e.SensorReading.Acceleration.X * -ACC_GRAVITY_COEF);
        }
    }
    

    Farseer Physics

    Farseer is a wonderful open source physics engine, based on the original Box 2D open source project (as an aside, the Angry Birds game uses Box2D). The difference is that Box2D is written in C++ (and has been ported to many languages), while Farseer is made with C# aiming Silverlight and XNA. Also, the Farseer website claims this framework has many other features other than the ones provided by the original Box2D release.

    Image 7

    Image 8

    Creating The First Object: Ball

    Image 9

    By default, Farseer renders a circle object as an ellipse with a certain border and texture. Our ball is nothing more than a Farseer circle rendered with a static texture of a steel ball. We first load the ball texture and define the initial position of the ball:

    C#
    public override void LoadContent()
    {
        base.LoadContent();
        LoadTextures();
        DefineScreenRectangle();
        InitializeBall();
        InitializeLabyrinth();
        StartAccelerometer();
    }
    
    private void InitializeLabyrinth()
    {
        List<Vertices> verticesList = GetVerticesFromLabyrinthTexture();
        ScaleVertices(verticesList);
        CreateLabyrinthBody(verticesList);
    }
    
    private void CreateLabyrinthBody(List<Vertices> verticesList)
    {
        //Create a single body with multiple fixtures
        labyrinth = BodyFactory.CreateCompoundPolygon(World, verticesList, 1f, BodyType.Dynamic);
        labyrinth.BodyType = BodyType.Dynamic;
        labyrinth.IsStatic = true;
    }
    
    private void ScaleVertices(List<Vertices> verticesList)
    {
        //scale the vertices from graphics space to sim space
        Vector2 vertScale = new Vector2(ConvertUnits.ToSimUnits(1)) * SCALE_FOR_WP_RESOLUTION;
        foreach (Vertices vertices in verticesList)
        {
            vertices.Scale(ref vertScale);
        }
    }
    
    private List<Vertices> GetVerticesFromLabyrinthTexture()
    {
        Vertices textureVertices;
        //Create an array to hold the data from the texture
        uint[] data = new uint[labyrinthTexture.Width * labyrinthTexture.Height];
    
        //Transfer the texture data to the array
        labyrinthTexture.GetData(data);
    
        //Find the vertices that makes up the outline of the shape in the texture
        textureVertices = PolygonTools.CreatePolygon(data, labyrinthTexture.Width, false);
    
        //The tool return vertices as they were found in the texture.
        //We need to find the real center (centroid) of the vertices for 2 reasons:
    
        //1. To translate the vertices so the polygon is centered around the centroid.
        Vector2 centroid = -textureVertices.GetCentroid();
        textureVertices.Translate(ref centroid);
    
        //2. To draw the texture the correct place.
        labyrinthOrigin = -centroid;
    
        //We simplify the vertices found in the texture.
        textureVertices = SimplifyTools.ReduceByDistance(textureVertices, 4f);
    
        //Since it is a concave polygon, we need to partition it into several smaller convex polygons
        return BayazitDecomposer.ConvexPartition(textureVertices);
    }
    
    private void InitializeBall()
    {
        Vector2 startPosition = new Vector2(-20f, -11f);
        Vector2 endPosition = new Vector2(20, -11f);
        ball = new Ball(World, this, startPosition, 1.5f, false);
        ball.BallCollided += new Labyrinth.Ball.BallCollisionEventHandler(ball_BallCollided);
    }
    
    private void DefineScreenRectangle()
    {
        var rectangle = ScreenManager.GraphicsDevice.Viewport.Bounds;
        screenRectangle = new Rectangle(-rectangle.Width, -rectangle.Height, rectangle.Width * 2, rectangle.Height * 2);
    }
    
    private void LoadTextures()
    {
        backgroundTexture = ScreenManager.Content.Load<Texture2D>("Common/woodenbackground");
        ballofSteelTexture = ScreenManager.Content.Load<Texture2D>("Samples/ballofsteel");
        ballShadowTexture = ScreenManager.Content.Load<Texture2D>("Samples/ballshadow");
        woodenForegroundTexture = ScreenManager.Content.Load<Texture2D>("Samples/woodenforeground");
        labyrinthShadowTexture = ScreenManager.Content.Load<Texture2D>("Samples/labyrinthshadow");
        labyrinthTexture = ScreenManager.Content.Load<Texture2D>("Samples/labyrinth");
    }
    

    Then we draw the ball using the preloaded textures:

    C#
    public override void Draw(GameTime gameTime)
    {
        BeginSpriteBatch();
        DrawBackground();
        DrawLabyrinthShadow();
        DrawBall();
        DrawLabyrinth();
        EndSpriteBatch();
        base.Draw(gameTime);
    }
    
    private void DrawLabyrinth()
    {
        ScreenManager.SpriteBatch.Draw(labyrinthTexture, ConvertUnits.ToDisplayUnits(labyrinth.Position),
                                       null, Color.Azure, labyrinth.Rotation, labyrinthOrigin, SCALE_FOR_WP_RESOLUTION, SpriteEffects.None, 0f);
    }
    
    private void DrawBall()
    {
        var ballShadowVector = new Vector2(World.Gravity.X, World.Gravity.Y) * ACC_BALL_SHADOW_COEF;
        ball.Draw(ballofSteelTexture, ballShadowTexture, ballShadowVector);
    }
    
    private void DrawLabyrinthShadow()
    {
        var shadowVector = new Vector2(World.Gravity.X, World.Gravity.Y) * -ACC_SHADOW_COEF;
    
        ScreenManager.SpriteBatch.Draw(labyrinthShadowTexture, ConvertUnits.ToDisplayUnits(labyrinth.Position),
        null, Color.Azure, labyrinth.Rotation, labyrinthOrigin - shadowVector, SCALE_FOR_WP_RESOLUTION, SpriteEffects.None, 0f);
    }
    
    private void DrawBackground()
    {
        ScreenManager.SpriteBatch.Draw(backgroundTexture, screenRectangle, Color.White);
    }
    
    private void EndSpriteBatch()
    {
        ScreenManager.SpriteBatch.End();
    }
    
    private void BeginSpriteBatch()
    {
        ScreenManager.SpriteBatch.Begin(0, null, null, null, null, null, Camera.View);
    }
    

    Setting Up The Second Object: The Labyrinth

    The labyrinth itself is rendered with 3 textures: the wooden background, the labyrinth shadow and the labirinth wooden borders:

    Image 10

    As a result, the shadows helps in imitating the 3D look and feel of the labyrinth according to the rotation provided by the accelerometer data:

    Image 11

    the labyrinth texture is first loaded much like the steel ball mentioned earlier:

    C#
    public override void LoadContent()
    {
        .
        .
        .
        //load texture that will represent the physics body
        labyrinthTexture = ScreenManager.Content.Load<Texture2D>("Samples/labyrinth");
        .
        .
        .
    }
    

    Now we create our labyrinth by joining a series of rectangles, right? Wrong! Unlike the ball, the labyrinth is created from the texture image of the labyrinth. This is a very nice feature of Farseer engine that makes our life much easier! The farseer built-in functions create a solid object from our labyrinth plain image, provided that we leave the empty spaces with the transparent color, like this:

    Image 12

    Now imagine how easy it is to create new levels... amazing, isn't it? The Farseer functions will do the hard job for us, and from the code below, the part that is more worth mentioning is the BayazitDecomposer.ConvexPartition method, which creates smaller convex polygons out of a single big concave polygon:

    C#
    public override void LoadContent()
    {
        .
        .
        .
    
        //Create an array to hold the data from the texture
        uint[] data = new uint[labyrinthTexture.Width * labyrinthTexture.Height];
    
        //Transfer the texture data to the array
        labyrinthTexture.GetData(data);
    
        //Find the vertices that makes up the outline of the shape in the texture
        Vertices textureVertices = PolygonTools.CreatePolygon(data, labyrinthTexture.Width, false);
    
        //The tool return vertices as they were found in the texture.
        //We need to find the real center (centroid) of the vertices for 2 reasons:
    
        //1. To translate the vertices so the polygon is centered around the centroid.
        Vector2 centroid = -textureVertices.GetCentroid();
        textureVertices.Translate(ref centroid);
    
        //2. To draw the texture the correct place.
        labyrinthOrigin = -centroid;
    
        //We simplify the vertices found in the texture.
        textureVertices = SimplifyTools.ReduceByDistance(textureVertices, 4f);
    
        //Since it is a concave polygon, we need to partition it into several smaller convex polygons
        List<Vertices> list = BayazitDecomposer.ConvexPartition(textureVertices);
    
        //Adjust the scale of the object for WP7's lower resolution
    
        scale = 2f;
    
        //scale the vertices from graphics space to sim space
        Vector2 vertScale = new Vector2(ConvertUnits.ToSimUnits(1)) * scale;
        foreach (Vertices vertices in list)
        {
            vertices.Scale(ref vertScale);
        }
    
        //Create a single body with multiple fixtures
        labyrinth = BodyFactory.CreateCompoundPolygon(World, list, 1f, BodyType.Dynamic);
        labyrinth.BodyType = BodyType.Dynamic;
        labyrinth.IsStatic = true;
    
        StartAccelerometer();
    }
    

    Haptics, a.k.a. Vibration Feedback

    "Haptics Technology", or "Haptics" is the tactile feedback provided by devices to the their users in response to some event occurring in games or simulations in some kind of virtual reality.

    Although this project may not be called "virtual reality", indeed it tries to imitate those old glass-covered wooden labyrinth boxes with little balls inside. The addition of the tactile feedback certainly improves the user experience and that's why we are using it. But how to implement it in Windows Phone?

    One of the nice aspects of programming for smartphones is that this little toys have many features/devices/sensors that desktop computers don't. So, while in desktop programming you target the keyboard/mouse/screen/speakers/disk, in smartphones you now have access to built-in accelerometer/vibration/gyroscope/multitouch and so on.

    We implement the tactile feedback in Windows Phone just by accessing the Microsoft.Devices.VibrateController class (http://msdn.microsoft.com/en-us/library/microsoft.devices.vibratecontroller(v=vs.92).aspx) and starting and maintaining the vibration for as long as we want. Simply as that. But when and how long will we vibrate the device?

    First, we must handle the collisions between ball and the labyrinth. Then, we should measure the impact of the collision and finally tell the phone to vibrate according to the impact. The harder the impact, the longer the vibration will be maintained.

    As for the implementation, we first use the reference to the Microsoft.Devices namespace:

    C#
    using Microsoft.Devices;

    Then we declare two constants, one for the maximum interval (in milliseconds) of the vibration and the other for the minimum velocity needed to start the vibration.

    C#
    private const float MAX_VIBRATION_MILLISECS = 16f;
    private const float MIN_DELTAVELOCITY_FOR_VIBRATING_DEVICE = 8f;
    

    Next, we implement the BallCollided event of our ball instance:

    C#
    private void InitializeBall()
    {
        .
        .
        .
        ball.BallCollided += new Labyrinth.Ball.BallCollisionEventHandler(ball_BallCollided);
    }
    
    void ball_BallCollided(float deltaVelocity)
    {
        if (deltaVelocity > MIN_DELTAVELOCITY_FOR_VIBRATING_DEVICE)
        {
            VibrateController.Default.Start(TimeSpan.FromMilliseconds(
                deltaVelocity > MAX_VIBRATION_MILLISECS ? MAX_VIBRATION_MILLISECS : deltaVelocity)
            );
        }
    }
    

    Notice that not all collisions cause vibrations. The ball impact velocity must match a predefined minimum value, as well as the vibration must not be kept for too long. We have applied these constraints because otherwise the feedback wouldn't be as real as we expected. For example, little impacts would make the phone vibrate all the time, while big impacts would produce a long vibration. This would be annoying for the user. The best way as I found it, was to keep the vibrations under these two constraints.

    60 frames per second, Thanks Mango Update!

    If you tried to develop games before the Windows Phone Mango update, you may have become disappointed by the fact that XNA only managed to to run at 30 frames per second (no matter how powerful your hardware was). This might have been a nice spec 3 years back, but for the current smartphone platforms 30 fps is terrible for action games.

    Fortunately, Mango update lifted this barrier by letting us to enable the highest frame rate possible for our hardware, which is currently 60 fps. But in order to benefit from this feature, you must first configure yor application to run at 60 fps.

    First, implement the PreparingDeviceSettings event of the GraphicsDeviceManager in your Game class:

    C#
    public class LabyrinthGame : Game
    {
        private GraphicsDeviceManager _graphics;
    
        public LabyrinthGame()
        {
            .
            .
            .
            _graphics.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(_graphics_PreparingDeviceSettings);
            .
            .
            .
        }
    

    Then, inside the PreparingDeviceSettings event, set the PresentationInterval to PresentInterval.One:

    C#
    void _graphics_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
    {
        e.GraphicsDeviceInformation.PresentationParameters.PresentationInterval = PresentInterval.One;
    }
    

    For more information, please refer to "Changed APIs for Windows Phone OS 7.1" documentation:

    API Windows Phone OS 7.0 behavior Windows Phone OS 7.1 behavior
    PresentationParameters.PresentationInterval property The XNA Framework on Windows Phone OS 7.0 targeted a refresh rate of 30 Hz, regardless of which PresentationInterval you set. The XNA Framework on Windows Phone OS 7.1 supports the native device refresh rate, up to 60 Hz. If you set the PresentationParameters.PresentationInterval property to PresentInterval.One, then the PresentInterval.One value is used, targeting the native device refresh rate.

    And that's it. Maybe in future updates this will be the defaul setting, but for now remember to enable it in your applications.

    Looks Cool, But This Is Not A Game Yet

    Yes, at this point, this is not yet a "labyrinth game", this is just "labyrinth". My intention here is not to create a complete game, just to point to the direction to it. Now you could create a decent score control, "holes" in the floor, traps, fans, coils, elevators, all sorts of obstacles, and so on. There is a lot of potential for this to become a very nice game, I think.

    Ideas, Ideas, Ideas...

    From now on, it's up to you to create new apps with Windows Phone accelerometer and/or Farseer Physics Engine. Here are some ideas you might be considering, for example:

    • An Angry Birds-like game
    • A Tetris-like game (using accelerometer to position the pieces)
    • A flight simulator
    • A first person shooter
    • A snooker game
    • A rocket launching game
    • A car racing game

    And this is just to name a few. This list I could imagine in just a few seconds. There is certainly a huge potential in these technologies.

    Final Considerations

    We are glad that the Windows Phone SDK 7.1 that followed the "Mango" update has brought lots of new tools for us developers. I'm sure this will help improving both the quantity and quality of the applications at the Windows Phone Marketplace.

    If you're interested in developing new, exciting apps for the Marketplace, I strongly recommend stay in touch with the newest tools and technologies. First, read the "What's New in the Windows Phone SDK 7.1" paper. Also, download the samples in that page and take a time to understand how it works.

    Image 13

    Keep in mind that there are "only" a little more than 36,000 apps in this one year-old Marketplace, but according to Gartner Group Windows Phone tends to take a more important position in the next years. Here resides an nice opportunity for you to study and create great apps for the platform.

    History

    • 2011-10-31: Initial version.
    • 2011-11-06: Code refactoring. Added: tactile feedback.
    • 2011-11-08: Added: "60 frames per second" section.

    License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


    Written By
    Instructor / Trainer Alura Cursos Online
    Brazil Brazil

    Comments and Discussions

     
    Questioncamera Pin
    Member 1085765817-Jun-14 10:26
    Member 1085765817-Jun-14 10:26 
    GeneralMy vote of 5 Pin
    Ashish Jain(Be Jovial)8-Aug-13 22:08
    Ashish Jain(Be Jovial)8-Aug-13 22:08 
    GeneralMy vote of 5 Pin
    Ranjan.D7-Oct-12 1:41
    professionalRanjan.D7-Oct-12 1:41 
    GeneralMy vote of 2 Pin
    CS14011-Feb-12 1:01
    CS14011-Feb-12 1:01 
    GeneralRe: My vote of 2 Pin
    Pete O'Hanlon1-Feb-12 3:54
    subeditorPete O'Hanlon1-Feb-12 3:54 
    GeneralMy vote of 5 Pin
    Dr.Luiji10-Jan-12 23:16
    professionalDr.Luiji10-Jan-12 23:16 
    GeneralMy vote of 5 Pin
    SamNaseri17-Nov-11 14:39
    SamNaseri17-Nov-11 14:39 
    GeneralRe: My vote of 5 Pin
    Marcelo Ricardo de Oliveira3-Jan-12 14:25
    mvaMarcelo Ricardo de Oliveira3-Jan-12 14:25 
    GeneralMy vote of 5 Pin
    User 482203315-Nov-11 2:42
    User 482203315-Nov-11 2:42 
    GeneralRe: My vote of 5 Pin
    Marcelo Ricardo de Oliveira17-Nov-11 2:58
    mvaMarcelo Ricardo de Oliveira17-Nov-11 2:58 
    GeneralMy vote of 5 Pin
    Ștefan-Mihai MOGA12-Nov-11 21:01
    professionalȘtefan-Mihai MOGA12-Nov-11 21:01 
    GeneralRe: My vote of 5 Pin
    Marcelo Ricardo de Oliveira13-Nov-11 7:10
    mvaMarcelo Ricardo de Oliveira13-Nov-11 7:10 
    GeneralMy vote of 5 Pin
    Dávid Kocsis11-Nov-11 2:04
    Dávid Kocsis11-Nov-11 2:04 
    GeneralRe: My vote of 5 Pin
    Marcelo Ricardo de Oliveira11-Nov-11 5:29
    mvaMarcelo Ricardo de Oliveira11-Nov-11 5:29 
    GeneralIt sounds great Pin
    SamNaseri10-Nov-11 20:48
    SamNaseri10-Nov-11 20:48 
    GeneralRe: It sounds great Pin
    Marcelo Ricardo de Oliveira11-Nov-11 5:27
    mvaMarcelo Ricardo de Oliveira11-Nov-11 5:27 
    GeneralMy vote of 5 Pin
    Kanasz Robert8-Nov-11 23:30
    professionalKanasz Robert8-Nov-11 23:30 
    GeneralRe: My vote of 5 Pin
    Marcelo Ricardo de Oliveira9-Nov-11 10:37
    mvaMarcelo Ricardo de Oliveira9-Nov-11 10:37 
    GeneralMy vote of 5 Pin
    Sergio Andrés Gutiérrez Rojas8-Nov-11 6:01
    Sergio Andrés Gutiérrez Rojas8-Nov-11 6:01 
    GeneralRe: My vote of 5 Pin
    Marcelo Ricardo de Oliveira8-Nov-11 13:59
    mvaMarcelo Ricardo de Oliveira8-Nov-11 13:59 
    General5 Pin
    Meshack Musundi8-Nov-11 1:00
    professionalMeshack Musundi8-Nov-11 1:00 
    GeneralRe: 5 Pin
    Marcelo Ricardo de Oliveira8-Nov-11 13:58
    mvaMarcelo Ricardo de Oliveira8-Nov-11 13:58 
    Thanks man, glad you liked it!
    cheers,
    marcelo
    There's no free lunch. Let's wait for the dinner.

    Take a look at Windows Phone Labyrinth here in The Code Project.

    GeneralMy vote of 5 Pin
    sam.hill6-Nov-11 15:29
    sam.hill6-Nov-11 15:29 
    GeneralRe: My vote of 5 Pin
    Marcelo Ricardo de Oliveira8-Nov-11 13:55
    mvaMarcelo Ricardo de Oliveira8-Nov-11 13:55 
    GeneralMy vote of 5 Pin
    Halil ibrahim Kalkan2-Nov-11 23:53
    Halil ibrahim Kalkan2-Nov-11 23:53 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.