RPA Game History Available

You spoke, we listened.   Rock, Paper, Azure is going well, but one thing we’ve heard from many people is that they would like a history of their games.   The main reason for this is the “attack and retreat” approach  some people have taken – that is, they submit a bot with their main game playing skill, quickly observe/download the results, then resubmit a weak bot.   Because the MyBot page shows only the current results, odds are the other players won’t see the interim results.   We originally thought the current results would be all people would want to see, as game history (aside from taking a lot of space) could potentially be a lot of data to go through.  But, we’ve been proven wrong, so we decided to keep the history for players to go through.  We wanted to incorporate this in a non-breaking way, so it’s optional data to look at. On the MyBot page, you’ll still see the current games: Under that, you’ll see game history that shows all interim games: The filter allows you to select a single player to look at.  The number of games you see depends on the number of times your or your opponent’s bot was uploaded.  We haven’t quite decided how long we’ll keep history, but at the very least, it’s there to use!  Thanks for the feedback!

RPA Winners, Losers, and Eligibility

Over the past few weeks, we (the dev team of Rock, Paper, Azure) have gotten to be best friends with our internal legal department.  It’s been an educational experience, and this is where things stand with Rock, Paper, Azure. When one player wins a round, he or she is ineligible to win a future round.  If you don’t win, you are eligible to keep playing in subsequent weeks to try to win.  We’ve received a number of very good questions and concerns, so, I’ll try to summarize them here. 1. What if I win second or third place, and I want to keep playing to try to in the first place prize?  Can I do that? Players do have the option to refuse their prize.  If you come in second or third, you may reject it, forfeit your place, and continue playing.   Please note though that you assume the risk for doing this and the decision is final.  As weeks go by, I would expect the competition to get more difficult, so you may do worse and not win any prize at all, and also give up your place should there be a change in eligibility (see next paragraph). In addition to a player purposefully choosing to remove him or herself from claiming the prize, a player may also be found ineligible.   In either case, the prize goes to the next person in line.  So, hypothetically speaking, if the first place player was found ineligible, the second/third/fourth players become first/second/third.  Had player 2 already decided to refuse his place … yep, that’s right – the top three are now third/fourth/fifth from the leaderboard. It’s a bit confusing, but as you can imagine there are legalities involved here. 2. Why are previous winners still playing?  I thought they were ineligible!? Past winners are ineligible to win a prize again.  We’ll remove ineligible players as we can – as you might imagine, there’s a little delay because we’re sorting out the winners from week 1.   But rest assured, before we run the final round on Fridays, we vet all players and then rerun the entire round.  Remember that during the week, the “continuous integration” of your bot is primarily for fun and strategy – it’s only the final round that counts! 3. So, who won week 1? We had no idea it would be this volatile, but it turns out players 2 and 3 (Serplat and marsh) have been removed – one for his choice to keep playing, the other because of, unfortunately, eligibility.  (I wish that wasn’t the case, but laws are laws.)  That means that players 4 and 5 slide up to 2 and 3 – s7orm and AttnSystem now become 2nd and 3rd place, unless they too decide to keep playing and give up their place.  So just remember – you’re never out of it!

Can’t Lock() in RPA

For security and performance reasons, we often review what bots are doing and review any rejected bots to see how things are going.   When a bot is rejected, it is typically easy to spot the exact reason when looking at the code, but sometimes it’s not so apparent.  By far, the biggest case that I see is using the Lock keyword.  For example: 1: lock (someobject) 2: { 3: //synchronized code 4: } On the surface, it’s not apparent this would be a rules violation, and in the vast majority of cases, it is fine.  But under the covers, it calls Monitor.Enter() in the System.Threading namespace (known as compiler syntactical sugar), and we don’t allow anything in that namespace to be called.  The question we have internally is:  should we review the rule and make exceptions to specific calls? In this case, there’s no reason to use such code in a bot.  The only time you’d need to is if your bot has a static method/object reference, but that’s a bad idea in a bot because it will lead to unpredictable results.  Your bot should have only instance members.    So what’s the correct way?   Locking is a typical pattern (and a good habit) in object creation where there’s a risk of concurrency causing an issue.  Even if there is no risk, many developers are careful particularly with singletons to be sure of thread safety by using locks.  In the case of developing a bot, the best way to do this is in the bot’s constructor without any lock/monitor.   Questions or feedback?  Let us know!

Testing a RockPaperAzure Bot in a Winform

In Friday’s webcast, I showed a quick ‘n dirty utility for testing a RockPaperAzure bot outside of the game server and even the Bot Lab. When we ran some of these tournaments in person, it was very difficult to get a good feel for your bot before deploying it, and that’s why we introduced the idea of a Bot Lab. But even with the Bot Lab, if you’re working out a complex algorithm, it can be handy to have a simple test hardness. By pulling in the DLLs from the Bot Lab, it’s pretty easy to put together a little utility. To locate the DLLs, crack open the BotLabSite project and locate the ref directory, and grab the Compete.Bot, RockPaperScissorsPro, and RockPaperScissorsPro.RPA libraries: One thing to note: the RockPaperScissorsPro.RPA assembly, in the MyBot project, has an XML file that you can also include if you’d like intellisense – for example, we call out that GetRandomMove returns Rock/Paper/Scissors, not dynamite/water balloon: In our Windows Form app, we’ll create a few textboxes for the moves of each player, and the player’s log file: In our project, we’ll build a couple of bots – these can be your actual bots (because they use the same interface) or completely different – it’s up to you. MyBot will be the main bot we want to test, and Opponent will be, obviously, the opponent. In our form app, we’ll declare some of the player/game objects we need to test: 1: IRockPaperScissorsBot myBot; 2: IRockPaperScissorsBot opponentBot; 3: Player rpsPlayer1; 4: Player rpsPlayer2; 5: PlayerLog player1Log; 6: PlayerLog player2Log; 7: 8: private void Form1_Load(object sender, EventArgs e) 9: { 10: myBot = new MyBot(); 11: opponentBot = new OpponentBot(); 12: 13: rpsPlayer1 = new Player("you", myBot); 14: rpsPlayer2 = new Player("opponent", opponentBot); 15: 16: player1Log = new PlayerLog(rpsPlayer1, rpsPlayer1, rpsPlayer2); 17: player2Log = new PlayerLog(rpsPlayer2, rpsPlayer1, rpsPlayer2); 18: } When the form loads, we initialize our players. Each player has a bunch of methods and properties, but most importantly, also has the player’s bot. This can be a little confusing because bot is a property of the player. We’ll also create some player logs – normally the engine does this for us, but we’re running outside the engine so we need to do that ourselves. There are 2 ways we can have out bots move: Option 1: Simulate the game environment. This is a good option if you want to test timings against the game engine. I’m including this here really just for information sake – most people who want to test timings will be far better off testing in the bot lab. 1: int i = 0; 2: 3: private void button1_Click(object sender, EventArgs e) 4: { 5: i++; 6: 7: PlayerMove ourMove = rpsPlayer1.GetMoveToMake( 8: rpsPlayer2, GameRules.Default, player1Log); 9: 10: PlayerMove opponentMove = rpsPlayer2.GetMoveToMake( 11: rpsPlayer2, GameRules.Default, player1Log); By calling GetMoveToMake on the player, we’ll get back the Player’s move and this will be done just like it is in the real game – timeouts and all. Option 2: Take your time! This is really the best option – we’ll call into the bots MakeMove directly and this avoids threading/timeouts – the big advantage here is that it’s much easier to step through and debug. There is one tiny gotcha. The player log files are managed by the player, and in this case, we’re sidestepping that. In order to have a working log, we’ll need to inject that log via reflection. Reflection code won’t work on the real game server, but it comes in handy in our test app: 1: int i = 0; 2: 3: private void button1_Click(object sender, EventArgs e) 4: { 5: i++; 6: 7: Type t = typeof(Player); 8: FieldInfo fieldInfo = t.GetField( 9: "<Log>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance); 10: fieldInfo.SetValue(rpsPlayer1, player1Log); 11: fieldInfo.SetValue(rpsPlayer2, player2Log); 12: 13: Move ourMove = myBot.MakeMove(rpsPlayer1, rpsPlayer2, GameRules.Default); 14: Move opponentMove = opponentBot.MakeMove(rpsPlayer2, rpsPlayer1, GameRules.Default); What lines 7 – 11 do is use reflection to assign the player log. We need to use reflection because these are internal objects that we can’t typically access. Once we have the move, we call SetLastMove which gives bots the visibility into their last moves. 1: ... 2: rpsPlayer1.SetLastMove(new PlayerMove(rpsPlayer1, ourMove)); 3: rpsPlayer2.SetLastMove(new PlayerMove(rpsPlayer2, opponentMove)); 4: ... 5: 6: protected override void OnClosing(CancelEventArgs e) 7: { 8: rpsPlayer1.MatchOver(); 9: rpsPlayer2.MatchOver(); 10: 11: base.OnClosing(e); 12: } One other comment: in the form closing (or other location) you need to remember to close down the bots by calling MatchOver. This will make sure they shut down properly and the threads exit. With everything set up, we can run the app and see the output: So let’s return to the original question: Why do this? Suppose we want a simple way to track number and percentage of the moves thrown. We could keep simple counters (in fact, that would be the fastest way to do that), but consider this code block: 1: List<Move> opponentHistory = new List<Move>(); 2: 3: public Move MakeMove(IPlayer you, IPlayer opponent, GameRules rules) 4: { 5: gamenum++; 6: 7: if (gamenum == 1) { 8: you.Log.AppendLine("starting up."); 9: } else { 10: opponentHistory.Add(opponent.LastMove); 11: } 12: 13: if (opponentHistory.Count > 0) 14: { 15: var moveHistory = (from m in opponentHistory 16: group m by m.ToString() into g 17: select new { MoveName = g.Key, MoveCount = g.Count(), 18: Percentage = (Convert.ToDouble(g.Count()) / 19: Convert.ToDouble(opponentHistory.Count)*100) 20: } 21: ).ToList(); 22: } What we’re doing is keeping a log of the opponent’s moves into the opponentHistory list. We’ll then aggregate that into a moveHistory list, which is derived from the aggregated data. By looking at the opponentHistory, you can ask very specific questions of the data, such as move totals and percentages: 1: Rock - 3 - 27.27% 2: Paper - 6 - 54.55% 3: Scissors - 2 - 18.18% You could look at the past few moves, or the entire game – or adapt it to your project. I’ve uploaded the project, with a link below. You’ll need to pull the assemblies above into the project – I’ve left them out for licensing clarity. If you use or improve on this solution, let me know! It’s obviously not terribly advanced but should be enough to get you started. Download the solution

Rock, Paper, Azure Tournament Information

We had a couple of great questions on RPA tournaments over the first week – I thought I’d clarify a few things here. International Players As many of you know, the main tournament (with prizes) is limited the US residents. This is for legal reasons as I explained in this post. But, we wanted to get the framework in place to expand – and I’m thrilled to say that we’re there (how’s that for fast implementation?). Currently, when deploying bots, you’ll see other rounds that are open – for example, on the MyBot page: Currently, only US residents can enter the Tournament Round but everyone (including MSFT employees) can enter the Open Rounds. As other groups in varies locations put together tournaments, we are happy to support that effort. Resubmitting – not fair? One question received was from a player who thought he noticed several players resubmitting their bots rapidly without modification in order to force their bots to replay, and hopefully do better due to random elements they’ve built in their bot. Obviously, with random elements in your bot, you can win some games and lose others that will cause your bot to fluctuate position. To be clear, it doesn’t matter and this completely okay/fair to do. Here’s why: the games are played during the week in near real-time. On Friday at 2pm (for our main tournaments), we close submissions. At this time, we take a look at the players in the game for eligibility, and we start the final round at about 2:15pm. During this round at 2:15pm, all games are replayed – so the leaderboard before this is completely irrelevant – only the leaderboard created as a result of the final round is what matters. So why have the continuous integration during the week? Is it still important? You bet! It’s a chance to play and fine tune your strategy. To get ideas from other players and come up with your own. Your bot might play the same during the final, or it might not. Sandbagging Since the final leaderboard is generated as a result of the 2:15pm match, we’ve seen some interesting behavior – and this makes the game exciting! For example, suppose you’ve got a cool strategy – perhaps you wait to implement it because you don’t want your opponents to develop a counter play. This is all completely legit and part of the game. In some ways, it’s a double-edged sword – you need to test your ideas to see how good they are, but like in a poker game, you want to keep some cards close to your vest. One question we got was whether you could use time-based code in your bot. Absolutely! What I’d recommend, if coding specific for the Friday tournament, you do something like this: 1: if (DateTime.UtcNow > new DateTime(2011, 4, 15, 18, 0, 0)) 2: { 3: //begin super playing mode! 4: } In this case, we’re setting the bot to play differently after the 2pm close time on 4/15. Remember, all time is UTC on the game server (DateTime.UtcNow and DateTime.Now are the same in Windows Azure, but I use UTC for clarity). Because UTC is currently +4 hours from EDT, we add 4 hours to 2pm (1400 hours) so the cutoff time is 1800 hours UTC. I don’t recommend you set this to 2:15pm because the game may start slightly before 2:15pm. Additionally, I don’t recommend an end time because while the games should play at 2:15pm, it’s possible due to technical issues it might need to be delayed slightly. Ineligible Players We know some plays sneak in occasionally – past winners, employees … even we submit a test bot to demo the project or when trying to resolve an issue. We are usually pretty good at removing those bots. In the main tournament rounds, we’ll do our best to police the registrants to make sure they qualify, but we’ll apply a bit more scrutiny before the round at 2:15pm Friday (hence why we take a 15 minute break to give things a once-over).

We’re Ready to Rock…

… but there are some changes to Rock, Paper, Azure we need to discuss based on our test week.   First things first:  I highly recommend you download the latest Bot Lab and MyBot project here.  For the most part, old bots bots will continue to work, but since this was a test round anyway, I highly recommend you upgrade – there are a few edge cases that might break bots, or cases where a bot doesn’t do what you think it would.  Let’s discuss all of these changes we’ve made.  I’ll save the best for last. CanBeat and LosesTo – Don’t Use We re-scoped the CanBeat and LosesTo methods from the Move class to internal (from public).   Most people weren’t using them, and they weren’t really intended for outside consumption and we received a couple of questions on behavior.  The reason for the change is this – and some of this is historical:  the way the engine works is that it goes through a series of steps to determine a winner.   It will first look if a move can beat another move: 1: internal bool CanBeat(Move move) 2: { 3: if (!ValidMoves.Contains(GetType().Name)) 4: return false; 5:  6: if (!ValidMoves.Contains(move.GetType().Name)) 7: return true; 8:  9: return CanBeatLegalMove(move); 10: } So the RockMove implementation returns true if the move is scissors which is implemented in the RockMove class.  The original game had moves in different assemblies for multiple rounds, so the win determination path had to waterfall through, and examine rules to the game – such as whether or not you have any dynamite remaining.   The short answer here is there are cases where this will return unexpected results, so it’s best to just not expose them.  Performance wise, it’s better to have your own implementation, anyway.  If your bot is currently using this, sorry to say this is a breaking change. Game Summary At the top of the log file, you’ll now see a game summary that is helpful for info-at-a-glance.  It looks like this: Game Summary bhitney JohnSmith Points 953 (L) 1000 (W) Dynamite Used 45 100 Time Deciding (s) 0.04885 0.06507 Time deciding shows how much time your bot has used for the entire round.   This is interesting to see how your bot compares to others, but, it’s also useful for timeouts which is below.   Timeout   The server has some strict timeouts – we’re always tweaking the exact logic and we’re hesitant to give too much information because, in a multithreaded environment, the time slice each bot has isn’t exclusive.   But, your bot has a maximum of 1 second (subject to change) per match.  Typically that is no problem, as you can see in the game summary.  Once your bot crosses the 1 second timeout, your bot will stop making moves (essentially forfeiting the rest of the moves).   Server Messaging   In the log file, you’ll now see more clear messaging if something happened – for example, if a player threw dynamite but was out of dynamite, the log will indicate this next to the score.    ExceptionMove   We standardized the error moves to “ExceptionMove.”  If a player times-out or throws an exception, their last move will be an ExceptionMove.  This is visible in the log, and detectable like so:   if (opponent.LastMove is ExceptionMove){ //player threw an exception or timeout} This is a good time to mention that the opponent’s LastMove is null on the first move.  Also, if a player throws “illegal dynamite” (that is, throws dynamite when he/she is out) their last move will be Dynamite.  It’s up to you to figure out they were out of dynamite!   Fresh Upgrade   One of Azure’s strengths is flexibility in deployment – when deploying, you have an option for an in-place upgrade, or can deploy to staging and do a VIP (virtual IP) swap that allows you to seamlessly move a new deployment into production.    Because of the significant changes to our Bot Lab and new features, we recommend deleting the old deployment first, and then deploying the new package and not doing an in-place upgrade.   Player Log   And now the best part!  There’s a private player log you can write to.  This information will be appended to the bottom of the game log – but it’s only visible to you.  We’re still testing this feature, but it should be helpful in isolating issues with your bot.  To write to your log, you can do something like:   1: if (!you.HasDynamite) 2: { 3: you.Log.AppendLine("I'm out of dynamite!"); 4:  5: //opponent's log is always null! 6: if (opponent.Log == null) { } 7: }   Note:  Your opponent’s log is always null, so don’t try it!  Also, logs are limited to a total of 300k – this should be more than enough, but once the log passes this mark, the engine won’t write to the log (but no exception is raised).

RPA: Why is my Bot Rejected?

We’ve had a few people ask about getting the following message when submitting bots: Bot contains invalid calls. Please review the official rules for more information. In short, this message means the bot didn’t pass the static analysis tests.  On the About the Challenge page, you’ll see this: While this list is subject to change, we do not allow any classes/methods from System.IO, System.Net, System.Reflection, System.Threading, System.Diagnostics, or System.GC. That list is subject to change, and there are a few other red flags like P/Invoke or unsafe code.  Sometimes, these are caused by what would otherwise seem innocuous, such as calling GetType on an object – but that’s a reflection call, so would trigger the static analysis. Another occurrence:  analysis will look for the presence of such code, not if it’s reachable.  In one case, the code was never called but still present in the bot. So, if you’re getting the above message, look for those items and if you have any questions, leave a comment here or send us an note through the www.rockpaperazure.com site!

Rock, Paper, Azure Deep Dive: Part 1

If you’re not sure what Rock, Paper, Azure (RPA) is all about, check out the website or look over some of my recent posts.   In this series of posts, I want to go into some of the technical nuts and bolts regarding the project. First, you can download Aaron’s original project on github (here and here).   The first project is the Compete framework, which is an extensible framework design to host games like Rock, Paper, Scissors Pro! (the second project).    The idea, of course, is that other games can be created to work within the framework. Aaron and the other contributors to the project (I remember Aaron telling me some others had helped with various pieces, but I don’t recall who did what) did a great job in assembling the solution.   When moving it to Windows Azure, we had a number of issues – the bottom line is, our core requirements were a bit different than what was in the original solution.   When I describe some of these changes in this and other posts, don’t mistake it for me being critical of Aaron’s project.   Obviously, having used it at code camps and the basis for RPA shows I have a high regard for the concept, and the implementation, in many parts, were quite impressive. So, if you download those two projects on github, the first challenge is getting it up and running.  You’ll see in a few locations there are references to a local path – by default, I believe this is “c:\compete”.  This is the local scratch folder for bots, games, the db4o database, and the logfile.  Getting this to work in Windows Azure was actually pretty straightforward.   A Windows Azure project has several storage mechanisms.  When it comes to NTFS disk I/O, you have two options in Azure:  Local Storage, or Azure Drives.   Azure Drives are VHD files stored in Azure Blob Storage and can be mounted by a VM.   For our purposes, this was a little overkill because we only needed the disk space as a scratch medium: the players and results were being stored in SQL Azure.  The first thing we needed to do to get local storage configured is add a local storage resource: In this case, we just created a local storage area called compete, 4GB in size, set to clean itself if the role recycles. The next step was to remove any path references.  For example, in Compete.Site.Models, you’ll see directory references like this: Because there’s so much disk I/O going on, we created an AzureHelper project to ultimately help with the abstraction, and have a simple GetLocalScratchFolder method that resolves the right place to put files: Now, we inject that call wherever a directory is needed (about a half dozen or so places, if memory serves).   The next major change was deciding: to Spark, or not to Spark?  If you look at the project references (and in the views themselves, of course), you’ll see the Spark view engine is used: I’m no expert on Spark but having worked with it some, I grew to like its simplicity: The problem is, getting Spark to work in .NET 4.0 with MVC 2 was, at the time, difficult.  That doesn’t appear to be the case today as Spark has been revived a bit on their web page, but we started this a few weeks earlier (before this existed) and while we recompiled the engine and got it working, we ultimately decided to stick with what we knew best. The end result is the Bot Lab project.   While we’re using RPA with the idea that it can help others learn about Azure while having fun, it’s also a great example of why to use Windows Azure.  The Bot Lab project is around 1 MB in size, and the Bot Lab itself can be up and running in no time (open solution, hit F5). Imagine if you wanted to host an RPS style competition at a code camp.  If you have a deployment package, you could take the package and host it locally if you wanted, or upload it to Windows Azure – hosting an extra small instance for 6 hours at a code camp would cost $0.30.   Best of all, there’s no configuring that needs to be done (except for what the application dictates, like a username or password).  This, if you ask me, is one of the greatest strengths behind a platform as a service.

RockPaperAzure Coding Challenge

I’m pleased to announce that we’re finally launching our Rock, Paper, Azure Challenge! For the past couple of months, I’ve been working with Jim O’Neil and Peter Laudati on a new Azure event/game called Rock, Paper, Azure.  The concept is this:  you (hopefully) code a “bot” that plays rock, paper, scissors against the other players in the game.  Simple, right? Here’s where it gets interesting.  Rock, paper, scissors by itself isn’t all that interesting (after all, you can’t really beat random in a computer game – assuming you can figure out a good random generator!), so there are two additional moves in the game.  The first is dynamite, which beats rock, paper, and scissors.   Sounds very powerful – and it is – however, you only have a few per match so you need to decide when to use them.   The other move is a water balloon.  The water balloon beats dynamite, but it loses to everything else.   You have unlimited water balloons. Now, with the additional rules, it becomes a challenge to craft an effective strategy.   We do what we call “continuous integration” on the leaderboard – as soon as your bot enters, it’s an all out slugfest and you see where you are in near real time.   In fact, just a few minutes ago, a few of us playing a test round were constantly tweaking our bots to defeat each other – it was a lot of fun trying to outthink each other. Starting next week, we’ve got some great prizes on the line – including Xbox systems, Kinect, and gift cards – so be sure to check it out!   The project homepage is here: http://www.rockpaperazure.com See you in the game!

Creating a Quick Progress Visual like UpdateProgress

On Thursday, we’re going to go live with our RockPaperAzure coding challenge – and it’s time to brain dump on some of the lessons learned while building out the solution – some small (like this one), some large. When developing the RPA website, I chose to use ASP.NET Webforms and ASP.NET Ajax.  The reason for this is simple … I’m a big fan ASP.NET MVC, but for this, given the very short time frame, it was the fastest route to completion.   (For those familiar with Aaron’s base project on Github, I thought about doing it all client side via JQuery but wasn’t impressed with the perf, so decided to go server side.) ASP.NET Ajax has a nifty UpdateProgress control that is useful during async postbacks to show activity, and while it has a display delay property, it doesn’t have a minimum display time property.  This is problematic because if you want a small spinning icon/wait cursor, displaying it too briefly is just an annoyance.  (Of course, brief is good because it means your page is snappy.)  One of the solutions to this (as I read on StackOverflow) was to simply put a Thread.Sleep in the server postback method, causing the round-trip to take longer and thus display your animation longer.  While this will work, it will crush scalability.  Depending on how many concurrent users you have, a Thread.Sleep on an ASP.NET thread should be avoided at all costs, and this wasn’t something I was willing to do.  There are a few commercial controls that will do this, and indeed, using the Ajax toolkit, you can develop a control that will accomplish the same.  But I wanted something that I could develop in 5 minutes – basically in less time than it is taking me to write this blog post.  I already have the spinning icon, so I wrapped it in a div or span (whatever is appropriate): 1: <span id="ProgressTemplate" style="visibility: hidden;"> 2: <img src="~/images/rpadie.gif" clientidmode="Static" runat="server" 3: id="imganim" height="18" width="18" /> 4: </span> Then, we just plug into the request model to show/hide the control.  It’s a little hack-ish – for example, I reset the visibility to visible on EndRequest because the span, with a default of hidden, will disappear on its own.  This can be fixed by making it a server control and enabling viewstate.  Still, not too bad for 5 minutes: 1: <script language="javascript" type="text/javascript"> 2: <!-- 3: var prm = Sys.WebForms.PageRequestManager.getInstance(); 4: 5: prm.add_initializeRequest(InitializeRequest); 6: prm.add_endRequest(EndRequest); 7: var postBackElement; 8: function InitializeRequest(sender, args) { 9: postBackElement = args.get_postBackElement(); 10: $get('ProgressTemplate').style.visibility = 'visible'; 11: } 12: function EndRequest(sender, args) { 13: $get('ProgressTemplate').style.visibility = 'visible'; 14: setTimeout('hideProgress()', 1000); 15: } 16:  17: function hideProgress() { 18: $get('ProgressTemplate').style.visibility = 'hidden'; 19: } 20: // --> 21: </script> When the Ajax request comes back, it will keep the span visible for 1 second.  Ideally, a nice modification would use a elapsed time mechanism, so if the request actually took 1 second for some reason, it would hide the span without delay.  This isn’t took hard to implement, but broke the 5 minute goal.

My Apps

Dark Skies Astrophotography Journal Vol 1 Explore The Moon
Mars Explorer Moons of Jupiter Messier Object Explorer
Brew Finder Earthquake Explorer Venus Explorer  

My Worldmap

Month List