Silverlight: Pong

What with all the high-tech announcements around “Project Natal” I thought I might head the other direction and play with something a little more on the low-tech end of the spectrum;

image

a Silverlight 3 version of Pong 🙂

I tried to be reasonably retro here and reproduce the original look and feel somewhat ( didn’t take me long to draw those 2 lines in Expression Blend ).

You can run the application from here – not sure it really makes for a two-player game these days but I spent a few minutes playing against myself.

(keys involved are left, right, up, down, space to start, N for new game, L/S for load/save UI )

A couple of interesting things drop out from this.

One is that I ended up using the new Viewbox control because it is easier for me to program against a game like this using the absolute layout capabilities of a Canvas but I still want a resizable UI. So…I program against a fixed size Canvas and then just drop it into a Viewbox to get a cheap-and-cheerful form of resizable UI.

The other is that I added in a few secret keys. One reveals a different UI;

image

and that UI then allows me to Save out the default UI that the game is using which looks like this;

<controls:Viewbox
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
    Stretch="Fill">
    <Grid>
        <Canvas
            Width="640"
            Height="480"
            Background="#FF242424">
            <Path
                Fill="#FFFFFFFF"
                Stretch="Fill"
                Stroke="#FF98FF94"
                StrokeThickness="10"
                Height="10"
                Width="620"
                UseLayoutRounding="False"
                Canvas.Left="10"
                Canvas.Top="10"
                Data="M35.930759,28.61153 L609.49213,28.61153"
                StrokeDashArray="3"
                StrokeDashOffset="0" />
            <Canvas
                x:Name="CanvasUI"
                Height="460"
                Width="640"
                Canvas.Left="0"
                Canvas.Top="20">
                <Rectangle
                    x:Name="Player1UI"
                    Fill="#FF98FF94"
                    Stroke="{x:Null}"
                    StrokeThickness="10"
                    Height="100"
                    Width="15"
                    Canvas.Top="{Binding PlayerPositions.Player1Position}" />
                <Rectangle
                    x:Name="Player2UI"
                    Fill="#FF98FF94"
                    Stroke="{x:Null}"
                    StrokeThickness="10"
                    Height="100"
                    Width="15"
                    Canvas.Left="625"
                    Canvas.Top="{Binding PlayerPositions.Player2Position}" />
                <Rectangle
                    x:Name="BallUI"
                    Fill="#FF98FF94"
                    Stroke="{x:Null}"
                    StrokeThickness="10"
                    Height="20"
                    Width="20"
                    Canvas.Left="{Binding BallPosition.X}"
                    Canvas.Top="{Binding BallPosition.Y}" />
            </Canvas>
            <Path
                Fill="#FFFFFFFF"
                Stretch="Fill"
                Stroke="#FF98FF94"
                StrokeDashArray="1"
                StrokeDashOffset="3"
                StrokeThickness="10"
                Height="460"
                Width="10"
                UseLayoutRounding="False"
                Canvas.Left="320"
                Canvas.Top="20"
                Data="M303.91434,48.573063 L303.91434,443.24084" />
            <TextBlock
                Height="120"
                Width="84"
                Canvas.Left="349"
                Canvas.Top="39"
                FontFamily="./Fonts/Fonts.zip#OCR A Std"
                FontSize="72"
                TextAlignment="Left"
                TextWrapping="Wrap"
                Foreground="#FF98FF94"
                Text="{Binding PlayerScores.Player1Score}" />
            <TextBlock
                Height="120"
                Width="84"
                Canvas.Left="220"
                Canvas.Top="39"
                FontFamily="./Fonts/Fonts.zip#OCR A Std"
                FontSize="72"
                TextAlignment="Right"
                TextWrapping="Wrap"
                Foreground="#FF98FF94"
                Text="{Binding PlayerScores.Player2Score}">
            </TextBlock>
        </Canvas>
        <TextBlock
            Foreground="#FF98FF94"
            HorizontalAlignment="Center"
            FontSize="72"
            FontFamily="./Fonts/Fonts.zip#OCR A Std"
            VerticalAlignment="Center"
            Text="Game Over"
            Visibility="{Binding IsGameOverVisibility}"/>
    </Grid>
</controls:Viewbox>

and then I could go ahead and tweak that because it’s essentially a templated control ( I should perhaps go the whole-hog here and introduce the Visual State Manager to actually build a control ) but I rely on the UI containing a few pieces;

  • Player1UI
  • Player2UI
  • BallUI
  • CanvasUI

and I make a few properties available to binding;

  • PlayerScores.Player1Score, Player2Score
  • BallPosition.X, BallPosition.Y
  • PlayerPositions.Player1Position, Player2Position

but, with a few caveats, the UI can be ripped and replaced so if I change that UI to look something like;

<controls:Viewbox
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
    Stretch="Fill">
    <Grid>
        <Canvas
            x:Name="CanvasUI"
            Width="640"
            Height="480">
            <Canvas.Background>
                <ImageBrush
                    ImageSource="http://www.windowsvienna.com/Pictures/Windows%207/Wallpaper/Windows%207%20Aurora%20Green%20Wallpaper.jpg"
                    Stretch="Fill" />
            </Canvas.Background>

            <Rectangle
                x:Name="Player1UI"
                Stroke="#FFFFFFFF"
                StrokeThickness="3"
                Height="100"
                Width="15"
                Canvas.Left="25"
                Canvas.Top="{Binding PlayerPositions.Player1Position}"
                RadiusX="5"
                RadiusY="5">
                <Rectangle.Fill>
                    <LinearGradientBrush
                        EndPoint="0.5,1"
                        StartPoint="0.5,0">
                        <GradientStop
                            Color="#FF52BE00" />
                        <GradientStop
                            Color="#FF006909"
                            Offset="1" />
                        <GradientStop
                            Color="#FF90FE6B"
                            Offset="0.51" />
                    </LinearGradientBrush>
                </Rectangle.Fill>
                <Rectangle.Effect>
                    <DropShadowEffect
                        Color="#FF128400" />
                </Rectangle.Effect>
            </Rectangle>

            <Rectangle
                x:Name="Player2UI"
                Stroke="#FFFFFFFF"
                StrokeThickness="3"
                Height="100"
                Width="15"
                Canvas.Left="600"
                Canvas.Top="{Binding PlayerPositions.Player2Position}"
                RadiusX="5"
                RadiusY="5">
                <Rectangle.Fill>
                    <LinearGradientBrush
                        EndPoint="0.5,1"
                        StartPoint="0.5,0">
                        <GradientStop
                            Color="#FF52BE00" />
                        <GradientStop
                            Color="#FF006909"
                            Offset="1" />
                        <GradientStop
                            Color="#FF90FE6B"
                            Offset="0.51" />
                    </LinearGradientBrush>
                </Rectangle.Fill>
                <Rectangle.Effect>
                    <DropShadowEffect
                        Color="#FF128400" />
                </Rectangle.Effect>
            </Rectangle>
            <Image
                x:Name="BallUI"
                Height="48"
                Width="48"
                Source="http://www.pclaunches.com/entry_images/0907/22/microsoft_vista-logo.png"
                Canvas.Left="{Binding BallPosition.X}"
                Canvas.Top="{Binding BallPosition.Y}" />

        </Canvas>
        <TextBlock
            FontFamily="Trebuchet MS"
            Foreground="White"
            HorizontalAlignment="Center"
            FontSize="72"
            VerticalAlignment="Center"
            Text="Game Over"
            Visibility="{Binding IsGameOverVisibility}" />

        <StackPanel
            HorizontalAlignment="Left"
            VerticalAlignment="Top">
            <StackPanel
                Orientation="Horizontal">
                <TextBlock
                    FontFamily="Trebuchet MS"
                    FontSize="20"
                    Foreground="White"
                    Text="Player 1" />
                <TextBlock
                    Margin="10,0,0,0"
                    FontFamily="Trebuchet MS"
                    FontSize="20"
                    Foreground="White"
                    Text="{Binding PlayerScores.Player1Score}" />
            </StackPanel>
            <StackPanel
                Orientation="Horizontal">
                <TextBlock
                    FontFamily="Trebuchet MS"
                    FontSize="20"
                    Foreground="White"
                    Text="Player 2" />
                <TextBlock
                    Margin="10,0,0,0"
                    FontFamily="Trebuchet MS"
                    FontSize="20"
                    Foreground="White"
                    Text="{Binding PlayerScores.Player2Score}" />
            </StackPanel>
        </StackPanel>
    </Grid>
</controls:Viewbox>

then I end up with a new “playing experience” something like;

image

I wonder how far I could take that kind of idea – this was just for fun but I might revisit this whole idea and perhaps;

  • Port the code to WPF and see what that looks like based on my previous post.
  • Introduce the Visual State Manager and turn this into more of a templated control.

Here’s the code to download – not necessarily the most beautiful thing I’ve ever done so don’t read too much into it.