David Kennedy’s Tech Ramblings

Just another WordPress.com weblog

Silverlight Adventures Part 1 July 28, 2008

Filed under: Silverlight Tutorials — dotnetdave @ 12:02 am
Tags: , , ,

The Setup

OK, first things first. We need to install Microsoft Expression Blend (I’m using the 2.5 June 2008 Preview which has been updated for Silverlight Beta 2 and version 3.5 SP1 of the .NET Framework)

http://www.microsoft.com/expression/try-it/default.aspx

You will also need to install the Silverlight Tools Beta 2 for VS 2008 in order to edit Silverlight projects & codefiles in VS:

http://www.microsoft.com/downloads/details.aspx?FamilyId=50A9EC01-267B-4521-B7D7-C0DBA8866434&displaylang=en

This includes Silverlight version 2 client which will allow you to run all the samples in your browsers. Note that this one requires all dependant apps be closed & takes 10 to 15 minutes to install, so its a good time to grab a coffee if you’re so inclined.

Now onto the basics. Aside from the demo’s & tutorials available within Expression Blend, I’ve primarily utilised the
following two resources as a basis to develop my understanding of Silverlight:

http://www.ddjsilverlight.com/tutorial/section1_3.asp
http://www.c-sharpcorner.com/UploadFile/nipuntomar/DataBindingSilverlightWCFService07152008011652AM/DataBindingSilverlightWCFService.aspx

As a starting point, I’d like to develop two small projects to demonstrate the application of Silverlight. The first a simple game, and the second a simple client-side grid capable of aggregating data directly from a backend SqlServer database via a WCF service.

The Game

I asked Rachel (my partner) to suggest a simple game to re-author in Silverlight, and she came back with Pacman. There are similar projects online, however most of them seem to utilise Microsofts XNA/Game Studio which is not my interest in this exercise. I came across http://ctrl-alt-die.blogspot.com/2008/03/pacman-silverlight.html which seems to be exactly what I want to accomplish here, so for my very first Silverlight project I’ll use this post from Dominic Green’s blog as a springboard to get me started into re-creating the game myself in order to gain familiarity with both xaml (I didn’t realise Silverlight utilised so much xaml until this week!) and Expression Blend.

So far, I’ve found that using VS & Expression Blend side-by-side has been the best strategy. The majority can be done within VS, however I am finding that Expression Blend is handy for the positioning & layering of objects visually in the designer.

The xaml for this is very simple, as follows:

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="SilverlightPacman.Page" Width="590" Height="433" KeyDown="UserControl_KeyDown"> <Canvas x:Name="GameMap" Background="black"> <Image Source="pacman_gamemap.png"/> <TextBlock Height="22.31" Width="62" Foreground="White" x:Name="pacPosition" Canvas.Left="510" Canvas.Top="396.11" Text="00" TextWrapping="Wrap" RenderTransformOrigin="1.95200002193451,-19.9109992980957"/> <Image Source="pacman.png" x:Name="pacman" Height="15" Width="15"/> <TextBlock Height="22.31" Width="62" Foreground="White" x:Name="pacCollision" Text="none" TextWrapping="Wrap" Canvas.Left="510" Canvas.Top="300"/> <TextBlock x:Name="score" Height="21" Width="82" Canvas.Top="60" Canvas.Left="10" Text="00000001" Foreground="White" TextWrapping="Wrap"/> </Canvas> </UserControl>

After following the approach indicated in the blog post, we have a simple pacman screen with a moving pacman & collision detection for the boundaries. Now we’re on our own, so lets take it one step at a time

The next thing we need is for our pacman’s mouth to be facing correctly for his direction of movement.
Lets hit IrfanView (or any simple image editor) and produce rotated images for the pacman facing up, down, and left.

Now the key things we are missing is some tic tacs? for our pacman to munch on, and a couple of monsters.

Rather than deliberate on the specifics of how I tackled each area of the game development, I’ll dump the complete source in which these should be clearly apparent.

The Code

namespace SilverlightPacman { public partial class Page : UserControl { int count = 0; int spriteX = 0; int spriteY = 0; int moveX = 0; int moveY = 0; Rect[] rects; int noRects; /// <summary> /// The current appropriate image given his direction of movement (or attempted movement) /// </summary> string pacImage = "pacman.png"; /// <summary> /// Stores the tictacs so we can make our pacman eat them /// </summary> Ellipse[] tictacs; /// <summary> /// The bad guys! /// </summary> Image[] monsters; // and a timer for monsters System.Threading.Timer aiTimer; public Page() { // Required to initialize variables InitializeComponent(); ResetGame(); } /// <summary> /// Clears the game board and restarts game play /// </summary> private void ResetGame() { // clear any existing game objects if (monsters != null) foreach (Image m in monsters) GameMap.Children.Remove(m); if (aiTimer != null) aiTimer.Dispose(); if (tictacs != null) foreach (Ellipse e in tictacs) GameMap.Children.Remove(e); // create the boundaries createOutOfBounds(); // start pacman at the entrance spriteX = 92; spriteY = 190; pacman.SetValue(Canvas.LeftProperty, (double)spriteX); pacman.SetValue(Canvas.TopProperty, (double)spriteY); // and draw some tic tacs for foodage tictacs = new Ellipse[2000]; // should be enough DrawTicTacs(); // lets add some monsters PlaceMonsters(); // start our timer for the monster movement (gives a 2 second headstart for pacman) aiTimer = new System.Threading.Timer(new System.Threading.TimerCallback(RunMonsterAI), null, 2000, 300); } private enum CoinToss { heads, tails } private CoinToss FlipCoin() { Random rand = new Random(); if (rand.Next(42) % 2 == 0) return CoinToss.heads; else return CoinToss.tails; } /// <summary> /// Makes it easy to re-call the movement code. If reverseOverride is tainted, they will make the first possible move /// </summary> /// <param name="reverseOveride"></param> private delegate void moveDelegate(bool reverseOverride); /// <summary> /// Moves the monsters based on the simple strategy of generally moving towards pacman /// </summary> /// <param name="unused"></param> private void RunMonsterAI(object unused) { // rule 1: Monsters should move towards pacman // need UI thread to access DependencyProperties Dispatcher.BeginInvoke(() => { foreach (Image monster in monsters) { // pull position props double pacLeft = (double)pacman.GetValue(Canvas.LeftProperty); double pacTop = (double)pacman.GetValue(Canvas.TopProperty); double monLeft = (double)monster.GetValue(Canvas.LeftProperty); double monTop = (double)monster.GetValue(Canvas.TopProperty); bool moved = false; // need to mixup the order of these so they won't ALWAYS move horizontally when they have both options available moveDelegate horizontal = delegate(bool logicOverride) { // horizontal first if (logicOverride || pacLeft > monLeft) // try right if (!CheckForCollisionAny(monLeft + 5, monTop, 18, 18)) // can move { monLeft += 5; monster.SetValue(Canvas.LeftProperty, monLeft); moved = true; } if (!moved && logicOverride || pacLeft < monLeft) // try left if (!CheckForCollisionAny(monLeft - 5, monTop, 18, 18)) // can move { monLeft -= 5; monster.SetValue(Canvas.LeftProperty, monLeft); moved = true; } }; moveDelegate vertical = delegate(bool logicOverride) { if (!moved && logicOverride || pacTop > monTop) // try down if (!CheckForCollisionAny(monLeft, monTop + 5, 18, 18)) // can move { monTop += 5; monster.SetValue(Canvas.TopProperty, monTop); moved = true; } if (!moved && pacTop < monTop) // try up if (!CheckForCollisionAny(monLeft, monTop - 5, 18, 18)) // can move { monTop -= 5; monster.SetValue(Canvas.TopProperty, monTop - 5); moved = true; } }; moveDelegate flipMove = delegate(bool logicOverride) { if (FlipCoin() == CoinToss.heads) { horizontal(logicOverride); vertical(logicOverride); } else { vertical(logicOverride); horizontal(logicOverride); } }; // try logical moves first flipMove(false); // if no sensible moves can be made (ie. towards pacman) then override the logic and force any possible move if (!moved) { flipMove(true); } } // and finally lets check to see if they've caught pacman // need to do this based on pacmans moves as well CheckForMonsterSmackdown(); }); } /// <summary> /// Checks if a monster has come in contact with pacman, and Resets the game as needed /// </summary> private void CheckForMonsterSmackdown() { foreach (Image monster in monsters) { // pull position props double pacLeft = (double)pacman.GetValue(Canvas.LeftProperty); double pacTop = (double)pacman.GetValue(Canvas.TopProperty); double monLeft = (double)monster.GetValue(Canvas.LeftProperty); double monTop = (double)monster.GetValue(Canvas.TopProperty); // look for any intersect Rect pacRect = new Rect(pacLeft, pacTop, pacman.Width, pacman.Height); Rect monRect = new Rect(monLeft, monTop, 18, 18); // check for intersect with boundary pacRect.Intersect(monRect); // returns the intersect rect (if there is one) if (pacRect != Rect.Empty) { ResetGame(); break; } } } /// <summary> /// Handler for all keystrokes in the game /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void UserControl_KeyDown(object sender, KeyEventArgs e) { switch (e.Key) { case Key.Up: moveX = 0; moveY = -5; pacImage = "pacman_up.png"; if (!CheckForCollision()) updatePosition(moveX, moveY); break; case Key.Down: moveX = 0; moveY = 5; pacImage = "pacman_down.png"; if (!CheckForCollision()) updatePosition(moveX, moveY); break; case Key.Left: moveX = -5; moveY = 0; pacImage = "pacman_left.png"; if (!CheckForCollision()) updatePosition(moveX, moveY); break; case Key.Right: moveX = 5; moveY = 0; pacImage = "pacman.png"; if (!CheckForCollision()) updatePosition(moveX, moveY); break; } } /// <summary> /// Checks if the currently propositioned move of Pacman will intersect any of the defined boundaries /// </summary> /// <returns></returns> private bool CheckForCollision() { return CheckForCollisionAny(spriteX + moveX, spriteY + moveY, pacman.Width, pacman.Height); } /// <summary> /// Checks if the object specified by the given co-ordinates is colliding with defined boundaries /// </summary> /// <param name="X"></param> /// <param name="Y"></param> /// <param name="Width"></param> /// <param name="Height"></param> /// <returns></returns> private bool CheckForCollisionAny(double X, double Y, double Width, double Height) { bool tmp = false; // and loop through the boundary rects to look for any intersects for (int i = 0; i < noRects; i++) { // construct a rect representative of the move taking place Rect tmpRect = new Rect(X, Y, Width, Height); // check for intersect with boundary tmpRect.Intersect(rects[i]); // returns the intersect rect (if there is one) if (tmpRect != Rect.Empty) { tmp = true; pacCollision.Text = i.ToString(); } } return tmp; } /// <summary> /// Moves pacman by the given offsets /// Implicitly calls AnimatePacman, EatTicTacs, and checks for Monster Collision /// </summary> /// <param name="x"></param> /// <param name="y"></param> private void updatePosition(int x, int y) { spriteX += x; spriteY += y; // i needed to cast to double, looks like the dependency prop def has changed recently pacman.SetValue(Canvas.LeftProperty, (double)spriteX); pacman.SetValue(Canvas.TopProperty, (double)spriteY); animatePacman(); // and mainly to help me define the boundaries, lets throw the pacman position co-ordinates on the page pacPosition.Text = pacman.GetValue(Canvas.LeftProperty).ToString() + "," + pacman.GetValue(Canvas.TopProperty).ToString(); // have pacman eat any tictacs that he crosses paths with EatTicTacs(); // check for death CheckForMonsterSmackdown(); } /// <summary> /// Alternates the image used for pacman between mouth open and mouth closed to visualise a hungry pacman /// </summary> private void animatePacman() { if (count % 4 == 0) pacman.Source = new BitmapImage(new Uri("pacman2.png", UriKind.Relative)); else pacman.Source = new BitmapImage(new Uri(pacImage, UriKind.Relative)); count++; } /// <summary> /// Defines the boundaries of play, based upon the corridors visible on the game map image /// </summary> private void createOutOfBounds() { noRects = 50; int i = 0; // make it easier rects = new Rect[noRects]; // left outer wall = 95-100 2 blocks, 10-135 top & 260-410 bottom rects[i++] = new Rect(95, 10, 5, 125); rects[i++] = new Rect(95, 260, 5, 150); // top outer wall = 95-505, 10-15 rects[i++] = new Rect(95, 10, 410, 5); // right outer wall, 2 blocks rects[i++] = new Rect(505, 10, 5, 125); rects[i++] = new Rect(505, 260, 5, 150); // bottom outer wall rects[i++] = new Rect(95, 408, 410, 5); // and now all the internal stuff (working from top left to bottom right, across and then down) // the top half rects[i++] = new Rect(125, 40, 50, 27); rects[i++] = new Rect(201, 41, 64, 27); rects[i++] = new Rect(290, 15, 19, 50); // top centre 9 rects[i++] = new Rect(335, 40, 65, 27); rects[i++] = new Rect(426, 41, 50, 27); rects[i++] = new Rect(125, 93, 49, 15); rects[i++] = new Rect(201, 93, 20, 92); rects[i++] = new Rect(217, 133, 46, 13); rects[i++] = new Rect(245, 93, 109, 14); rects[i++] = new Rect(290, 106, 21, 40); rects[i++] = new Rect(335, 133, 46, 13); rects[i++] = new Rect(381, 94, 19, 92); rects[i++] = new Rect(426, 93, 49, 14); // the middle block rects[i++] = new Rect(246, 173, 108, 52); // entrance rects[i++] = new Rect(99, 133, 77, 5); // top top of entrance rects[i++] = new Rect(167, 135, 5, 47); // top inside of entrance rects[i++] = new Rect(93, 180, 82, 5); // top corridor of entrance rects[i++] = new Rect(93, 212, 82, 6); // bottom corridor of entrance rects[i++] = new Rect(169, 215, 6, 50); // bottom inside of entrance rects[i++] = new Rect(93, 260, 79, 5); // bottom bottom of entrance // exit rects[i++] = new Rect(427, 133, 80, 5); // top top of exit rects[i++] = new Rect(426, 135, 5, 50); // top inside of exit rects[i++] = new Rect(427, 181, 79, 5); // top corridor of exit rects[i++] = new Rect(427, 212, 79, 5); // bottom corridor of exit rects[i++] = new Rect(427,212, 5, 50); // bottom inside rects[i++] = new Rect(427, 259, 79, 5); // bottom bottom of exit // the bottom half rects[i++] = new Rect(201, 212, 18, 52); rects[i++] = new Rect(246, 252, 108, 12); rects[i++] = new Rect(291, 264, 18, 40); rects[i++] = new Rect(381, 212, 18, 51); rects[i++] = new Rect(126, 291, 50, 12); rects[i++] = new Rect(156, 291, 17, 52); rects[i++] = new Rect(201, 291, 63, 12); rects[i++] = new Rect(336, 292, 62, 12); rects[i++] = new Rect(426, 291, 48, 12); rects[i++] = new Rect(427, 292, 18, 51); rects[i++] = new Rect(96, 331, 34, 12); rects[i++] = new Rect(201, 331, 18, 40); rects[i++] = new Rect(126, 371, 138, 10); rects[i++] = new Rect(247, 331, 108, 12); rects[i++] = new Rect(291, 343, 19, 39); rects[i++] = new Rect(383, 331, 18, 40); rects[i++] = new Rect(337, 371, 139, 13); rects[i++] = new Rect(473, 331, 30, 12); } /// <summary> /// Adds the munchables for our pacman to consume /// </summary> private void DrawTicTacs() { // define our brushes used to draw the biscuits SolidColorBrush fillBrush = new SolidColorBrush() { Color = Colors.Yellow }; SolidColorBrush strokeBrush = new SolidColorBrush() { Color = Colors.White }; // in lines except where crosses bounds foreach (int line in new int[] { 24, 79, 119, 275, 316, 357, 394 }) { for (int i = 107; i < 488; i = i + 20) { Ellipse tt = new Ellipse() { Height = 5, Width = 5, StrokeThickness = 1, Fill = fillBrush, Stroke = strokeBrush }; tt.SetValue(Canvas.TopProperty, (double)line); tt.SetValue(Canvas.LeftProperty, (double)i); // don't draw if it intersects a boundary if (!CheckForCollisionAny(i, line, 10, 10)) // dont want to draw them too close to boundaries { GameMap.Children.Add(tt); // and keep a reference array (no Lists in silverlight!) to check once its eaten tictacs[GameMap.Children.Count - 1] = tt; } } } // and the downwards lines foreach (int line in new int[] { 113, 186, 232, 279, 325, 369, 415, 489 }) { for (int i = 24; i < 388; i = i + 20) { Ellipse tt = new Ellipse() { Height = 5, Width = 5, StrokeThickness = 1, Fill = fillBrush, Stroke = strokeBrush }; tt.SetValue(Canvas.TopProperty, (double)i); tt.SetValue(Canvas.LeftProperty, (double)line); if (!CheckForCollisionAny(line, i, 10, 10)) // leave a buffer as we dont want them hard-up against walls { GameMap.Children.Add(tt); // and keep a reference array (no Lists in silverlight!) to check once its eaten tictacs[GameMap.Children.Count - 1] = tt; } } } } /// <summary> /// If pacman is currently on top of any tictacs, this will have him 'eat' them and update the score /// </summary> private void EatTicTacs() { foreach (Ellipse lolly in tictacs) { if (lolly == null) continue; // and ignore eaten ones if (!GameMap.Children.Contains(lolly)) continue; // check if pacman is on top of the biscuit if ((((double)lolly.GetValue(Canvas.TopProperty)) > ((double)spriteY)) && ((((double)lolly.GetValue(Canvas.TopProperty)) + 5) < ((double)spriteY + pacman.Height)) && (((double)lolly.GetValue(Canvas.LeftProperty)) > ((double)spriteX)) && ((((double)lolly.GetValue(Canvas.LeftProperty)) + 5) < ((double)spriteX + pacman.Width))) { // eat it! GameMap.Children.Remove(lolly); // and update the score int tmp = int.Parse(score.Text.TrimStart('0')); tmp = tmp + 5; score.Text = tmp.ToString().PadLeft(8, '0'); } } } /// <summary> /// Adds the bad guys /// </summary> private void PlaceMonsters() { const int numMonsters = 4; monsters = new Image[numMonsters]; // make it easier to reposition our bad guys double[] monsterX = new double[numMonsters] { 18, 387, 387, 18 }; double[] monsterY = new double[numMonsters] { 478, 480, 103, 103 }; for (int i = 0; i < numMonsters; i++) { monsters[i] = new Image() { Source = new BitmapImage(new Uri("monster_hunter.png", UriKind.Relative)), Width = 18 }; monsters[i].SetValue(Canvas.TopProperty, monsterX[i]); monsters[i].SetValue(Canvas.LeftProperty, monsterY[i]); GameMap.Children.Add(monsters[i]); } } } }
 

Hello world! July 27, 2008

Filed under: Uncategorized — dotnetdave @ 11:26 pm

‘It has drowned out the most heavenly ecstacies of religious fervor, of chivalrous enthusiasm, of philistine sentimentalism, in the icy water of egotistical calculation. It has resolved personal worth into exchange value, and in place of the numberless indefeasible chartered freedoms, has set up that single, unconscionable freedom — Free Trade. In one word, for exploitation, veiled by religious and political illusions, it has substituted naked, shameless, direct, brutal exploitation.’ – Marx & Engels speaking of Capitalism, 1848.

“A democracy cannot exist as a permanent form of government. It can only exist until the voters discover that they can vote themselves money from the public treasure. From that moment on, the majority always votes for the candidates promising the most money from the public treasury, with the result that democracy always collapses over loose fiscal policy followed by a dictatorship. The average of the world’s greatest civilizations has been two hundred years. These nations have progressed through the following sequence: from bondage to spiritual faith, from spiritual faith to great courage, from courage to liberty, from liberty to abundance, from abundance to selfishness, from selfishness to complacency, from complacency to apathy, from apathy to dependency, from dependency back to bondage.” — Alexander Tyler, 1778

But the principal, underlying cause was that, as early as the beginning of the twentieth century, human equality had become technically possible. It was still true that people were not equal in their native talents and that functions had to be specialized in ways that favored some individuals against others; but there was no longer any real need for class distinctions or for large differences of wealth. In earlier ages, class distinctions had been not only inevitable but desirable. Inequality was the price of civilization. With the development of machine production, however, the case was altered. Even if it were still necessary for human beings to do different kinds of work, it was no longer necessary for them to live at different social or economic levels. Therefore, from the point of view of the new groups that were on the point of seizing power, human equality was no longer an ideal to be striven after, but a danger to be averted. And so the idea of earthly paradise — which had haunted the human imagination for thousands of years — came into discredit at exactly the moment when it became realizable. Every new political theory, by whatever name it called itself, led back to hierarchy and regimentation. — Emanueal Goldstein