Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / XAML

Soccerlight World Cup 2010

4.96/5 (78 votes)
11 Jul 2010CPOL9 min read 139.3K   2.7K  
A soccer game made with Silverlight and VS 2008.

Soccerlight

Contents

Introduction

This is my first Silverlight game. Some months ago, I had the idea to create something related to the ongoing 2010 FIFA World Cup, but for some time, I was asking myself what could be done? Fortunately, my childhood memories helped me to answer this question.

This game is a product of hours of hard work, mixing both study and trial-and-error approaches. Fortunately, when you really put focus on something, the learning curve starts getting easier. When the road is paved, you are ready to run.

Background

In Brazil, there is a very well-known game named "Futebol de Botão" (Button Football), or "Futebol de Mesa" (Table Football), which deserves at least a brief note.

In the beginning of the 20th century, the boys in Brazil, just like in many other parts of the world, were big fans of football. There were football clubs everywhere, and the radio broadcasting helped spread this entertainment throughout Brazil. According to Wikipedia, someone had this idea to create a table football, where the field was any smooth flat surface, and the players were buttons positioned on a table. The buttons were the large ones, taken from clothes. Much for the despair of the poor mothers of that time, who had to sew new buttons, the table football became one of the most popular games among Brazilian children (specially when they were grounded and forbidden to play real football outdoors...)

Besides the use of clothing buttons, people tried other materials, such as pieces made of coconut shells and bones. The goalkeepers were just matchboxes filled in by sand or some heavy material. But finally, the industry started making buttons from plastic materials, which became the standard for commercial versions of the game.

Since most kids today prefer computer games, in this article, I tried to reproduce the feeling and atmosphere of those plastic buttons.

You can have a quick overview of the game by taking a look at the YouTube video I uploaded, in the link below:

Image 2

System Requirements

In order to get the game to work, you can download the following, if you don't have VS 2008 and Silverlight 3:

Soccerlight Solution

Solution

Figure 1: Solution structure

The Visual Studio 2008 solution is made up mainly by the Soccerlight project, which contains the Silverlight logic itself, and other helper projects, as we can see in the following table.

ProjectDescription
SilverlightThis is the Silverlight project itself.
Silverlight.ControlsThis Silverlight Class Library project that holds some of the custom controls I use in the project.
Silverlight.CoreThis project holds some of the game logic and the model classes.
Silverlight.WebThis project is the start up project that contains the application's entry page.

Intro Menu

The Intro menu consists of a table containing all of the 8 groups which played in the 2010 FIFA World Cup.

The idea is that you pick up a national team and start playing with that team. Then you will have to follow exactly the same games as they were scheduled in the 2010 World Cup.

For the implementation of this teams table, I didn't use much of XAML. I did it mostly programmatically. What you see in the image below is made up of Grid, TextBlock, and Image elements. I know I might have used a ListBox element with a custom template, but... I wanted more freedom than that. By constructing the visual elements programmatically, I feel that I can have much more control over animations, transforms, etc.

Intro

Figure 2: Intro menu

The following function GenerateGroups creates all the 32 teams divided into the 8 groups:

C#
void GenerateGroups()
{
    for(int i = 0; i < 8; i++)
    {
        Border brd = new Border();
        brd.CornerRadius = new CornerRadius(5);
        brd.Margin = new Thickness(2);
        brd.SetValue(Grid.ColumnProperty, i % 4);
        brd.SetValue(Grid.RowProperty, i / 4);

        LinearGradientBrush lgb = new LinearGradientBrush();
        lgb.StartPoint = new Point(0, 0);
        lgb.EndPoint = new Point(1, 1);
        lgb.GradientStops = new GradientStopCollection();
        lgb.GradientStops.Add(new GradientStop() 
          { Offset = 0.0, Color = Color.FromArgb(255, 0, 0, 0)});
        lgb.GradientStops.Add(new GradientStop() 
          { Offset = 0.5, Color = Color.FromArgb(255, 30, 30, 30)});
        lgb.GradientStops.Add(new GradientStop() 
          { Offset = 1.0, Color = Color.FromArgb(255, 40, 40, 40)});
        brd.Background = lgb;

        lgbEven.StartPoint = new Point(0, 0);
        lgbEven.EndPoint = new Point(1, 1);
        lgbEven.GradientStops = new GradientStopCollection();
        lgbEven.GradientStops.Add(new GradientStop() 
              { Offset = 0.0, Color = Color.FromArgb(255, 0, 0, 0) });
        lgbEven.GradientStops.Add(new GradientStop() 
              { Offset = 0.5, Color = Color.FromArgb(255, 30, 30, 30) });
        lgbEven.GradientStops.Add(new GradientStop() 
              { Offset = 1.0, Color = Color.FromArgb(255, 80, 80, 80) });

        Grid grdGroup = new Grid();
        grdGroup.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(28) });
        grdGroup.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
        grdGroup.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
        grdGroup.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(18) });
        grdGroup.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(18) });
        grdGroup.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(18) });
        grdGroup.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(18) });

        TextBlock txtGroupID = new TextBlock()
        {
            Text = ((char)(i + 65)).ToString(),
            Foreground = new SolidColorBrush(Colors.White),
            FontSize = 18,
            FontWeight = FontWeights.Bold
        };
        txtGroupID.SetValue(Grid.ColumnProperty, 0);
        txtGroupID.SetValue(Grid.RowProperty, 0);
        txtGroupID.SetValue(Grid.RowSpanProperty, 4);
        txtGroupID.SetValue(VerticalAlignmentProperty, VerticalAlignment.Center);
        txtGroupID.SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Center);
        grdGroup.Children.Add(txtGroupID);

        for (int j = 0; j < 4; j++)
        {
            Grid grdTeam = new Grid();
            grdTeam.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
            grdTeam.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
            grdTeam.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(25) });
            grdTeam.SetValue(Grid.ColumnProperty, 1);
            grdTeam.SetValue(Grid.ColumnSpanProperty, 2);
            grdTeam.SetValue(Grid.RowProperty, j);
            grdTeam.HorizontalAlignment = HorizontalAlignment.Stretch;
            grdTeam.VerticalAlignment = VerticalAlignment.Center;
            grdTeam.Width = 120;

            Team team = 
              GameHelper.Instance.TeamsDictionary[GameHelper.Instance.TeamCodes[i * 4 + j]];
            Image img = new Image()
            {
                Source = new BitmapImage(new Uri(string.Format(
                         @"http://www.fifa.com/imgml/flags/reflected/m/{0}.png", 
                         team.TeamID.ToLower()), UriKind.Absolute)),
                
                Width = 28.5,
                Height = 25.0,
                VerticalAlignment = VerticalAlignment.Center
            };
            img.SetValue(Grid.ColumnProperty, 0);
            img.SetValue(Grid.RowProperty, j);
            img.VerticalAlignment = VerticalAlignment.Top;
            img.HorizontalAlignment = HorizontalAlignment.Stretch;
            img.Clip = new RectangleGeometry() { Rect = 
                       new Rect(new Point(0, 0), new Point(28.5, 14)) };
            img.Tag = team.TeamID;
            TranslateTransform tf = new TranslateTransform()
            {
                 X = 0,
                 Y = 6
            };
            img.RenderTransform = tf;

            TextBlock txt = new TextBlock()
            {
                Text = team.TeamName,
                Foreground = new SolidColorBrush(Colors.White)
            };
            txt.SetValue(Grid.ColumnProperty, 1);
            txt.SetValue(Grid.RowProperty, j);
            txt.VerticalAlignment = VerticalAlignment.Center;
            txt.HorizontalAlignment = HorizontalAlignment.Stretch;
            txt.Tag = team.TeamID;

            grdTeam.Tag = team.TeamID;
            grdTeam.Children.Add(img);
            grdTeam.Children.Add(txt);
            grdTeam.MouseEnter += new MouseEventHandler(team_MouseEnter);
            grdTeam.MouseLeave += new MouseEventHandler(team_MouseLeave);
            grdTeam.MouseLeftButtonUp += 
                    new MouseButtonEventHandler(team_MouseLeftButtonUp);

            grdGroup.Children.Add(grdTeam);
        }

        brd.Child = grdGroup;

        grdGroupsContainer.Children.Add(brd);
    }
}
Figure 3: The GenerateGroups() function in the IntroMenu.xaml.cs file

Soccerlight Table

If you have chosen England as your team in the Intro menu, you would next be redirected to the following view, which is, of course, the first game of England in the World Cup (England vs. USA, 06/12, in Rustenburg).

Notice that both teams start with the traditional 3-5-2 formation, being 3 fullbacks, 5 midfielders, and 2 forwards. Although this is the default formation for all teams in the game, it wouldn't be so hard to select another formation for the selected teams, since the formation is simply an array of integers:

C#
public Team()
{
    Formation = new int[] {1, 3, 5, 2};
}
Figure 4: Team class constructor, showing how the 3-5-2 formation is set up for every team.

Notice that the array in the code snippet above starts with 1. This is so because we have to take the goalkeeper into consideration.

Table

Figure 5: Soccerlight table, showing the 3-5-2 formations of England vs. USA

Notice in the listing below that the field lines and circles are not really images, but instead are made up of Border and Ellipse elements. Notice also that at the four corners, there are quarter-of-circles, which can easily be created by clipping the ordinary circles (see the Ellipse.Clip tags below) using RectangleGeometry to show only the quarter you want.

XML
<Grid x:Name="LayoutRoot" 
       MouseLeftButtonUp="LayoutRoot_MouseLeftButtonUp" 
       VerticalAlignment="Center" ...
    <Grid.ColumnDefinitions>
        <ColumnDefinition x:Name="colLeftEscapeArea" Width="53"/>
        <ColumnDefinition x:Name="colLeftPosts" Width="53"/>
        <ColumnDefinition x:Name="colLeftGoalArea" Width="70"/>
        <ColumnDefinition x:Name="colLeftPenaltyArea" Width="280"/>
        <ColumnDefinition x:Name="colHalfWay" Width="280"/>
        <ColumnDefinition x:Name="colRightPenaltyArea" Width="70"/>
        <ColumnDefinition x:Name="colRightGoalArea" Width="53"/>
        <ColumnDefinition x:Name="colRightPosts" Width="53"/>
        <ColumnDefinition x:Name="colMenu" Width="32"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition x:Name="rowTopEscapeArea" Height="50"/>
        <RowDefinition x:Name="rowTopFieldLine" Height="108"/>
        <RowDefinition x:Name="rowTopPenaltyArea" Height="67"/>
        <RowDefinition x:Name="rowTopGoalArea" Height="153"/>
        <RowDefinition x:Name="rowBottomGoalArea" Height="67"/>
        <RowDefinition x:Name="rowBottomPenaltyArea" Height="108"/>
        <RowDefinition x:Name="rowBottomFieldLine" Height="50"/>
    </Grid.RowDefinitions>
    <Border Grid.Column="0" Grid.Row="0" 
            Grid.ColumnSpan="8" Grid.RowSpan="1" 
            Background="DarkGreen" ...
    <Border Grid.Column="0" Grid.Row="1" 
            Grid.ColumnSpan="1" Grid.RowSpan="5" Background="DarkGreen" ...
    <Border Grid.Column="1" Grid.Row="1" 
            Grid.ColumnSpan="3" Grid.RowSpan="5" BorderBrush="White" ...
    <Border Grid.Column="4" Grid.Row="1" 
            Grid.ColumnSpan="3" Grid.RowSpan="5" BorderBrush="White" ...
    <Border Grid.Column="1" Grid.Row="3" 
            Grid.ColumnSpan="1" Grid.RowSpan="1" BorderBrush="White" ...
    <Border Grid.Column="6" Grid.Row="3" 
            Grid.ColumnSpan="1" Grid.RowSpan="1" BorderBrush="White" ...
    <Border Grid.Column="1" Grid.Row="2" 
            Grid.ColumnSpan="2" Grid.RowSpan="3" BorderBrush="White" ...
    <Border Grid.Column="1" Grid.Row="2" 
            Grid.ColumnSpan="2" Grid.RowSpan="3" BorderBrush="White" ...
    <Border Grid.Column="5" Grid.Row="2" 
            Grid.ColumnSpan="2" Grid.RowSpan="3" BorderBrush="White" ...
    <Border Grid.Column="7" Grid.Row="1" 
            Grid.ColumnSpan="1" Grid.RowSpan="5" Background="DarkGreen" ...
    <Border Grid.Column="0" Grid.Row="6" 
            Grid.ColumnSpan="8" Grid.RowSpan="1" Background="DarkGreen" ...
    <StackPanel Grid.Column="1" Grid.Row="1" 
            Grid.ColumnSpan="6" Grid.RowSpan="5" Margin="8, 8, 0, 0" ...
        <Image Source="../Images/Soccerlight.png" Stretch="UniformToFill">
            <Image.RenderTransform>
                <ScaleTransform ScaleX="0.25" ScaleY="0.25"/>
            </Image.RenderTransform>
        </Image>
    </StackPanel>
    <Grid Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="6" Grid.RowSpan="5" >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="30"/>
            <ColumnDefinition Width="92"/>
            <ColumnDefinition Width="115"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="15"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="115"/>
            <ColumnDefinition Width="92"/>
            <ColumnDefinition Width="30"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="15"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>
        <Ellipse Grid.Column="0" Grid.Row="0" StrokeThickness="2" Stroke="White">
            <Ellipse.Clip>
                <RectangleGeometry Rect="15,15,30,30"/>
            </Ellipse.Clip>
            <Ellipse.RenderTransform>
                <TranslateTransform X="-15" Y="-15"/>
            </Ellipse.RenderTransform>
        </Ellipse>
        <Ellipse Grid.Column="10" Grid.Row="0" StrokeThickness="2" Stroke="White">
            <Ellipse.Clip>
                <RectangleGeometry Rect="0,15,15,15"/>
            </Ellipse.Clip>
            <Ellipse.RenderTransform>
                <TranslateTransform X="15" Y="-15"/>
            </Ellipse.RenderTransform>
        </Ellipse>
        <Ellipse Grid.Column="0" Grid.Row="6" StrokeThickness="2" Stroke="White">
            <Ellipse.Clip>
                <RectangleGeometry Rect="15,0,15,15"/>
            </Ellipse.Clip>
            <Ellipse.RenderTransform>
                <TranslateTransform X="-15" Y="15"/>
            </Ellipse.RenderTransform>
        </Ellipse>
        <Ellipse Grid.Column="10" Grid.Row="6" StrokeThickness="2" Stroke="White">
            <Ellipse.Clip>
                <RectangleGeometry Rect="0,0,15,15"/>
            </Ellipse.Clip>
            <Ellipse.RenderTransform>
                <TranslateTransform X="15" Y="15"/>
            </Ellipse.RenderTransform>
        </Ellipse>
        <Ellipse Grid.Column="4" Grid.Row="2" Grid.ColumnSpan="3" Grid.RowSpan="3" 
        StrokeThickness="2" Stroke="White"/>
        <Ellipse Grid.Column="5" Grid.Row="3" Fill="White"/>
        <Ellipse Grid.Column="2" Grid.Row="2" Grid.ColumnSpan="1" Grid.RowSpan="3" 
        StrokeThickness="2" Stroke="White">
            <Ellipse.Clip>
                <RectangleGeometry Rect="57.5,0,115,115"/>
            </Ellipse.Clip>
            <Ellipse.RenderTransform>
                <TranslateTransform X="-57.5" Y="0"/>
            </Ellipse.RenderTransform>
        </Ellipse>
        <Ellipse Grid.Column="8" Grid.Row="2" Grid.ColumnSpan="1" 
                 Grid.RowSpan="3" StrokeThickness="2" Stroke="White">
            <Ellipse.Clip>
                <RectangleGeometry Rect="0,0,57.5,115"/>
            </Ellipse.Clip>
            <Ellipse.RenderTransform>
                <TranslateTransform X="57.5" Y="0"/>
            </Ellipse.RenderTransform>
        </Ellipse>
    </Grid>
    <Canvas x:Name="rootCanvas"/>
    <Border Grid.Column="0" Grid.Row="3" 
             Grid.ColumnSpan="1" Grid.RowSpan="1" BorderBrush="White" ...
        <Grid>
            <Grid.Background>
                <ImageBrush ImageSource="../Images/goalnet.png" Stretch="UniformToFill"/>
            </Grid.Background>
        </Grid>
    </Border>
    <Border Grid.Column="7" Grid.Row="3" 
              Grid.ColumnSpan="1" Grid.RowSpan="1" BorderBrush="White" ...
        <Grid>
            <Grid.Background>
                <ImageBrush ImageSource="../Images/goalnet.png" Stretch="UniformToFill"/>
            </Grid.Background>
        </Grid>
    </Border>
    <Border x:Name="brdBallStrengthContainer" 
              Grid.Column="8" Grid.Row="4" Grid.ColumnSpan="1" ...
        <Grid>
            <Border x:Name="brdStrength" CornerRadius="4" Margin="8,56,8,8">
                <Border.Background>
                    <LinearGradientBrush>
                        <GradientStop Color="Red" Offset="0.0"/>
                        <GradientStop Color="Orange" Offset="0.5"/>
                        <GradientStop Color="Yellow" Offset="1.0"/>
                    </LinearGradientBrush>
                </Border.Background>
            </Border>
            <Image x:Name="imgBallStrength" Source="../Images/Jabulani.png" 
                      VerticalAlignment="Top" Margin="0,32,0,0">
                <Image.RenderTransform>
                    <RotateTransform x:Name="rtBallStrength" 
                         CenterX="11.5" CenterY="11.5" Angle="30">
                    </RotateTransform>
                </Image.RenderTransform>
            </Image>
        </Grid>
    </Border>
    <Grid x:Name="grdStadiumScreen" MaxWidth="800" 
             MaxHeight="180" Grid.Column="1" Grid.Row="0"...
        <Grid.Clip>
            <RectangleGeometry Rect="0,0,800,180"/>
        </Grid.Clip>
        <TextBlock Foreground="White" FontSize="120" 
              Text="GOALLLLLL!!!" TextAlignment="Center" Margin="0">
        <TextBlock.RenderTransform>
            <TranslateTransform x:Name="lettersXTranslate" X="0"/>
        </TextBlock.RenderTransform>
        </TextBlock>
        <Image Source="../Images/mask.png" Stretch="Fill" Opacity="0.75"/>
        <Grid x:Name="grdBrightness" Background="Black" Opacity="1.00"/>
    </Grid>
    <Grid.RenderTransform>
        <ScaleTransform ScaleX="0.868" ScaleY="0.8700"/>
    </Grid.RenderTransform>
</Grid>

Score Control

The score control shows the elapsed time, the team nicknames, a blinking ball indicating the playing team, and... the scores.

The elapsed time is controlled by a DispatcherTimer object, which triggers a Tick event every second. This is propagated to the score control to increase the total elapsed time.

In real soccer games, there are two halves of 45 minutes each, separated by a 15-minute break. In Soccerlight, however, the game is over when the timer reaches 30 minutes.

The blinking ball is a useful indicator to show which team is playing at a given moment.

ScoreControl

Figure 6: The score control

The Goals

Each goal is delimited by three borders: two at both sides, and one at the back of the goal. If the ball strikes these borders, it will bounce as if it was hitting a wall. Besides, at both sides in front of the goal, there are goalposts, which behave like real cylindrical goalposts, making the ball bounce as if it was hitting a circular object.

Besides, the goal has a transparent goal net, which gives the game a cool and realistic look and feel.

The rule is simple as that: whenever the ball enters the goal, the application updates the score control to increment the score of the attacking team.

Goals

Figure 7: The goal: notice the borders, the goal posts, and the detailed goal net

Jabulani

Jabulani

Figure 8: The Jabulani ball

As some of the readers may know, Jabulani is the innovative official ball in the FIFA World Cup, so I decided to use it too in Soccerlight World Cup.

Some players have complained about Jabulani in FIFA Cup, because they have reported "strange" behavior and unpredicted direction changing, while other players have declared their love for the ball. (On the other side, no Soccerlight player has complained about the ball yet...)

In Soccerlight, the physics calculations involving the ball are no different from those involving the players (except for the the fact that players have greater diameters and greater friction coefficients). In fact, both ball and players inherit from the same base class, Discoid:

C#
public class Ball : Discoid
{
    public class Player : Discoid
    {

Players

Each team has a set of 11 players. In the real world, each player may have different roles, but in Soccerlight, they are treated in the same manner. The only difference between a striker and a goalkeeper, for example, is the fact that the goalkeeper is closer to the goal and the striker's initial position is closer to the opponent's goal. Maybe in future versions I will define different skills, such as, for example, a skilled striker might have more precision when shooting to goal than other average players.

Players

Figure 9: Players caught in action: say "cheese"...

Player Info Bar

Whenever you select a player, the application will show a Player Info Bar. This bar contains basic info about the player: the number, the name, and the picture.

Each Soccerlight player has a number, a name, and a picture. Numbers and names are hard-coded in the application, but the pictures are taken from the FIFA website, as shown in the code snippet below:

C#
imgPlayer.ImageSource = new BitmapImage(new Uri(string.Format(
    "http://pt.fifa.com/imgml/tournament/worldcup2010/players/xl/{0}.png", 
    teamPlayer.ID), UriKind.Absolute));

The listing below is responsible for showing the Player Info Bar:

XML
<Grid x:Name="grdPlayerInfo" HorizontalAlignment="Center" 
             VerticalAlignment="Bottom" Visibility="Collapsed">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="100"/>
        <ColumnDefinition Width="8"/>
        <ColumnDefinition Width="400"/>
        <ColumnDefinition Width="200"/>
    </Grid.ColumnDefinitions>
    <Border Margin="8,8,0,64" CornerRadius="8" 
             Width="75" Height="100" 
             Background="#C0000000">
        <Border.RenderTransform>
            <TransformGroup>
                <TranslateTransform X="4" Y="4"/>
            </TransformGroup>
        </Border.RenderTransform>
    </Border>
    <Border Margin="8,8,0,64" CornerRadius="8" 
             Width="75" Height="100">
        <Border.Background>
            <RadialGradientBrush>
                <GradientStop Offset="0.0" Color="#00000000"/>
                <GradientStop Offset="9.0" Color="#20000000"/>
                <GradientStop Offset="1.0" Color="#40000000"/>
            </RadialGradientBrush>
        </Border.Background>
        <Border.RenderTransform>
            <TransformGroup>
                <TranslateTransform X="4" Y="4"/>
            </TransformGroup>
        </Border.RenderTransform>
    </Border>
    <Border Margin="8,8,0,64" CornerRadius="8" 
             Width="75" Height="100">
        <Border.Background>
            <ImageBrush x:Name="imgPlayer"/>
        </Border.Background>
    </Border>
    <StackPanel Grid.Column="2" VerticalAlignment="Center" Margin="0,0,0,64">
        <Border BorderBrush="Black" BorderThickness="2" CornerRadius="4">
            <Border.Background>
                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Offset="0.0" Color="Yellow"/>
                    <GradientStop Offset="0.5" Color="Yellow"/>
                    <GradientStop Offset="0.5" Color="Gold"/>
                    <GradientStop Offset="1.0" Color="Goldenrod"/>
                </LinearGradientBrush>
            </Border.Background>
            <Border.RenderTransform>
                <SkewTransform AngleX="-10"/>
            </Border.RenderTransform>
            <StackPanel Orientation="Horizontal">
                <TextBlock x:Name="numPlayer" Text="" Foreground="Black" 
                FontSize="22" FontWeight="Bold" Margin="32,0,0,0"/>
                <TextBlock Text=" - " Foreground="Black" FontSize="22" 
                FontWeight="Bold"/>
                <TextBlock x:Name="txtPlayerName" Text="" Foreground="Black" 
                FontSize="22" FontWeight="Bold" Margin="0,0,0,0"/>
            </StackPanel>
        </Border>
    </StackPanel>
</Grid>

PlayerInfo

Figure 10: Player Info Bar

Strength Control Bar

The Strength Control Bar is the means that you have to calibrate your shot strength. The higher you put the Jabulani ball in the bar, the stronger you shoot the ball.

StrengthControl

Figure 11: Strength Control Bar

The strength is redefined whenever the user clicks in the Strength Control Bar. This is done by the following code:

C#
private void LayoutRoot_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    Point point = e.GetPosition(rootCanvas);

    if ((point.X > strengthPointNW.X) &&
        (point.X < strengthPointSE.X) &&
        (point.Y > strengthPointNW.Y) &&
        (point.Y < strengthPointSE.Y))
    {
        e.Handled = true;
        double relativeY = strengthPointSE.Y - point.Y;
        currentGame.Team1BallStrength = ((strengthPointSE.Y - point.Y) / 
               (strengthPointSE.Y - strengthPointNW.Y)) * 100.0;

        imgBallStrength.Margin = new Thickness(0, point.Y - 
            strengthPointNW.Y - imgBallStrength.ActualHeight / 2.0, 0, 0);
        brdStrength.Margin = new Thickness(8, point.Y - 
            strengthPointNW.Y + imgBallStrength.ActualHeight / 2.0, 8, 8);
    }

Stadium Screen

The role of the Stadium Screen is just to show that a team has scored the goal. Simple as that.

StadiumScreen

Figure 12: Stadium Screen

The Stadium Screen has three different animations. First, there is the translateAnimation, which translates the "GOOAL" string from right to left. Then there is the lettersOpacityAnimation that fades in and fades out the letters of the screen. And finally, there is the screenOpacityAnimation, responsible for fading out the whole Stadium Screen element:

C#
sbStadiumScreen = new Storyboard()
{
    Duration = new Duration(new TimeSpan(0, 0, 0, 10))
};

DoubleAnimation translateAnimation = new DoubleAnimation()
{
    From = 800,
    To = 0,
    Duration = new Duration(new TimeSpan(0, 0, 0, 3))
};

Storyboard.SetTarget(translateAnimation, lettersXTranslate);
Storyboard.SetTargetProperty(translateAnimation, new PropertyPath("X"));

DoubleAnimation lettersOpacityAnimation = new DoubleAnimation()
{
    From = 0.8,
    To = 1.0,
    Duration = new Duration(new TimeSpan(0, 0, 0, 0, 500)),
    AutoReverse = true,
    RepeatBehavior = RepeatBehavior.Forever
};

Storyboard.SetTarget(lettersOpacityAnimation, grdBrightness);
Storyboard.SetTargetProperty(lettersOpacityAnimation, new PropertyPath("Opacity"));

DoubleAnimation screenOpacityAnimation = new DoubleAnimation()
{
    From = 1.0,
    To = 0.0,
    BeginTime = new TimeSpan(0, 0, 0, 0),
    Duration = new Duration(new TimeSpan(0, 0, 0, 4))
};

sbStadiumScreen.Children.Add(translateAnimation);
sbStadiumScreen.Children.Add(lettersOpacityAnimation);
sbStadiumScreen.Children.Add(screenOpacityAnimation);

Storyboard.SetTarget(screenOpacityAnimation, grdStadiumScreen);
Storyboard.SetTargetProperty(screenOpacityAnimation, new PropertyPath("Opacity"));

Wish List and Known Issues

Some issues with the game, and some desirable improvements which I'm willing to be correcting/implementing in future releases:

  • The game still doesn't handle fouls. At first, this seems easy to implement, but depending on where the foul occurred, after the foul, you might have to rearrange the players so that they don't collide with each other, etc.
  • There are still no games for the knockout stage. Maybe I'll generate random results for the other matches, and if your team keeps winning, you'll advance from the round of 16 to the quarter-of-finals, semi-finals, and finally to the final match.
  • It would be very cool to implement an online, service-based, human-to-human version of the game. A WCF Service will do, I think.

Final Considerations

If you reached this line, I'd like to thank you for your patience. Please tell me what you liked or disliked in the application. Criticism and suggestions are welcome, and I'm willing to improve this article and application based on your feedback.

History

  • 2010-06-29: First version.

License

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