<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-4004158710435644696</id><updated>2011-11-28T07:59:05.059+07:00</updated><category term='design tool'/><category term='asp.net'/><category term='performance'/><category term='dotnet'/><title type='text'>ES Websoft</title><subtitle type='html'>Enterprise Solution Websoft</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>13</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4004158710435644696.post-3184671451063920096</id><published>2009-08-03T12:55:00.001+07:00</published><updated>2009-08-03T12:57:28.549+07:00</updated><title type='text'>Speed Up Your Site! 8 ASP.NET Performance Tips 6</title><content type='html'>&lt;p&gt;&lt;strong&gt;Lack of Indexes&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;The ability to index data is one of the key benefits of storing information in a database. Imagine for a moment that you need to look up a name in a telephone book. You take advantage of the fact that the last names are sorted in alphabetic order -- it would take forever to find a name if the entries were listed in random order. Databases take advantage of the way your information is sorted for the same reason. The default sort order in a table is called a clustered index.&lt;/p&gt;  &lt;p&gt;Of course, you may want to search for your information in several different ways. To continue with the phone book example, you may want to look up businesses by zip code, business type, or name. The common approach to implementing this capability in a database is to order the data based on the most common search order, then place additional indexes to facilitate other search criteria. The data is sorted by the clustered index, but the database stores additional information to help it look up rows using other criteria. This additional lookup information is called a non-clustered index.&lt;/p&gt;  &lt;p&gt;One of the most common reasons for slow queries is that the database is performing a table scan, which occurs when the database lacks an appropriate index to use to look up data. Asking your database to perform a table scan is equivalent to asking someone to look up a person in a phone book that lists entries in random order. To summarize, proper indexes are a necessity for database performance.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Incorrect Indexes&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;If indexes are good, more indexes are great, right?&lt;/p&gt;  &lt;p&gt;Actually, no. There's a cost incurred when you add indexes to a database. Each time a row is added or updated, all the indexes need to be updated, and too many indexes can slow your database down. It's important to select a limited number of indexes that'll give you quick lookups without slowing down your updates. We'll be talking about some tools to help you with this task later in this chapter.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Poorly Written Queries&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;It's easy to land yourself in trouble if you don't really understand SQL. SQL is not just another programming language -- it's a declarative, set-based query language. A lot of your standard programming tricks don't apply here.&lt;/p&gt;  &lt;p&gt;We'll talk about troubleshooting query plans and poorly written queries in the section called "How can I troubleshoot a slow query?" later in this chapter.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Deadlocks&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Databases use locks to prevent your data from being messed up by conflicting updates.&lt;/p&gt;  &lt;p&gt;For example, we can't apply a 10% discount to all of our product prices and delete all products that are priced over $100 at the same time -- we need to process one product at a time. Locks allow us to do this. The SQL Server engine is reasonably clever about how it uses database locks -- it'll often lock portions of a table (called pages) and, sometimes, individual rows.&lt;/p&gt;  &lt;p&gt;Yet there's always the potential for two transactions to arrive at a state where both are waiting for the freeing of a lock that's simultaneously held by the other transaction. This situation is called a deadlock.&lt;/p&gt;  &lt;p&gt;For example, consider two queries that use the Products and Orders tables. The first query, which we'll call &lt;code&gt;ProductsThenOrders&lt;/code&gt;, uses the Products table first; the second query, &lt;code&gt;OrdersThenProducts&lt;/code&gt;, uses the Orders table first.&lt;/p&gt;  &lt;p&gt;&lt;code&gt;ProductsThenOrders&lt;/code&gt; locks the Products table and begins to make updates. Meanwhile, &lt;code&gt;OrdersThenProducts&lt;/code&gt; locks the Orders table and performs its updates. No problems so far.&lt;/p&gt;  &lt;p&gt;Now &lt;code&gt;ProductsThenOrders&lt;/code&gt; is ready to update the Orders table, but it can't -- the other query has it locked. Likewise, &lt;code&gt;OrdersThenProducts&lt;/code&gt; wants to update the Products table, but is also blocked for the same reason. We're deadlocked!&lt;/p&gt;  &lt;p&gt;When SQL Server eventually detects a deadlock, it will pick one query as the "deadlock victim" and kill it, while the survivors are released. The result of this conflict resolution process is that one of the queries will return with an error that it was unable to complete -- not the most efficient use of resources.&lt;/p&gt;  &lt;p&gt;Deadlocks don't happen too frequently -- unless your application executes a lot of transactions. It's important to be aware of them and to fix deadlock conditions quickly. Deadlocks can be avoided by:&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;minimizing transaction length&lt;/li&gt;&lt;li&gt;accessing tables in the same order in competing queries&lt;/li&gt;&lt;/ul&gt;   &lt;p&gt;In the above example, accessing the Products table first in both queries would have prevented the deadlock.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;The NOLOCK Query Optimizer Hint&lt;/em&gt;&lt;br /&gt;&lt;em&gt;Even if you're not encountering deadlocks, locks have a definite performance impact. Locks restrict access to your data in such a way that only one query can use it at any time -- an approach that's safe but slow.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;In some cases, you mightn't need to lock your rows. You might query historical data that isn't subject to change, or it mightn't be crucial that the data returned in the query is perfectly up to date -- comments on a weblog might fall into this category.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;In these cases, you can use the &lt;code&gt;NOLOCK&lt;/code&gt; hint to tell SQL Server you want to read directly from the table without honoring any locks. Note that this only makes sense for &lt;code&gt;SELECT&lt;/code&gt; statements -- any data modification will always require a lock. Best practices avoid using table hints -- parameters that override the default behavior of a query -- when possible. However, this one is relatively innocuous as long as you understand that you may be viewing uncommitted changes. Just don't use it when displaying critical information, such as financial data.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Here's how you'd use it:&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;code&gt;SELECT COUNT(1) FROM Orders WITH (NOLOCK)&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Since this statement places no locks on the data that it's reading, other queries won't be forced to wait for the query to complete before they can use the &lt;code&gt;Orders&lt;/code&gt; table.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;You can read more about deadlocks in the MSDN article, &lt;a class="sublink" href="http://msdn2.microsoft.com/en-us/library/ms188246.aspx"&gt;"Analyzing Deadlocks with SQL Server Profiler."&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Hardware Issues&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;As with any software application, SQL Server performs at its optimum when it's running on sufficiently powerful hardware.&lt;/p&gt;  &lt;p&gt;If upgrading your server is an option, the first thing you should look at is memory, as SQL Server makes heavy use of available memory to cache frequently used data. And the cost of new memory is relatively cheap -- often cheaper than the time required to tune an underpowered database server. Adding memory can compensate for slow CPU or drive access, since caching can significantly reduce the work that SQL Server needs to complete.&lt;/p&gt;  &lt;p&gt;After you've exhausted your memory upgrade options, the next most common hardware issue is a disk read/write bottleneck. Database hardware configuration is a large topic and falls well beyond the scope of an ASP.NET book, but a great first step is to put your log files on a drive that's as fast possible, and is separate from the operating system and database files.&lt;/p&gt;  &lt;p&gt;Using a production database server for other tasks -- especially IIS -- is a bad idea. It's often necessary in a development environment, but it will have a performance impact in production.&lt;/p&gt;  &lt;h5&gt;How can I troubleshoot a slow query?&lt;/h5&gt;    &lt;p&gt;Optimizing database performance is a complex topic that's the subject of numerous very thick books, so I'm not going to pretend that we can make you an expert in query optimization in a few short pages. Instead, I'll focus on some of my favorite "developer to developer" tips to point you in the right direction.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Solution&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Before you begin to look for a solution, it's important to verify the problem at hand. You can then begin the process of elimination.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Verifying the Problem&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;First, verify that the SQL you think is being executed is actually being executed. The best way to confirm this is to duplicate the problem: run the query in SQL Server Management Studio (SSMS).&lt;/p&gt;  &lt;p&gt;If you have any doubt about which SQL commands are being executed, run the SQL Profiler for confirmation (see the section called "How do I speed up my database queries?" earlier in this chapter for details on using the SQL Profiler). This tool is especially helpful when used with applications that make use of declarative data binding, or with frameworks that handle data access for you.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Clearing the SQL Cache when Testing in SSMS&lt;/em&gt;&lt;br /&gt;&lt;em&gt;SQL Server uses an intelligent caching system to enhance performance. If you run frequent queries against a certain table, SQL Server will recognize that fact and store the source (and result data) of those queries in its internal cache. By doing so, future matching queries won't need to look up this data until the next time it changes.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;This functionality, while useful, can be confusing if you conduct your tests by running your queries from SSMS -- some of your query information may be cached, so your queries will run faster the second time you execute them.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;To ensure that you make valid comparisons that don't return cached information, clear your cache each time you run the query. The following script does just this -- first it drops caches, then it calls a &lt;code&gt;CHECKPOINT&lt;/code&gt; to flush pending changes from memory to disk, and finally it clears any data that has been stored in memory:&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;code&gt;DBCC FREESESSIONCACHE&lt;br /&gt;DBCC FREEPROCCACHE&lt;br /&gt;DBCC FREESYSTEMCACHE('ALL')&lt;br /&gt;CHECKPOINT&lt;br /&gt;DBCC DROPCLEANBUFFERS&lt;br /&gt;GO -- Your query goes here&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;Once you're able to duplicate the problem in SSMS, you can dig into the query itself.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Checking for Large Result Sets&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;If your query returns more rows than you expected it to, there are two main problems to look at -- cross joins and incomplete &lt;code&gt;WHERE&lt;/code&gt; clauses.&lt;/p&gt;  &lt;p&gt;A cross join occurs when you fail to specify a join correctly. Here's an example:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;SELECT * FROM Customers, Orders, [Order Details], Products&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;In the above query, we haven't specified how the tables should be joined, so the SQL interpreter will attempt to return every possible combination. That means that our result set will include every Order combined with every Customer (not just the Orders that each Customer made). So this query returns about 12.5 billion rows (91 Customers × 830 Orders × 2155 Order Details × 77 Products) -- that's roughly 7.5GB of data.&lt;/p&gt;  &lt;p&gt;That return is obviously way out of line, considering there are only 830 orders in the system. Of course, this is a slightly exaggerated example for demonstration purposes, but it's easy to see how a single cross join can waste a lot of database resources (CPU and memory) and delay network traffic between the database and web server.&lt;/p&gt;  &lt;p&gt;An incomplete &lt;code&gt;WHERE&lt;/code&gt; clause isn't quite as bad, but can still return more rows than you need. The following query returns 2155 rows:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;SELECT * FROM [Order Details]&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;This one, on the other hand, returns three rows:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;SELECT * FROM [Order Details] WHERE OrderID = 10252&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;ADO.NET makes it really easy to filter your data on the server, but unfortunately this feature is a double-edged sword -- it can mask problems with a large result set. That's why it's important to verify the problem with real, systematic measurement rather than just assume that a &lt;code&gt;Gridview&lt;/code&gt; displaying only a handful of rows couldn't possibly be the source of the problem.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Checking the Query Plan&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;If your query is indeed returning the correct number of rows, but still takes too long, the next step is to look at the query plan, which is a visual representation of the steps that your query takes to return its result set.&lt;/p&gt;  &lt;p&gt;You can view the estimated query execution plan in SSMS if you first select the query, then select Display Estimated Execution Plan from the Query menu (tor use the toolbar button or the keyboard shortcut -- Ctrl-L). You'll also have the option to include the actual query execution plan (also available from the Query menu, the toolbar, and via the keyboard shortcut Ctrl-M). The actual plan is a little more accurate than the estimated one, but requires that you actually execute the query and wait for it to complete.&lt;/p&gt;  &lt;p&gt;Let's look at the actual execution plan for the &lt;code&gt;uspGetBillOfMaterials&lt;/code&gt; stored procedure in the AdventureWorks sample database that comes with SQL Server. Enter the following text in the SSMS query window, then turn on the Include Actual Execution Plan option and execute the query:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;EXEC dbo.uspGetBillOfMaterials 800, '2001-01-09'&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;Figure 15.16 shows the result.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_query-plan-complete.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.16. The execution plan for the uspGetBillOfMaterials stored procedure (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_query-plan-complete.thumb.png" alt="" height="129" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;Figure 15.17 shows a close-up of the bottom right-hand corner of our plan.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_query-plan-zoom.png" class="beatbox"&gt;&lt;em&gt;Figure 15.17. A close-up of the execution plan for the uspGetBillOfMaterials stored procedure (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_query-plan-zoom.png" alt="" height="263" width="398" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;You'll need to look for a few important things when you're analyzing an execution plan:&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;&lt;strong&gt;Thick lines&lt;/strong&gt; in the execution plan indicate large amounts of data being passed between steps. I was once troubleshooting an extremely slow summary report query that returned only a dozen rows. When I looked at the execution plan, I saw that some of the lines between steps were an inch thick -- this indicated billions of rows being passed between those steps, which were then filtered down to the final dozen rows displayed in the browser. The solution was to modify the query to ensure that the data was filtered as early as possible.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Large percentage numbers&lt;/strong&gt; indicate the most expensive operations -- the value of 44% in Figure 15.17 is one example of this.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;If one of the steps on your plan contains the word "scan"&lt;/strong&gt; (or, in particular, "Table Scan"), this is an indication that the SQL engine had to step through every row in a table to find the data that it was after. This is usually associated with a high Cost value. There are occasions when a table scan is acceptable -- such as when you're performing a lookup against a very small table -- but in general they're best avoided.&lt;/li&gt;&lt;/ul&gt;   &lt;p&gt;If you see a problem, you can troubleshoot it in SSMS: modify the query and view the effect of your change on the execution plan.&lt;/p&gt;  &lt;p&gt;If it looks as though your issue may be the result of an indexing problem, the best solution is to right-click the query and select Analyze Query in Database Engine Tuning Advisor. The DTA will launch with all the necessary options preselected, so all you need to do is click the Start Analysis button. Figure 15.18 shows the results of one such analysis.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_dta-recommendations.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.18. Analyzing a query in the DTA (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_dta-recommendations.thumb.png" alt="" height="254" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;As you can see, the DTA has recommended two index changes and one statistical change that should improve this query's performance by 31%. Of course, you'll need to consider the effect that these additional indexes will have on updates to the affected tables. In this case, since the tables in this particular example are probably updated rather infrequently, I think that these new indexes make sense. You can apply or save these changes via the Actions menu.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Eliminating Cursors&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;You'll want to look for and eliminate any unnecessary cursors -- pointers for traversing records in the database.&lt;/p&gt;  &lt;p&gt;Cursors let you write in a procedural style, applying logic to a single table row at a time. While it can be tempting to drop back to those skills that are most familiar to you in sticky situations, cursor-based queries will prevent the database engine from taking advantage of the index optimizations and set-based operations for which it was designed.&lt;/p&gt;  &lt;p&gt;Resist the urge and get rid of your cursors!&lt;/p&gt;  &lt;p&gt;I've written a lot of SQL in ten years of professional programming, and I've yet to encounter a case where cursors were required. I recently rewrote someone else's complex query from using cursors to standard SQL, and the time for the resulting operation dropped from eight hours to just over one minute.&lt;/p&gt;  &lt;p&gt;Think about how to describe the problem as a bulk operation. For example, suppose your mode of thinking about a query was something like this:&lt;/p&gt;  &lt;p&gt;"I'll loop through the orders table, get the product ID, then grab the price, and compare it to ..."&lt;/p&gt;  &lt;p&gt;Instead, consider rephrasing it to something like this:&lt;/p&gt;  &lt;p&gt;"I want to find all orders for products that have prices greater than a certain amount ..."&lt;/p&gt;  &lt;p&gt;Remember that you can use common table expressions (CTEs), table variables, and temporary tables if you're stuck. While these fallback options aren't as efficient as performing a bulk operation, they at least allow the query engine to make use of indexes.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;The Problem with &lt;code&gt;SELECT *&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Most developers will tell you that &lt;code&gt;SELECT *&lt;/code&gt; queries are bad, but for the wrong reason.&lt;/p&gt;  &lt;p&gt;The commonly understood reason is that &lt;code&gt;SELECT *&lt;/code&gt; is wasteful because it returns columns that you don't need. While this is true, most normalized tables don't contain that many columns, so these extra rows usually won't have a noticeable impact on your site's performance unless they number in the millions.&lt;/p&gt;  &lt;p&gt;Often, the bigger problem with &lt;code&gt;SELECT *&lt;/code&gt; is the effect it will have on the execution plan. While SQL Server primarily uses indexes to look up your data, if the index happens to contain all of the columns you request, it won't even need to look in the table. This concept is known as index coverage.&lt;/p&gt;  &lt;p&gt;Compare the following two queries (against the sample AdventureWorks database):&lt;/p&gt;  &lt;p&gt;&lt;code&gt;SELECT * FROM Production.TransactionHistoryArchive &lt;br /&gt;  WHERE ReferenceOrderID &lt; 100&lt;br /&gt;SELECT ReferenceOrderLineID FROM &lt;br /&gt;    Production.TransactionHistoryArchive&lt;br /&gt;  WHERE ReferenceOrderID &lt;&gt;&lt;/p&gt;  &lt;p&gt;In both cases, we're returning the same number of rows, and the SELECT * query only returns 15KB more data than the second query. However, take a look at the execution plans shown in Figure 15.19.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_exec-plan-comparison.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.19. The execution plan for two queries -- one using SELECT *, and one using the table name (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_exec-plan-comparison.thumb.png" alt="" height="144" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;You'll notice that the first query took 99% of the relative work for both queries. The second query was able to look up the values in the index via an index seek -- a search that touches on only those rows that qualify. The first query, however, had to scan all the rows in the table. In this case, the fact that the requested columns were all contained in the search index resulted in a performance difference of nearly one hundred-fold.&lt;/p&gt;  &lt;p&gt;It's important to include commonly queried data in your indexes -- something that's simply not feasible if you're using &lt;code&gt;SELECT *&lt;/code&gt;. If you just query the rows you need, the DTA will be able to recommend indexes to cover them.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Accessing More Information&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;There's a plethora of resources to which you can turn when you're stuck on a really difficult SQL database issue. Here are just a few of them:&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;The Microsoft TechNet &lt;a class="sublink" href="http://www.microsoft.com/technet/prodtechnol/sql/2005/tsprfprb.mspx#E1BAG"&gt;article on troubleshooting performance problems in SQL Server 2005&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a class="sublink" href="http://www.sqlteam.com/"&gt;SQLTeam.com&lt;/a&gt; -- one of many SQL Server community forums&lt;/li&gt;&lt;li&gt;&lt;a class="sublink" href="http://www.sitepoint.com/launch/dbforum/"&gt;The SitePoint Databases Forum&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;   &lt;h5&gt;Summary&lt;/h5&gt;    &lt;p&gt;Performance optimization is an iterative process -- be prepared to follow the repetitive steps of analyzing your site's performance, tuning your application, analyzing the performance again, then tuning some more, until your site performs the way you want it to. Premature optimization -- tuning without understanding what's causing the slowdown -- is likely to cause more problems than it solves.&lt;/p&gt;  &lt;p&gt;ASP.NET and SQL Server 2005 power some of the most popular and powerful sites upon the planet, including &lt;a class="sublink" href="http://www.myspace.com/"&gt;MySpace&lt;/a&gt;, which serves billions of page views per day. You've got all the tools you need to get the maximum possible use out of your web server -- I hope the tips in this chapter will help you to put them to work.&lt;/p&gt;  &lt;p&gt;That's it for this chpater of &lt;a class="sublink" href="http://www.sitepoint.com/books/aspnetant1/"&gt;&lt;em&gt;The ASP.NET 2.0 Anthology, 101 Essential Tips, Tricks &amp;amp; Hacks&lt;/em&gt;&lt;/a&gt;. Feel free to download this chapter -- along with three others -- for offline reference.&lt;/p&gt;&lt;p style="text-align: right;"&gt;&lt;span style="font-weight: bold;"&gt;www.sitepoint.com&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4004158710435644696-3184671451063920096?l=eswebsoft.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/3184671451063920096/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/speed-up-your-site-8-aspnet-performance_363.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/3184671451063920096'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/3184671451063920096'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/speed-up-your-site-8-aspnet-performance_363.html' title='Speed Up Your Site! 8 ASP.NET Performance Tips 6'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4004158710435644696.post-88254218570243724</id><published>2009-08-03T12:54:00.000+07:00</published><updated>2009-08-03T12:55:39.102+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='asp.net'/><category scheme='http://www.blogger.com/atom/ns#' term='dotnet'/><title type='text'>Speed Up Your Site! 8 ASP.NET Performance Tips 5</title><content type='html'>&lt;h5&gt;How can I gain more control over the ASP.NET cache?&lt;/h5&gt;    &lt;p&gt;As we've seen, declarative caching provides a great return on investment -- it's very easy to enable, and will give you some immediate performance benefits. However, you can gain even more benefit from the ASP.NET cache with only a little more work.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Solution&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;If you'd like to really take advantage of caching in ASP.NET, it's time you met the Cache API. Declarative caching might be easy to set up, but it can only take you so far. Unlike declarative caching, which stores and reuses rendered HTML, the Cache API allows you to store data efficiently in your application logic or code-behind code.&lt;/p&gt;  &lt;p&gt;The simplest way to think of the ASP.NET cache is to compare it to the Application object. They're similar in that you can use both to store objects or values by a string key:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;Cache["States"] = new string[] {"Alabama","Arkansas","Alaska"};&lt;br /&gt;Cache["ProductData"] = GetProductDataset();&lt;/code&gt;&lt;/p&gt;&lt;p&gt;So what's the difference between the Application object and the cache for the storage of information? ASP.NET can remove items from the cache whenever it needs to free up memory. The cache has a limited amount of memory to work with, so when new content is added to the cache, the cache usually has to delete some older, cached data.&lt;/p&gt;  &lt;p&gt;Different classes of information can be stored in your cache:&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;&lt;strong&gt;Expensive data&lt;/strong&gt; is information that you want to keep in the cache whenever possible. The term "expensive" refers to the fact that the generation of this class of data involves valuable resources (database or processing power).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Inexpensive data&lt;/strong&gt; refers to all of the other types of information that you'd like to put in the cache if there happens to be room, but is not particularly resource-intensive to generate.&lt;/li&gt;&lt;/ul&gt;   &lt;p&gt;The challenge is to prevent the inexpensive data from pushing the expensive data out of the cache.&lt;/p&gt;  &lt;p&gt;The &lt;code&gt;Cache&lt;/code&gt; object comes with some features that give you some control over which items are placed in the cache, and how long they stay there. Using the &lt;code&gt;Cache&lt;/code&gt; object, you can apply custom cache dependencies, explicitly set cache expiration policies, and define callback events that fire when an item is removed from the cache (so you can decide whether you'd like to add it to the cache again).&lt;/p&gt;  &lt;p&gt;One of my favorite features is the sliding expiration, which is a setting that lets you specify that an item should stay in the cache -- as long as it has been used within a certain period of time:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;Cache.Insert("ProductData", GetProducts(), null, &lt;br /&gt;    System.Web.Caching.Cache.NoAbsoluteExpiration,&lt;br /&gt;    TimeSpan.FromMinutes(10));&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;The above code tells the cache that we'd like it to keep our Products data set, as long as it has been used in the past ten minutes. The benefit of this approach is that frequently used data will stay cached, and infrequently used data will expire and stop taking up valuable space.&lt;/p&gt;  &lt;p&gt;We can customize the settings for our cache, for example, setting a longer sliding expiration timeframe on data that's more expensive (such as that which results from a web service call). We could even add a cache dependency on the results of a &lt;code&gt;GetLastUpdateTimestamp&lt;/code&gt; web service call to keep the data current if needed. Remember, though, that any data can still be removed from the cache at any time -- our sliding expiration time setting is really just a suggestion to the cache system.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Discussion&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Once you've begun to cache your data, you'll begin to see the benefit of what are some tried and true cache access patterns. Steven Smith wrote about the cache data reference pattern in his &lt;a class="sublink" href="http://msdn2.microsoft.com/en-us/library/aa478965.aspx"&gt;excellent MSDN article&lt;/a&gt;. Here's some code that implements this pattern:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;public DataTable GetCustomers(bool BypassCache)&lt;br /&gt;{&lt;br /&gt;  string cacheKey = "CustomersDataTable";&lt;br /&gt;  object cacheItem = Cache[cacheKey] as DataTable;&lt;br /&gt;  if((BypassCache) || (cacheItem == null))&lt;br /&gt;  {&lt;br /&gt;    cacheItem = GetCustomersFromDataSource();&lt;br /&gt;    Cache.Insert(cacheKey, cacheItem, null,&lt;br /&gt;        DateTime.Now.AddSeconds(GetCacheSecondsFromConfig(cacheKey),&lt;br /&gt;        TimeSpan.Zero);&lt;br /&gt;  }&lt;br /&gt;  return (DataTable)cacheItem;&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;Smith's article explains the technique in more detail, but the most important point to note is the possibility that the object may not be in cache, or could potentially be removed at any time. The above code is safe because it loads the cached object, checks whether the object is null, and, if so, loads the object data and adds it back to the cache.&lt;/p&gt;  &lt;p&gt;Another of my favorite cache patterns is one that Gavin Joyce uses on his DotNetKicks site -- the &lt;a class="sublink" href="http://weblogs.asp.net/gavinjoyce/pages/The-Reluctant-Cache-Pattern.aspx"&gt;reluctant cache pattern&lt;/a&gt;, which relies on his &lt;code&gt;ReluctantCacheHelper&lt;/code&gt; class.  This pattern prevents an application from adding to the cache information that's unlikely to be used.&lt;/p&gt;  &lt;p&gt;For example, when Google indexes your site, it will load every page that it can find. If your site implements a cache that is used by a large number of pages, your server will perform a lot of unnecessary work adding data to the cache, only for that data to be immediately dropped from the cache as other pages are added. Similar to the sliding expiration pattern, but in reverse, this pattern only adds data to the cache if it's been used a lot recently. Here's an example that implements this pattern:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;public static List&lt;customer&gt; GetCustomers() {&lt;br /&gt;  string cacheKey = "Customers";&lt;br /&gt;  int cacheDurationInSeconds = 5; // low number for demo purposes&lt;br /&gt;  object customers = HttpRuntime.Cache[cacheKey] as List&lt;customer&gt;;&lt;br /&gt;  if (customers == null) {&lt;br /&gt;    customers = CustomerDao.GetCustomers();&lt;br /&gt;    if (new ReluctantCacheHelper(cacheKey, &lt;br /&gt;        cacheDurationInSeconds, 2).ThresholdHasBeenReached)&lt;br /&gt;    {&lt;br /&gt;      HttpRuntime.Cache.Insert(cacheKey,&lt;br /&gt;          customers, &lt;br /&gt;          null, &lt;br /&gt;          DateTime.Now.AddSeconds(cacheDurationInSeconds),&lt;br /&gt;          System.Web.Caching.Cache.NoSlidingExpiration);&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;  return (List&lt;customer&gt;)customers;&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;  &lt;h5&gt;How do I speed up my database queries?&lt;/h5&gt;    &lt;p&gt;We've looked at a few ways to optimize your ASP.NET code, but if your queries are slow, you'll still have a site that drags its heels. You can hide the problem to some degree if you cache your data or add servers to your web farm, but eventually you will need to deal with your slow queries.&lt;/p&gt;  &lt;p&gt;Of course, the best bet is to work with a good Database Administrator (DBA). We're ASP.NET developers, and while we can't help but learn about databases as we work with them, database administration is not our full-time job. A good DBA is by far the best solution to a database problem, but sometimes, it's just not an option. If you work on a team without a DBA, or you have limited access to your DBA, you need to do your best to solve problems when you can.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Slow Query or Slow Database?&lt;/em&gt;&lt;br /&gt;&lt;em&gt;It's important to decide whether you're dealing with one slow query or a whole slow database. Is one particular page slow, or is the whole site groaning? This solution will focus on the former; if you can narrow your database performance problems down to individual queries, refer to the section called "How can I troubleshoot a slow query?" later in this chapter.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Solution&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Use the SQL Profiler and Database Tuning Advisor, located in the SQL Server Performance Tools folder.&lt;/p&gt;  &lt;p&gt;Tuning a database server is hard. Initially, it can be difficult to find out what's responsible for slowing things down. Even then, fixing one problem (for example, applying indexes to a table to improve the speed of &lt;code&gt;SELECT&lt;/code&gt; statements) can introduce new problems (such as slower &lt;code&gt;INSERT&lt;/code&gt;s and &lt;code&gt;UPDATE&lt;/code&gt;s).&lt;/p&gt;  &lt;p&gt;Fortunately, the existence of the SQL Performance Tools means you don't have to bother with guesswork.&lt;/p&gt;  &lt;p&gt;The SQL Profiler captures what's going on in your database, including which SQL statements are being executed, who's executing them, and how long they're taking. The profiler is the first step to determining what's actually happening on your server.&lt;/p&gt;  &lt;p&gt;Note that the profiler captures both ad hoc and dynamic SQL. This means that the profiler is especially useful when control, library, or framework code is making a call in your database -- you may not have access to the ASP.NET code, but the profiler will reveal exactly which queries are being executed, and how long they are taking.&lt;/p&gt;  &lt;p&gt;It's best if you can run a profiler trace on your actual production system, but this may not always be possible -- profiling a server slows the server down, and the site's regular users may not appreciate the extra delays. If you can't run on the production system, you can still gain a reasonable idea of server activity on a local or development server by simulating the way real users would using your site on the live server.&lt;/p&gt;  &lt;p&gt;Running the profiler can be a bit daunting the first time -- there are numerous advanced settings to monitor and tweak. It's best to start out with a predefined template like the one I've chosen in Figure 15.9. The two most useful templates for general performance diagnostics are the TSQL_Duration and Tuning templates.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_sql-profiler-templates.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.9. Selecting the TSQL_Duration template (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_sql-profiler-templates.thumb.png" alt="" height="259" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;The TSQL_Duration template is useful for giving you a quick snapshot of the queries and stored procedures that take the longest time to execute. Figure 15.10 shows some sample queries running against the Northwind example database. The slowest query -- the query with the greatest duration value -- is highlighted at the bottom of the list.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_sql-profiler-sample-queries.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.10. Query duration times in SQL Server Profiler (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_sql-profiler-sample-queries.thumb.png" alt="" height="275" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;In a simple case like the one above, you may be able to deduce enough information from the TSQL_Duration trace to begin tuning a specific query. If you're at all in doubt, however, it's best to run the profiler with the Tuning template and analyze the results in the Database Tuning Advisor (also referred to as the DTA), a tool for analyzing database performance and suggesting which tables should be indexed.&lt;/p&gt;  &lt;p&gt;To do this, first save your trace file from the profiler, as shown in Figure 15.11.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_database-tuning-advisor.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.11. Saving a trace file from SQL Server Profiler (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_database-tuning-advisor.thumb.png" alt="" height="321" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;Now, we'll use the DTA to open the trace file that we just saved, as I've done in Figure 15.12, and click the Start Analysis button.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_dta-start-analysis.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.12. Loading the trace file in the Database Tuning Advisor (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_dta-start-analysis.thumb.png" alt="" height="316" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;For my sample queries, DTA recommended that I apply a few indexes, to produce an estimated performance improvement of 5%, as shown in Figure 15.13. (The Northwind database is already more or less indexed; your estimated improvement should be a lot higher, with any luck.)&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_dta-estimated-improvements.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.13. The index recommendations suggested by the DTA (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_dta-estimated-improvements.thumb.png" alt="" height="321" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;If you scroll to the right, so that the Description column is visible, you'll see exactly which indexes and statistical changes the DTA recommends. Click on a recommendation to see a preview of the script that will add the proposed index, as shown in Figure 15.14.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_dta-script-preview.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.14. Viewing a preview of the script for applying one of the recommended indexes (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_dta-script-preview.thumb.png" alt="" height="321" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;To implement these changes, I suggest you save the recommendations (Actions &gt; Save Recommendations...), review them in SQL Server Management Studio (SSMS), and apply them if you feel comfortable with them. Once you've done this, repeat your original profiling test and verify that the changes have improved your database performance.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Running the DTA with a SQL Workload Script File&lt;/em&gt;&lt;br /&gt;&lt;em&gt;Our walkthrough of the DTA used a SQL Server Trace file for the workload, but you can also use the DTA against a SQL script. Here's an abbreviated copy of the script I used for this walkthrough:&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;code&gt;Example 15.10. SampleSqlWorkload.sql (excerpt)&lt;br /&gt;&lt;br /&gt;USE [Northwind]&lt;br /&gt;GO&lt;br /&gt;SELECT * FROM [Order Details] od&lt;br /&gt;INNER JOIN Orders o on o.OrderID = od.OrderID&lt;br /&gt;GO&lt;br /&gt;SELECT * FROM [Category Sales for 1997]&lt;br /&gt;GO&lt;br /&gt;SELECT * FROM Invoices&lt;br /&gt;GO&lt;br /&gt;SELECT COUNT(OrderID) OrderCount, ShipPostalCode &lt;br /&gt;    FROM Orders&lt;br /&gt;GROUP BY ShipPostalCode&lt;br /&gt;ORDER BY COUNT(OrderID) DESC&lt;br /&gt;GO&lt;br /&gt;EXEC [Ten Most Expensive Products]&lt;br /&gt;GO&lt;br /&gt;SELECT COUNT(OrderID) OrderCount, c.CustomerID, &lt;br /&gt;    c.ContactName&lt;br /&gt;FROM Orders o INNER JOIN Customers c &lt;br /&gt;    ON o.CustomerID = c.CustomerID&lt;br /&gt;GROUP BY c.CustomerID, c.ContactName &lt;br /&gt;sORDER BY COUNT(OrderID) DESC&lt;br /&gt;GO&lt;br /&gt;SELECT LEN(ContactName), ContactName&lt;br /&gt;FROM Customers ORDER BY LEN(ContactName) DESC&lt;br /&gt;GO&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;The important point to note is that there are GO separators between statements, which ensures that they're executed independently. You'll want your SQL workload script file to simulate actual usage, which means that you should include repeated calls to the most commonly used queries.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Using the Performance Dashboard&lt;/em&gt;&lt;br /&gt;&lt;em&gt;SQL Server includes Dynamic Management Views (DMVs) -- database views that contain lots of useful management and troubleshooting information about your database. All the DMV views begin with the prefix &lt;code&gt;sys.dm_&lt;/code&gt;; for example: &lt;code&gt;sys.dm_index_usage_stats&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;SSMS includes some built-in reports that leverage SQL Server Reporting Services as well as the DMVs. You can view these reports in SSMS if you right-click a database and select Reports &gt; Standard Reports....&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;SQL Server SP2 includes the ability to include custom reports, and one of the first to be released is the &lt;a class="sublink" href="http://www.microsoft.com/downloads/details.aspx?familyid=1d3a4a0d-7e0c-4730-8204-e419218c1efc"&gt;Performance Dashboard&lt;/a&gt;. Once it's installed, the Performance Dashboard gives you a graphical snapshot that's visible in your browser, without you having to run a trace. Figure 15.15 shows the dashboard in action.&lt;/em&gt;&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_dta-performance-dashboard.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.15. Viewing a handy third-party custom report in the Performance Dashboard (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_dta-performance-dashboard.thumb.png" alt="" height="312" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Discussion&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;One important aspect of troubleshooting a slow database is to understand what's making it run slowly. There are many potential causes of slow performance, but some common problems head the list. Let's look at a few of them.&lt;/p&gt;&lt;p style="text-align: right;"&gt;&lt;span style="font-weight: bold;"&gt;www.sitepoint.com&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4004158710435644696-88254218570243724?l=eswebsoft.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/88254218570243724/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/speed-up-your-site-8-aspnet-performance_5620.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/88254218570243724'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/88254218570243724'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/speed-up-your-site-8-aspnet-performance_5620.html' title='Speed Up Your Site! 8 ASP.NET Performance Tips 5'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4004158710435644696.post-3243405540287423446</id><published>2009-08-03T12:51:00.000+07:00</published><updated>2009-08-03T12:52:41.460+07:00</updated><title type='text'>Speed Up Your Site! 8 ASP.NET Performance Tips 4</title><content type='html'>&lt;h5&gt;How can I improve the speed of my site?&lt;/h5&gt;    &lt;p&gt;Most ASP.NET web servers perform a lot of unnecessarily repetitive work.&lt;/p&gt;  &lt;p&gt;For example, suppose you have a page with a DataGrid bound to a table called Products. Every time a user requests the Products page, ASP.NET has to:&lt;/p&gt;  &lt;ol&gt;&lt;li&gt;Look up the products data in the database.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Process the page.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Databind the product data.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Render the results to HTML.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Output the results to the browser.&lt;/li&gt;&lt;/ol&gt;   &lt;p&gt;If we assume the products list changes infrequently in comparison to how often it is requested by a user, most of that work is unnecessary. Instead of doing all that work to send the same HTML to all users, why not just store the HTML and reuse it until the Products table changes?&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Solution&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;The ASP.NET cache provides the key to efficient storage and reuse of our HTML.&lt;/p&gt;  &lt;p&gt;There are several ways to use the cache; we'll focus on the easiest tricks to get you started, then point you toward some resources that will help you tackle more advanced ways to utilize the cache.&lt;/p&gt;  &lt;p&gt;The simplest solution is to use the &lt;code&gt;OutputCache&lt;/code&gt; directive on your pages or user controls. For example, the following page will be cached for one hour (3600 seconds). You can refresh the page all you like, but the value of &lt;code&gt;DateTime&lt;/code&gt;. Now won't change until the page is cleared from the cache. Here's the code that retrieves the current time:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;Example 15.7. OutputCacheSimple.aspx (excerpt)&lt;br /&gt;&lt;br /&gt;&lt;%@ Page Language="C#" AutoEventWireup="true"   &lt;br /&gt;    CodeBehind="OutputCacheSimple.aspx.cs"&lt;br /&gt;    Inherits="chapter_15_performance.Performance.OutputCacheSimple" &lt;br /&gt;%&gt;&lt;br /&gt;&lt;%@ OutputCache Duration="3600" VaryByParam="none" %&gt;&lt;br /&gt;&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;&lt;br /&gt;&lt;head id="Head1" runat="server"&gt;&lt;br /&gt;  &lt;title&gt;Output Cache Example&lt;/title&gt;&lt;br /&gt;&lt;/head&gt;&lt;br /&gt;&lt;body&gt;&lt;br /&gt;  &lt;form id="form1" runat="server"&gt;&lt;br /&gt;    &lt;h1&gt;Output Cache Example&lt;/h1&gt;&lt;br /&gt;    &lt;div&gt;&lt;br /&gt;      &lt;%= DateTime.Now.ToLongTimeString() %&gt;&lt;br /&gt;    &lt;/div&gt;&lt;br /&gt;  &lt;/form&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;&lt;/html&gt;&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;Figure 15.5 represents our page at 11:01:41 p.m.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_output-cache-example-1.png" class="beatbox"&gt;&lt;em&gt;Figure 15.5. Loading a cached page for the first time (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_output-cache-example-1.png" alt="" height="115" width="355" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;Figure 15.6 shows what it looks like at 11:23:01 p.m.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_output-cache-example-2.png" class="beatbox"&gt;&lt;em&gt;Figure 15.6. Subsequent reloads of a cached page showing no changes to the page content (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_output-cache-example-2.png" alt="" height="115" width="355" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;Notice that the time didn't change, as the page wasn't re-rendered.&lt;/p&gt;  &lt;p&gt;Now let's look at a slightly more complex caching example:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;Example 15.8. OutputCache.aspx (excerpt)&lt;br /&gt;&lt;br /&gt;&lt;%@ Page Language="C#" AutoEventWireup="true"&lt;br /&gt;    CodeBehind="OutputCache.aspx.cs" &lt;br /&gt;    Inherits="chapter_15_performance.Performance.OutputCache" %&gt;&lt;br /&gt;&lt;%@ OutputCache Duration="30" VaryByParam="none" %&gt;&lt;br /&gt;&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;&lt;br /&gt;&lt;head id="Head1" runat="server"&gt;&lt;br /&gt;  &lt;title&gt;Output Cache Example&lt;/title&gt;&lt;br /&gt;  &lt;script type="text/javascript"&gt;&lt;br /&gt;    var d = new Date();&lt;br /&gt;  &lt;/script&gt;&lt;br /&gt;&lt;/head&gt;&lt;br /&gt;&lt;body&gt;&lt;br /&gt;  &lt;form id="form1" runat="server"&gt;&lt;br /&gt;  &lt;!-- Pausing 5 seconds to simulate a database hit --&gt;&lt;br /&gt;  &lt;% System.Threading.Thread.Sleep(5000); %&gt;&lt;br /&gt;  &lt;h1&gt;Output Cache Example&lt;/h1&gt;&lt;br /&gt;  &lt;div&gt;&lt;br /&gt;    Time written by ASP.NET:&lt;br /&gt;    &lt;%= DateTime.Now.ToLongTimeString() %&gt;&lt;br /&gt;  &lt;/div&gt;&lt;br /&gt;  &lt;div&gt;&lt;br /&gt;    Time written by Javascript:&lt;br /&gt;    &lt;script type="text/javascript"&gt;&lt;br /&gt;      document.write(d.toLocaleTimeString())&lt;br /&gt;    &lt;/script&gt;&lt;br /&gt;  &lt;/div&gt;&lt;br /&gt;  &lt;div id="divCached" style="margin-top:25px;width=300px;"&gt;&lt;br /&gt;    &lt;script type="text/javascript"&gt;&lt;br /&gt;      var aspTime = new Date();&lt;br /&gt;      aspTime.setSeconds(&lt;%= DateTime.Now.Second %&gt;);&lt;br /&gt;      &lt;br /&gt;      // If there are more than two seconds difference&lt;br /&gt;      // between the ASP.NET render and the javascript&lt;br /&gt;      // evaluation, the page is almost certainly cached.&lt;br /&gt;      if(Math.abs(d - aspTime) &gt; 2000)&lt;br /&gt;      {&lt;br /&gt;        document.write('Probably Cached');&lt;br /&gt;        document.getElementById("divCached").style.backgroundColor = &lt;br /&gt;          "Coral";&lt;br /&gt;      }&lt;br /&gt;      else&lt;br /&gt;      {&lt;br /&gt;        document.write('Not Cached');&lt;br /&gt;        document.getElementById("divCached").style.backgroundColor = &lt;br /&gt;          "Aqua";&lt;br /&gt;      }&lt;br /&gt;    &lt;/script&gt;&lt;br /&gt;  &lt;/div&gt;&lt;br /&gt;&lt;/form&gt;&lt;br /&gt;&lt;/body&gt;&lt;br /&gt;&lt;/html&gt;&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;The above page writes out the current time according to both ASP.NET (server side, cached) and JavaScript (client side, not cached). Additionally, if the JavaScript time is more than two seconds after the ASP.NET time, the page will report that it has probably been cached.&lt;/p&gt;  &lt;p&gt;The first time this page is viewed, there's a five-second delay (due to the call to Thread.Sleep), then the page shown in Figure 15.7 is displayed.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_output-not-cached.png" class="beatbox"&gt;&lt;em&gt;Figure 15.7. The page output on first load (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_output-not-cached.png" alt="" height="151" width="356" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;But if you refresh the page within 30 seconds, you'll notice two differences. First, the page will return immediately. Second, it looks like Figure 15.8.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_output-probably-cached.png" class="beatbox"&gt;&lt;em&gt;Figure 15.8. The page output 30 seconds after its initial load (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_output-probably-cached.png" alt="" height="151" width="356" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;&lt;em&gt;Using &lt;code&gt;VaryByParam&lt;/code&gt; to Cache Parameterized Pages&lt;/em&gt;&lt;br /&gt;&lt;em&gt;When you start to consider which pages in your application could be cached, you'll probably discover that many of them contain content that's 90% static. The remaining 10% of these pages is likely to contain one or two tiny portions that vary frequently. For example, consider a page in the example Northwind database that displays catalog information, including products filtered by category. The category is set by a parameter in the query string, so the following two URLs will yield different results:&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;code&gt;http://www.contoso.com/Northwind/Products.aspx?Category=Seafood&lt;/code&gt;&lt;br /&gt;&lt;code&gt;http://www.contoso.com/Northwind/Products.aspx?Category=Produce&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;The novice programmer might apply the following code to cache these pages:&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;code&gt;&lt;%@ OutputCache Duration="30" %&gt;&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;However, if you were to apply this code you'd quickly discover a problem -- the category filter would stop working. The first version of the page that was displayed would be cached, so the filtering logic would be ignored on subsequent page views.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;That's the exact function for which the &lt;code&gt;VaryByParam&lt;/code&gt; attribute is used. In this case, we'd change the &lt;code&gt;OutputCache&lt;/code&gt; directive to the following:&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;code&gt;&lt;%@ OutputCache Duration="30" VaryByParam="Category" %&gt;&lt;/code&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;The &lt;code&gt;VaryByParam&lt;/code&gt; attribute tells the cache system to store a different version of the page every time it sees a new value for Category in the URL. Once this attribute is in place, our users will be able to access a cached copy of the page for both the Seafood and Produce categories.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Keep in mind that storing different versions of the page for each parameter value isn't always the best idea -- in cases where you have many different parameter values, you can quickly use a large amount of memory to hold all possible variations of the page. Use &lt;code&gt;VaryByParam&lt;/code&gt; when your pages utilizes a limited number of parameter values.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;Note that the number of seconds displayed in the JavaScript-generated time has updated to 59 seconds, while the ASP.NET time still shows 39. What this discrepancy suggests is that the server is sending the same HTML to the client for each request. Suppose that instead of just displaying the current time, we'd bound several &lt;code&gt;GridView&lt;/code&gt;s to the results of several expensive database queries. With caching applied to such a page, the savings would be considerable.&lt;/p&gt;  &lt;p&gt;You don't have to cache a page for long to see significant performance increases -- a cache of one minute or less can improve your site's performance dramatically. For example, adding a cache to a simple page that just binds a &lt;code&gt;GridView&lt;/code&gt; to the Products table in the example Northwind database will increase the performance of that page by about 500%, and for a page that performs a complex database query or processor-intensive calculation, this saving is likely to be amplified even further.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Using Post-cache Substitution&lt;/em&gt;&lt;br /&gt;&lt;em&gt;While the &lt;code&gt;OutputCache&lt;/code&gt; directive allows you to specify that the cache should vary with a specific parameter, it's not really efficient to cache a multitude of copies of one page that are nearly all identical.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Post-cache substitution, using the &lt;code&gt;Substitution&lt;/code&gt; control, allows you to inject dynamic content into a cached page. The result is that you can cache some pages that you probably thought were unable to be cached. Read more on this subject in &lt;a class="sublink" href="http://weblogs.asp.net/scottgu/archive/2006/11/28/tip-trick-implement-donut-caching-with-the-asp-net-2-0-output-cache-substitution-feature.aspx"&gt;Scott Guthrie's article on the topic&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;One of the problems you might notice with this approach, however, is latency -- the time delay between the point at which your data is updated and the moment when that same updated data reaches a user's browser. In the examples we've looked at in this solution, we displayed the same timestamp for 30 seconds after the page was rendered -- this is great for performance, but might be a problem if your users require the data to always be up to date.&lt;/p&gt;  &lt;p&gt;Fortunately, ASP.NET 2.0 has a solution to this problem, which we'll look at next.&lt;/p&gt;  &lt;h5&gt;How do I refresh my cache when the data changes?&lt;/h5&gt;    &lt;p&gt;As we saw in the previous tip, judicious use of output caching can provide dramatic improvements in your site's performance. Output caching works by saving the generated HTML for a rendered ASP.NET page. As long as the cache is valid, future requests for that page will just return that stored HTML, rather than processing the page again, which means no page parsing, hits to the database, data binding -- any of those tasks that require precious bandwidth or processor cycles. Used correctly, caching can improve your requests-per-second on a high-traffic page by a factor of 100 or more.&lt;/p&gt;  &lt;p&gt;For example, suppose you had a page that contained a DataGrid bound to a table called Products. Without caching, every page request would require ASP.NET to look up the product data in the database, process the page, data bind the product data, render the results to HTML, and output the results to the browser.&lt;/p&gt;  &lt;p&gt;If the page was cached, only the first request for this page would require all that work; after that, ASP.NET would just skip all the hard work and serve up the HTML until the cache expired. The end result would be the same whether or not a cache was used: the same HTML would be sent to the browser. However, since a lot less work and network traffic is required to display cached HTML, the server can serve the cached page much faster and to more people.&lt;/p&gt;  &lt;p&gt;One problem with cached pages is that they don't pick up data that has changed right away. To continue with the example above: if we were to add a new product to the Products table, it wouldn't show up on the page until the cache expired. Fortunately, ASP.NET 2.0 provides a good mechanism to refresh your cache automatically when the underlying data changes.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Solution&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;The SQL Cache Dependency was created to solve this problem. A few steps are required to set it up, but once it's up and running, you can use it throughout your whole application.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Using SQL Cache Dependencies with Older Versions of SQL Server&lt;/em&gt;&lt;br /&gt;&lt;em&gt;In this book I'll only cover the steps for configuring SQL Server Cache Dependencies on SQL Server 2005; there's a wealth of information on MSDN about how to set it up on SQL Server 2000:&lt;/em&gt;&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;&lt;a class="sublink" href="http://msdn2.microsoft.com/en-us/library/ms229862%28VS.80%29.aspx"&gt;ASP.NET SQL Server Registration Tool (Aspnet_regsql.exe)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a class="sublink" href="http://msdn2.microsoft.com/en-us/library/ms178604%28VS.80%29.aspx"&gt;Caching in ASP.NET with the SqlCacheDependency Class&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a class="sublink" href="http://msdn2.microsoft.com/en-us/library/e3w8402y%28VS.80%29.aspx"&gt;Walkthrough: Using ASP.NET Output Caching with SQL Server&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;   &lt;p&gt;First, you'll need to make sure you've enabled SQL Server Service Broker for your database. You can confirm that with the following query:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;ALTER DATABASE Northwind SET ENABLE_BROKER;&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;Next, you'll need to start the SQL Dependency event listener. The recommended place to make the change that triggers this listener is in the &lt;code&gt;Application_Start&lt;/code&gt; method of &lt;code&gt;Global.asax&lt;/code&gt;:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;Example 15.9. Global.asax (excerpt)&lt;br /&gt;&lt;br /&gt;void Application_Start(object sender, EventArgs e)&lt;br /&gt;{&lt;br /&gt;  string northwindConnection = WebConfigurationManager.ConnectionStr&lt;br /&gt;&amp;#10149;ings["NorthwindConnectionString1"].ConnectionString;&lt;br /&gt;  SqlDependency.Start(northwindConnection);&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;Once the above code has been added to the &lt;code&gt;Global.asax&lt;/code&gt; file, our connection can employ an SQL Cache Dependency.&lt;/p&gt;  &lt;p&gt;To illustrate how an SQL Dependency is utilized, let's look at an example -- a simple &lt;code&gt;GridView&lt;/code&gt; that's bound to a table. We'll drag the good old Northwind Products table onto a new page, then set the following two attributes in the &lt;code&gt;SqlDataSource&lt;/code&gt; control:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;EnableCaching = "True" SqlCacheDependency =&lt;br /&gt;    "NorthwindConnectionString1:Products"&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;With this simple line of code in place, the &lt;code&gt;GridView&lt;/code&gt; won't read the Products table again until it changes. As soon as the Products table changes, though, SQL Server will notify ASP.NET to dump the cache, and the subsequent page request will reload the page from the database. The end result is that our application gains all of the performance benefits that come with caching, but our users will never see stale data.&lt;/p&gt;&lt;p style="text-align: right;"&gt;&lt;span style="font-weight: bold;"&gt;www.sitepoint.com&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4004158710435644696-3243405540287423446?l=eswebsoft.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/3243405540287423446/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/speed-up-your-site-8-aspnet-performance_8803.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/3243405540287423446'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/3243405540287423446'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/speed-up-your-site-8-aspnet-performance_8803.html' title='Speed Up Your Site! 8 ASP.NET Performance Tips 4'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4004158710435644696.post-2560854681882615627</id><published>2009-08-03T12:41:00.003+07:00</published><updated>2009-08-03T12:49:48.940+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='asp.net'/><category scheme='http://www.blogger.com/atom/ns#' term='dotnet'/><title type='text'>Speed Up Your Site! 8 ASP.NET Performance Tips 3</title><content type='html'>&lt;h5&gt;How can I decrease the bandwidth that my site uses?&lt;/h5&gt;    &lt;p&gt;ASP.NET abstracts a lot of traditional web development details from the developer. Just drag and drop a few controls on a form, set some properties, write a little bit of code, and -- bam! -- you've got a functioning web site.&lt;/p&gt;  &lt;p&gt;However, that doesn't mean the resulting HTML markup will necessarily be efficient or small. It's not unusual to see ASP.NET pages that contain more than 100 kilobytes of markup. I recommend that you keep a close eye on the HTML markup that results from your ASP.NET web pages -- to keep these file sizes in check can sometimes require additional effort, which is one reason we covered the topic of web standards in Chapter 9, ASP.NET and Web Standards.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Solutions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;The first rule of ASP.NET bandwidth control is to know how large your pages are. In Internet Explorer, the File &gt; Properties dialog will tell you how many kilobytes of HTML markup the current page has produced, as Figure 15.2 shows. Firefox has a similar dialog, pictured in Figure 15.3, which can be accessed by selecting Tools &gt; Page Info.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_ie7-file-properties.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.2. Viewing page information in Internet Explorer (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_ie7-file-properties.thumb.png" alt="" height="480" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_firefox-page-info.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.3. Viewing page information in Firefox (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_firefox-page-info.thumb.png" alt="" height="439" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;However, note that the Size field in Firefox reports a much smaller number than does IE 7 -- 13,976 bytes versus 49,774 bytes. That's because Firefox shows the actual number of bytes that came down over the wire, whereas IE 7 shows the size of the page after it has been received by the browser.How is such a discrepancy possible? Well, the ASP.NET web site uses HTTP compression to decrease the page size before sending the page. HTTP compression is a W3C standard that allows the server to provide a GZIP-compressed version of the HTML content to the client, at the cost of a very minor increase in CPU time. The client receives the compressed content, then decompresses it on the fly before rendering the page. Right off the bat, you can see that this is just one easy way to reduce the amount of bandwidth you use by an impressive 72% -- simply flip the switch to enable HTTP compression for your web site.You can enable HTTP compression in two ways. The first takes place at the web server level; the second is implemented via a custom HTTP module at the ASP.NET application level.       &lt;p&gt;&lt;strong&gt;Enabling HTTP Compression Support in IIS 6&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Use the IIS Service Manager to enable HTTP Compression in IIS 6. Right-click the node for your web site and select Properties. The Service tab contains the settings relevant to compression, as Figure 15.4 shows.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Manual Configuration is Only Necessary in IIS 6&lt;/em&gt;&lt;br /&gt;&lt;em&gt;That's right: it's only necessary to configure IIS to enable HTTP compression in IIS 6 and earlier, as IIS 7 enables static compression by default. Windows Server 2008 (which had yet to be released at the time of this writing) may offer a user interface to configure dynamic HTTP compression, but Vista's IIS Manager doesn't.&lt;/em&gt;&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_web-sites-properties-iis6.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.4. Configuring HTTP compression in IIS 6 (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_web-sites-properties-iis6.thumb.png" alt="" height="388" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;The compression setting available in the GUI works; however, it only affects static content, such as HTML pages and CSS files. This setting won't do anything to compress dynamic content in ASPX pages. We must resort to editing the metabase -- the IIS database for configuration and metadata storage -- to deploy dynamic content compression:&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;Open the metabase in Notepad. For IIS 6, this is located at &lt;code&gt;C:\WINDOWS\system32\inetsrv\MetaBase.xml&lt;/code&gt;. For IIS 5, the file is a binary file, so you'll need to &lt;a class="sublink" href="http://www.microsoft.com/downloads/details.aspx?FamilyID=48364A72-D54E-46DC-AACF-E3BE887D17A6"&gt;download the Meta-data Edit tool instead&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Search for the &lt;code&gt;&lt;iiscompressionscheme&gt;&lt;/iiscompressionscheme&gt;&lt;/code&gt; tag. There should be two &lt;code&gt;&lt;iiscompressionscheme&gt;&lt;/iiscompressionscheme&gt;&lt;/code&gt; entries: one for deflate and one for GZIP -- the two methods of compression that IIS supports. By default, IIS uses GZIP; deflate is rarely used.&lt;/li&gt;&lt;li&gt;Search for the &lt;code&gt;HcScriptFileExtensions&lt;/code&gt; section. Add to the list aspx, asmx, php, cgi, and any other file extensions that you want dynamically compressed. Follow the existing format carefully -- it's return-delimited, and any extra spaces will prevent the file extensions from working. Make the same changes in both deflate and GZIP.&lt;/li&gt;&lt;li&gt;Set &lt;code&gt;HcDynamicCompressionLevel&lt;/code&gt; to level 9 (it has a default value of 0, which means "no compression"). I recommend level 9, based on several reports that I've read on the Web suggesting that level 10 requires much more CPU time, while offering only a minimal reduction in file size over level 9. Make this change for both deflate and GZIP.&lt;/li&gt;&lt;/ul&gt;   &lt;p&gt;Note that this is a global compression rule that will affect all web sites. This setting is usually what you'll want, since HTTP compression is so effective and the cost is nominal. However, some poorly coded ASP.NET web sites may be incompatible with compression. In those circumstances, you may want to enable or disable compression on a per-site or per-folder basis -- a setting that's also supported by IIS. The easiest way to configure this setting is to use the command line &lt;code&gt;adsutil.vbs&lt;/code&gt; utility:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;C:\Inetpub\AdminScripts\&gt;adsutil.vbs set w3svc/ (site#) /root/DoStat&lt;br /&gt;➥icCompression False&lt;br /&gt;C:\Inetpub\AdminScripts\&gt;adsutil.vbs set w3svc/ (site#) /root/DoDyna&lt;br /&gt;➥micCompression False&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;The &lt;code&gt;(site#)&lt;/code&gt; number can be obtained from the log properties dialog in the IIS server properties, and usually takes the form &lt;code&gt;W3SVCn&lt;/code&gt;, where &lt;code&gt;n&lt;/code&gt; is an arbitrary site number.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Enabling HTTP Compression Support in an ASP.NET Application&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Perhaps you don't have control over the IIS settings on your server. Or maybe you'd just like a way to enable compression for your specific ASP.NET application. That's possible too.&lt;/p&gt;  &lt;p&gt;The open source HttpCompress library is very easy to incorporate into an ASP.NET web site. First, &lt;a class="sublink" href="http://www.blowery.org/code/HttpCompressionModule.html"&gt;download the latest version of HttpCompress&lt;/a&gt; from the official web site. &lt;/p&gt;  &lt;p&gt;Place the &lt;code&gt;blowery.Web.HttpCompress.dll&lt;/code&gt; binary somewhere logical, and add a reference to it.&lt;/p&gt;  &lt;p&gt;Next, add the following compression-specific configuration section to your site's &lt;code&gt;Web.config&lt;/code&gt;:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;Example 15.5. Web.config (excerpt)&lt;br /&gt;&lt;br /&gt;&lt;configsections&gt;&lt;br /&gt;&lt;sectiongroup name="blowery.web"&gt;&lt;br /&gt; &lt;section name="httpCompress" type="blowery.Web.HttpCompress.SectionHandler,              blowery.Web.HttpCompress"&gt;&lt;br /&gt;&lt;/section&gt;&lt;br /&gt;&lt;/sectiongroup&gt;&lt;br /&gt;&lt;blowery.web&gt;&lt;br /&gt;&lt;httpcompress preferredalgorithm="gzip" compressionlevel="high"&gt;&lt;br /&gt; &lt;excludedmimetypes&gt;&lt;br /&gt;   &lt;add type="image/png"&gt;&lt;br /&gt;   &lt;add type="image/jpeg"&gt;&lt;br /&gt;   &lt;add type="image/gif"&gt;&lt;br /&gt; &lt;/add&gt;&lt;br /&gt;&lt;/add&gt;&lt;br /&gt;&lt;/add&gt;&lt;/excludedmimetypes&gt;&lt;/httpcompress&gt;&lt;/blowery.web&gt;&lt;/configsections&gt;&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;Finally, bring the actual compression HTTP module into the pipeline:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;Example 15.6. Web.config (excerpt)&lt;br /&gt;&lt;br /&gt;&lt;system.web&gt;&lt;br /&gt;&lt;httpmodules&gt;&lt;br /&gt; &lt;add name="CompressionModule" type="blowery.Web.HttpCompress.HttpModule,              blowery.web.HttpCompress"&gt;&lt;br /&gt;&lt;/add&gt;&lt;br /&gt;&lt;/httpmodules&gt;&lt;/system.web&gt;&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;Once this is in place, you should see compressed ASPX content being returned to the browser. To verify that this is the case, use Port80 Software's convenient &lt;a class="sublink" href="http://www.port80software.com/products/httpzip/compresscheck/"&gt;Real-Time Compression Checker&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;One limitation of the HttpCompress module approach is that only ASPX content that forms part of your application will be compressed; the CSS and JavaScript aren't served through the ASP.NET ISAPI handler, and will therefore remain uncompressed. As such, I recommend that you enable compression at the web server level whenever possible, so that these files also gain the benefits of compression.&lt;/p&gt;&lt;p style="text-align: right;"&gt;&lt;span style="font-weight: bold;"&gt;www.sitepoint.com&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4004158710435644696-2560854681882615627?l=eswebsoft.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/2560854681882615627/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/speed-up-your-site-8-aspnet-performance_6626.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/2560854681882615627'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/2560854681882615627'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/speed-up-your-site-8-aspnet-performance_6626.html' title='Speed Up Your Site! 8 ASP.NET Performance Tips 3'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4004158710435644696.post-8793011593210265791</id><published>2009-08-03T12:40:00.002+07:00</published><updated>2009-08-03T12:47:51.112+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='asp.net'/><category scheme='http://www.blogger.com/atom/ns#' term='dotnet'/><title type='text'>Speed Up Your Site! 8 ASP.NET Performance Tips 2</title><content type='html'>&lt;h5&gt;How can I decrease the size of the view state?&lt;/h5&gt;    &lt;p&gt;One convenience of ASP.NET controls is that they can preserve state across postbacks -- a topic we've covered in depth in Chapter 6, Maintaining State. This, of course, is a feature that comes at a price -- to implement it, we add a hidden field to the page to store the control settings for transmission between the client and server, but depending on the controls the page uses, the view state can sometimes become quite large.&lt;/p&gt;  &lt;p&gt;One obvious way to reduce the size of view state is to turn it off if you don't need it. This adjustment can be performed either at the page level, or at the control level. If, for whatever reason, you can't disable the view state (for example, your page uses controls that are dependent upon the view state), there are a few other steps you can take to at least reduce its impact on your page size.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Solutions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;You have two options for reducing the impact that view state has on your page size -- either compress the view state, or store it on the server.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Compressing the View State&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;The following simple &lt;code&gt;CompressedViewStatePage&lt;/code&gt; class implements basic GZIP compression on the page's view state. It reduced the size of the &lt;code&gt;ViewState&lt;/code&gt; object on my sample page from 20,442 bytes to 6,056 bytes -- an impressive 70% reduction in size! Here's the class in all its glory:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;Example 15.3. CompressedViewStatePage.cs (excerpt)&lt;br /&gt;&lt;br /&gt;using System;&lt;br /&gt;using System.IO.Compression;&lt;br /&gt;using System.IO;&lt;br /&gt;using System.Web.UI;&lt;br /&gt;public class CompressedViewStatePage : System.Web.UI.Page&lt;br /&gt;{&lt;br /&gt; static public byte[] Compress(byte[] b)&lt;br /&gt; {&lt;br /&gt;   MemoryStream ms = new MemoryStream();&lt;br /&gt;   GZipStream zs = new GZipStream(ms, CompressionMode.Compress);&lt;br /&gt;   zs.Write(b, 0, b.Length);&lt;br /&gt;   return ms.ToArray();&lt;br /&gt; }&lt;br /&gt; static public byte[] Decompress(byte[] b)&lt;br /&gt; {&lt;br /&gt;   MemoryStream ms = new MemoryStream(b.Length);&lt;br /&gt;   ms.Write(b, 0, b.Length);&lt;br /&gt;   // last 4 bytes of GZipStream = length of decompressed data&lt;br /&gt;   ms.Seek(-4, SeekOrigin.Current);&lt;br /&gt;   byte[] lb = new byte[4];&lt;br /&gt;   ms.Read(lb, 0, 4);&lt;br /&gt;   int len = BitConverter.ToInt32(lb, 0);&lt;br /&gt;   ms.Seek(0, SeekOrigin.Begin);&lt;br /&gt;   byte[] ob = new byte[len];&lt;br /&gt;   GZipStream zs = new GZipStream(ms, CompressionMode.Decompress);&lt;br /&gt;   zs.Read(ob, 0, len);&lt;br /&gt;   return ob;&lt;br /&gt; }&lt;br /&gt; protected override object LoadPageStateFromPersistenceMedium()&lt;br /&gt; {&lt;br /&gt;   byte[] b = Convert.FromBase64String(Request.Form["__VSTATE"]);&lt;br /&gt;   LosFormatter lf = new LosFormatter();&lt;br /&gt;   return lf.Deserialize(Convert.ToBase64String(Decompress(b)));&lt;br /&gt; }&lt;br /&gt; protected override void SavePageStateToPersistenceMedium(&lt;br /&gt;     object state&lt;br /&gt; )&lt;br /&gt; {&lt;br /&gt;   LosFormatter lf = new LosFormatter();&lt;br /&gt;   StringWriter sw = new StringWriter();&lt;br /&gt;   lf.Serialize(sw, state);&lt;br /&gt;   byte[] b = Convert.FromBase64String(sw.ToString());&lt;br /&gt;   ClientScript.RegisterHiddenField("__VSTATE",&lt;br /&gt;   Convert.ToBase64String(Compress(b)));&lt;br /&gt; }&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt; &lt;div id="adz" class="vertical"&gt;&lt;div class="ad"&gt;&lt;iframe src="http://om.1and1.com/postview/?ac=OM.US.USa09K16030H7072a" style="border: 1px hidden rgb(255, 255, 255);" name="sitepoint 1&amp;amp;1 Cookie" marginheight="1px" marginwidth="1px" scrolling="no" frameborder="0" height="1px" width="1px"&gt;&lt;/iframe&gt; &lt;div id="beacon_1020" style="position: absolute; left: 0px; top: 0px; visibility: hidden;"&gt;&lt;img src="http://ads.aws.sitepoint.com/phpadsnew/www/delivery/lg.php?bannerid=1020&amp;amp;campaignid=568&amp;amp;zoneid=82&amp;amp;loc=http%3A%2F%2Fads.aws.sitepoint.com%2Fadjs.php%3Fregion%3D82%26did%3Dadz%26adtype%3Dvertical&amp;amp;referer=http%3A%2F%2Fwww.sitepoint.com%2Farticle%2Faspnet-performance-tips%2F2%2F&amp;amp;cb=3008ea7124" alt="" style="width: 0px; height: 0px;" height="0" width="0" /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;To use GZIP compression, simply inherit a specific page from the class, like this:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;public partial class MyPage : CompressedViewStatePage&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;If you're worried about the performance implications of compressing your view state, don't be. It's far more likely that bandwidth is a greater bottleneck than CPU time on any given web server. Although there are exceptions to this rule, the GZIP algorithm is blazingly fast on the CPUs of today. Besides, if your server's CPU operates at 100% all the time, you have far graver problems to worry about than the size of a handful of pages.&lt;/p&gt;  &lt;p&gt;This compression algorithm could also be implemented as an HTTP module, which could then be applied to an entire site with a simple Web.config modification. I suggest you try implementing this module as an exercise, if you're keen. The &lt;a class="sublink" href="http://support.microsoft.com/kb/307996/en-us"&gt;MSDN article on building an HTTP module&lt;/a&gt; is a good place to start. &lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Storing View State on the Server&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;The second option for reducing view state's impact on page size is to prevent view state data from being sent to the client altogether, and instead store the data on the server.&lt;/p&gt;  &lt;p&gt;The following &lt;code&gt;ServerViewStatePage&lt;/code&gt; class allows us to use the Session object to store the view state:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;Example 15.4. ServerViewStatePage.cs (excerpt)&lt;br /&gt;&lt;br /&gt;using System;&lt;br /&gt;using System.Web.UI;&lt;br /&gt;using System.Configuration;&lt;br /&gt;using System.IO;&lt;br /&gt;public class ServerViewStatePage : System.Web.UI.Page&lt;br /&gt;{&lt;br /&gt; private const string _configKey = "ServerViewStateMode";&lt;br /&gt; private const string _formField = "__SERVERVIEWSTATEKEY";&lt;br /&gt; private string ViewStateData&lt;br /&gt; {&lt;br /&gt;   get { return Request.Form[_formField]; }&lt;br /&gt;   set { ClientScript.RegisterHiddenField(_formField, value); }&lt;br /&gt; }&lt;br /&gt; private string PersistenceType&lt;br /&gt; {&lt;br /&gt;   get { return (ConfigurationManager.AppSettings[_configKey]&lt;br /&gt;   ?? "").ToLower(); }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private object ToObject(string viewstate)&lt;br /&gt; {&lt;br /&gt;   byte[] b = Convert.FromBase64String(viewstate);&lt;br /&gt;   LosFormatter lf = new LosFormatter();&lt;br /&gt;   return lf.Deserialize(Convert.ToBase64String(b));&lt;br /&gt; }&lt;br /&gt; private string ToBase64String(object state)&lt;br /&gt; {&lt;br /&gt;   LosFormatter lf = new LosFormatter();&lt;br /&gt;   StringWriter sw = new StringWriter();&lt;br /&gt;   lf.Serialize(sw, state);&lt;br /&gt;   byte[] b = Convert.FromBase64String(sw.ToString());&lt;br /&gt;   return Convert.ToBase64String(b);&lt;br /&gt; }&lt;br /&gt; private string ToSession(string value)&lt;br /&gt; {&lt;br /&gt;   string key = Guid.NewGuid().ToString();&lt;br /&gt;   Session.Add(key, value);&lt;br /&gt;   return key;&lt;br /&gt; }&lt;br /&gt; private string FromSession(string key)&lt;br /&gt; {&lt;br /&gt;   string value = Convert.ToString(Session[key]);&lt;br /&gt;   Session.Remove(key);&lt;br /&gt;   return value;&lt;br /&gt; }&lt;br /&gt; protected override object LoadPageStateFromPersistenceMedium()&lt;br /&gt; {&lt;br /&gt;   switch (PersistenceType)&lt;br /&gt;   {&lt;br /&gt;   case "session":&lt;br /&gt;     return ToObject(FromSession(ViewStateData));&lt;br /&gt;   default:&lt;br /&gt;     return base.LoadPageStateFromPersistenceMedium();&lt;br /&gt;   }&lt;br /&gt; }&lt;br /&gt; protected override void SavePageStateToPersistenceMedium(&lt;br /&gt;     object ViewStateObject&lt;br /&gt; )&lt;br /&gt; {&lt;br /&gt;   switch (PersistenceType)&lt;br /&gt;   {&lt;br /&gt;     case "session":&lt;br /&gt;       ViewStateData = ToSession(ToBase64String(ViewStateObject));&lt;br /&gt;       break;&lt;br /&gt;     default:&lt;br /&gt;       base.SavePageStateToPersistenceMedium(ViewStateObject);&lt;br /&gt;       break;&lt;br /&gt;   }&lt;br /&gt; }&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;To use &lt;code&gt;ServerViewStatePage&lt;/code&gt;, simply inherit a specific page from this class, like this:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;public partial class MyPage : ServerViewStatePage&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;This class is configured via a single setting in &lt;code&gt;Web.config&lt;/code&gt;: &lt;code&gt;ServerViewStateMode&lt;/code&gt;. Once you've configured this setting, you'll notice that the &lt;code&gt;ViewState&lt;/code&gt; object disappears from the page -- in its place is a simple ID that's used to look up the contents of the page's view state on the server, in the &lt;code&gt;Session&lt;/code&gt; object. If you feel uncomfortable storing view state in &lt;code&gt;Session&lt;/code&gt;, this class could easily be extended to store view state wherever you like -- in the file system, in the ASP.NET cache, or in a database.&lt;/p&gt;  &lt;p&gt;As usual, there's no free lunch here. The decision to push view state to the server and store it in Session has its own drawbacks. For example, the Session object could be lost if the IIS worker process recycles (a loss that does occur every so often in IIS, unless you've disabled this default behavior). Furthermore, any change to the underlying application files (such as editing &lt;code&gt;Web.config&lt;/code&gt;, or adding new binaries to the bin folder of your application) will also cause the web application to recycle and Session data to be lost. And if you use more than one web server (such as in a web farm environment) you'll need to manage any shared session state that's stored in a database.&lt;/p&gt;&lt;p style="text-align: right;"&gt;&lt;span style="font-weight: bold;"&gt;www.sitepoint.com&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4004158710435644696-8793011593210265791?l=eswebsoft.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/8793011593210265791/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/speed-up-your-site-8-aspnet-performance_03.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/8793011593210265791'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/8793011593210265791'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/speed-up-your-site-8-aspnet-performance_03.html' title='Speed Up Your Site! 8 ASP.NET Performance Tips 2'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4004158710435644696.post-2477149082269776434</id><published>2009-08-03T12:36:00.002+07:00</published><updated>2009-08-03T12:49:22.530+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='asp.net'/><category scheme='http://www.blogger.com/atom/ns#' term='dotnet'/><title type='text'>Speed Up Your Site! 8 ASP.NET Performance Tips 1</title><content type='html'>&lt;p&gt;&lt;strong&gt;Now that you've added the finishing touches to your web site and unleashed it onto the world, fame, fortune, and success will surely follow -- won't it?&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Unfortunately, your web application's success can lead to something less desirable -- performance and scaling problems. On a traditional desktop application, one thousand users translate to one thousand client computers chugging away, sharing the load of running your application. The application is effectively spread among all the users' machines. When it comes to a web application, though, those same thousand users are usually serviced by a single machine -- your web server.&lt;/p&gt;  &lt;p&gt;Success can come at a cost for web applications: a cost in bandwidth and server hardware. However, there are a few clever ways you can reduce -- and sometimes eliminate -- these problems. We'll take a look at some of the different approaches to improving the performance of an ASP.NET site in this chapter, which has been extracted from &lt;a class="sublink" href="http://www.sitepoint.com/books/aspnetant1/"&gt;&lt;em&gt;The ASP.NET 2.0 Anthology, 101 Essential Tips, Tricks &amp;amp; Hacks&lt;/em&gt;&lt;/a&gt;. Feel free to download this chapter -- along with three others -- for offline reference.&lt;/p&gt;  &lt;h5&gt;How do I determine what to optimize?&lt;/h5&gt;    &lt;p&gt;You want your web application to be the best, right? Like all of us, by "best" you mean "fastest." And what better way to create a blazingly fast application than to optimize everything? Optimize, optimize, optimize -- right? Wrong.&lt;/p&gt;  &lt;p&gt;Premature optimization refers to the fixing of a performance problem before you understand the problem, or before there even is a problem, and it's a bad idea.&lt;/p&gt; &lt;div id="adz" class="vertical"&gt; &lt;p&gt;That's not to say that you should write sloppy, inefficient code. My point is that you should trust the ASP.NET Framework, and make use of the features that make it such a terrific environment in which to develop, until you hit a problem. Once you hit a problem, you should take the time to understand what that problem is, and only then should you start to look at how best to address it. &lt;a class="sublink" href="http://www.flounder.com/optimization.htm"&gt;Dr. Joseph M. Newcomer's essay, "Optimization: Your Worst Enemy,"&lt;/a&gt; gives a fascinating overview of the perils of optimizing in the dark. &lt;/p&gt;  &lt;p&gt;The tips in this chapter propose fairly lightweight solutions for some common performance issues. I've steered away from dramatic changes, because I don't want you to end up doubling your development or maintenance time in order to shave a meagre 2% off your page load time. While it is possible to bypass the in-built ASP.NET Page mechanism completely (by using Response.Write, or building an ASHX-based sites), I'm not a fan of these approaches. The ASP.NET system as a whole has been tuned and improved for more than half a decade, and it's reasonably fast straight out of the box. There's a good chance that trying to improve on it will result in a slower and less maintainable web site.&lt;/p&gt;  &lt;p&gt;So, with all that out of the way, let's assume that some of your pages are running slowly. How do you figure out what to fix?&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Solution&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Isolate the bottleneck in your site by measuring the actual speed of the site. This exercise can be performed using logs, database profiling, and tracing.&lt;/p&gt;  &lt;p&gt;We've discussed the task of logging using log4net in Chapter 13, Handling Errors. If you suspect that the database is the cause of the slowdown (for example, you know that your application makes use of some large queries), you can either step through the page in debug mode to see whether the database calls are taking a long time to return, or you can use the SQL Server Profiler discussed in the section called "How do I speed up my database queries?" later in this chapter. For a very basic analysis of what's going on in your pages, you can use the ASP.NET trace; while it's not nearly as good as a full-featured logging system, it's easy to implement and will provide you with plenty of timing information.&lt;/p&gt;  &lt;p&gt;The first step in using the trace is to get into the habit of adding trace statements. Write to the trace whenever you think there's something you might want to see when you debug future problems. Tracing doesn't have any real performance impact on your site until it's enabled in the &lt;code&gt;Web.config&lt;/code&gt;, and when you need to troubleshoot a problem, you'll be glad it's there.&lt;/p&gt;  &lt;p&gt;There's more information on tracing in Chapter 13, Handling Errors, but the general idea is that you can write to the Trace object like this:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;Trace.Write("Here's a trace message.");&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;Tracing is disabled by default; when you want your &lt;code&gt;Trace.Write&lt;/code&gt; statements to actually do something, you'll need to turn tracing on in the &lt;code&gt;Web.config&lt;/code&gt; file, as follows:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;Example 15.1. Web.config (excerpt)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;configuration&gt;&lt;br /&gt;&lt;system.web&gt;&lt;br /&gt; &lt;trace enabled="true" mostrecent="true" localonly="true"&gt;&lt;br /&gt;&lt;/trace&gt;&lt;br /&gt;&lt;/system.web&gt;&lt;/configuration&gt;&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;In this solution, we'll look at a sample page that performs a few different actions -- it makes a call to a web service, retrieves some data from a database, and throws an exception. Each function that we'll use writes a trace message when it begins and ends, via a straightforward utility method called &lt;code&gt;writeTrace&lt;/code&gt;. However, it has one slightly complex aspect -- it uses the System.Diagnostics object to figure out the method name, so we don't have to pass it in. The code for our sample page is as follows:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;Example 15.2. Trace.aspx.cs (excerpt)&lt;br /&gt;&lt;br /&gt;using System;&lt;br /&gt;using System.Web;&lt;br /&gt;public partial class _Default : System.Web.UI.Page&lt;br /&gt;{&lt;br /&gt;protected void Page_Load(object sender, EventArgs e)&lt;br /&gt;{&lt;br /&gt; hitAWebservice();&lt;br /&gt; getSomeData();&lt;br /&gt; doSomeProcessing();&lt;br /&gt; breakSomething();&lt;br /&gt; displayTheResults();&lt;br /&gt;}&lt;br /&gt;private void getSomeData()&lt;br /&gt;{&lt;br /&gt; writeTrace(true);&lt;br /&gt; simulateWaiting(8000);&lt;br /&gt; writeTrace(false);&lt;br /&gt;}&lt;br /&gt;private void hitAWebservice()&lt;br /&gt;{&lt;br /&gt; writeTrace(true);&lt;br /&gt; Trace.Write("A message to demonstrate tracing.");&lt;br /&gt; simulateWaiting(2000);&lt;br /&gt; writeTrace(false);&lt;br /&gt;}&lt;br /&gt;private void doSomeProcessing()&lt;br /&gt;{&lt;br /&gt; writeTrace(true);&lt;br /&gt; simulateWaiting(1000);&lt;br /&gt; writeTrace(false);&lt;br /&gt;}&lt;br /&gt;private void displayTheResults()&lt;br /&gt;{&lt;br /&gt; writeTrace(true);&lt;br /&gt; simulateWaiting(500);&lt;br /&gt; writeTrace(false);&lt;br /&gt;}&lt;br /&gt;private void breakSomething()&lt;br /&gt;{&lt;br /&gt; writeTrace(true);&lt;br /&gt; try&lt;br /&gt; {&lt;br /&gt;   int superBig = int.MaxValue;&lt;br /&gt;   superBig += 1;&lt;br /&gt; }&lt;br /&gt; catch (Exception ex)&lt;br /&gt; {&lt;br /&gt;   Trace.Warn("Exception", "Oops", ex);&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;private void writeTrace(bool enteringFunction)&lt;br /&gt;{&lt;br /&gt; if (!Trace.IsEnabled)&lt;br /&gt; return;&lt;br /&gt; string callingFunctionName = "Undetermined method";&lt;br /&gt; string action = enteringFunction ? "Entering" : "Exiting";&lt;br /&gt; try&lt;br /&gt; {&lt;br /&gt;   //Determine the name of the calling function.&lt;br /&gt;   System.Diagnostics.StackTrace stackTrace =&lt;br /&gt;   new System.Diagnostics.StackTrace();&lt;br /&gt;   callingFunctionName =&lt;br /&gt;   stackTrace.GetFrame(1).GetMethod().Name;&lt;br /&gt; }&lt;br /&gt; catch { }&lt;br /&gt; Trace.Write(action, callingFunctionName);&lt;br /&gt;}&lt;br /&gt;/// &lt;summary&gt;&lt;br /&gt;/// Wait a bit.&lt;br /&gt;/// &lt;/summary&gt;&lt;br /&gt;/// &lt;param name="waitTime"&gt;Time in milliseconds to wait.&lt;br /&gt;private void simulateWaiting(int waitTime)&lt;br /&gt;{&lt;br /&gt; System.Threading.Thread.Sleep(waitTime);&lt;br /&gt;}&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;Right, we've got our trace statements in place. Now, let's assume that this page is taking abnormally long to load, and we'd like to get to the bottom of the problem. With tracing enabled, we can simply load the page, then browse to &lt;code&gt;Trace.axd&lt;/code&gt; within our web site; it's at &lt;code&gt;http://localhost:1209/MySite/Trace.axd&lt;/code&gt;.&lt;/p&gt;  &lt;p&gt;Figure 15.1 shows the first part of the Trace.axd output that was returned from the previous code.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/performance_trace-output.thumb.png" class="beatbox"&gt;&lt;em&gt;Figure 15.1. Trace output for our sample page (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/performance_trace-output.thumb.png" alt="" height="282" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;Table 15.1 shows the relevant portion of the trace output.&lt;/p&gt;  &lt;p&gt;Right away, we can see which aspect of our page load is taking the majority of time -- &lt;code&gt;getSomeData&lt;/code&gt; takes eight seconds to execute. Without this information, we might have assumed the web service call was at fault and spent valuable time solving the wrong problem. This example shows how, armed with some real information, we can begin to fix the right problem.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/1584_table.thumb.png" class="beatbox"&gt;&lt;em&gt;Snapshot of Trace Output for our Sample Page (click to view image)&lt;/em&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://i2.sitepoint.com/graphics/1584_table.thumb.png" class="beatbox"&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/1584_table.thumb.png" alt="" height="211" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;script src="http://ads.aws.sitepoint.com/adjs.php?region=82&amp;amp;did=adz&amp;amp;adtype=vertical" type="text/javascript"&gt;&lt;/script&gt;       &lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;p align="right"&gt;&lt;span style="font-weight: bold;"&gt;www.sitepoint.com&lt;/span&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4004158710435644696-2477149082269776434?l=eswebsoft.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/2477149082269776434/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/speed-up-your-site-8-aspnet-performance.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/2477149082269776434'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/2477149082269776434'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/speed-up-your-site-8-aspnet-performance.html' title='Speed Up Your Site! 8 ASP.NET Performance Tips 1'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4004158710435644696.post-1579382246906776154</id><published>2009-08-03T12:30:00.003+07:00</published><updated>2009-08-03T12:34:34.096+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='design tool'/><title type='text'>16 Design Tools for Prototyping and Wireframing</title><content type='html'>&lt;p&gt;&lt;strong&gt;&lt;strong&gt;In recent years the number of tools available to help you document and design your web site has just exploded. It seems that we all need a wireframing or prototyping tool at our fingertips (at least in the design arena). So in order to save you the hard work required to find one, I've assembled this list. It can be expanded upon, so if you use an unlisted application, please let me know and I’ll add it to the list.&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;The tools tend to fall into two categories:&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;purpose-built applications&lt;/li&gt;&lt;li&gt;multifunctional applications&lt;/li&gt;&lt;/ul&gt;   &lt;p&gt;Within these two groups I’ve found that usefulness can vary markedly among tools; some are only suitable for diagramming and wireframing, while others focus only on prototyping. The best are blessed with both capabilities and more.&lt;/p&gt;  &lt;h4&gt;Purpose-built Applications&lt;/h4&gt;    &lt;h5&gt;1. Visio&lt;/h5&gt;    &lt;p&gt;I will state upfront that I’m not a big fan of &lt;a class="sublink" href="http://office.microsoft.com/en-us/visio/default.aspx"&gt;Visio&lt;/a&gt; (price: from $US259, demo available); I’ve used it from time to time on various projects, but I’ve always found it fiddly and time-consuming.&lt;/p&gt;  &lt;p&gt;Visio first started as an add-on to MS Word, filling the need for a business and technical diagramming tool and eventually graduating to separate product status. Visio’s power remains in the area of diagrammatical documentation; as a prototyping tool its functionality is limited at best. However Visio, like Omigraffle (below), is particularly suited to &lt;a class="sublink" href="http://www.boxesandarrows.com/view/the_lazy_ia_s_guide_to_making_sitemaps"&gt;content map generation from CSV files&lt;/a&gt;. In the recent version, Visio 2007, the addition of the UML plugin has allowed for smoother importing of UML.&lt;/p&gt; &lt;div id="adz" class="vertical"&gt;&lt;div class="ad"&gt;&lt;iframe src="http://om.1and1.com/postview/?ac=OM.US.USa09K16030H7072a" style="border: 1px hidden rgb(255, 255, 255);" name="sitepoint 1&amp;amp;1 Cookie" marginheight="1px" marginwidth="1px" scrolling="no" frameborder="0" height="1px" width="1px"&gt;&lt;/iframe&gt; &lt;div id="beacon_969" style="position: absolute; left: 0px; top: 0px; visibility: hidden;"&gt;&lt;img src="http://ads.aws.sitepoint.com/phpadsnew/www/delivery/lg.php?bannerid=969&amp;amp;campaignid=568&amp;amp;zoneid=74&amp;amp;loc=http%3A%2F%2Fads.aws.sitepoint.com%2Fadjs.php%3Fregion%3D74%26did%3Dadz%26adtype%3Dvertical&amp;amp;referer=http%3A%2F%2Fwww.sitepoint.com%2Farticle%2Ftools-prototyping-wireframing%2F&amp;amp;cb=9f8254c592" alt="" style="width: 0px; height: 0px;" height="0" width="0" /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Visio has also spawned a number of add-on tools; &lt;a class="sublink" href="http://swipr.com/"&gt;Swipr&lt;/a&gt; (which is free) is the most relevant as it allows for rapid export of wireframes and screen flows into a clickable HTML prototype. This is very handy for creating the entire prototype in one go and not having to link it together after exporting.&lt;/p&gt;  &lt;p&gt;Visio is only available on the Windows platform.&lt;/p&gt;  &lt;h5&gt;2. OmniGraffle Pro&lt;/h5&gt;    &lt;p&gt;Yes, I’m a little biased here; I do like &lt;a class="sublink" href="http://www.omnigroup.com/applications/OmniGraffle/"&gt;OmniGraffle&lt;/a&gt; (price: $US199, demo available), especially the latest version. OmniGraffle is best suited as a tool for wireframes, screen flows, and content maps. It can also be used as a prototyping tool, with the ability to link canvases (pages). This allows you to produce a complete HTML prototype in one operation from OmniGraffle. You can, via the use of the notes function, easily annotate and complete the specification documentation for your prototype objects as you go.&lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/1714_omigraffle.thumb.jpg" class="beatbox"&gt;&lt;em&gt;OmniGraffle Pro (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/1714_omigraffle.thumb.jpg" alt="" height="295" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;OmniGraffle provides an extensive series of Visio import and export functions, allowing for cross-platform team compatibility. It also allows you to import your base content structure from a CSV or XML file via a fully customizable rule-based layout function; this can be especially handy for documenting large and ever changing content maps.&lt;/p&gt;  &lt;p&gt;Support for OmniGraffle is supplemented by a large online community, as is evidenced by the resources available at &lt;a class="sublink" href="http://www.graffletopia.com/"&gt;Graffletopia&lt;/a&gt;. OmniGraffle is only available for the Mac.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;h5&gt;3. Axure RP Pro&lt;/h5&gt;    &lt;p&gt;&lt;a class="sublink" href="http://www.axure.com/"&gt;Axure&lt;/a&gt; (price: $US589, demo available) has rapidly become the darling of the user experience and information architecture communities. The application allows you to construct wireframe models, document functional specifications, and generate prototypes, all using a built-in version control system. &lt;/p&gt;  &lt;p&gt;Axure is an extremely rapid wireframing and prototype generation system that I’ve personally found pays for itself very quickly—in terms of increased productivity—despite its mid-level price tag. &lt;/p&gt;  &lt;div style="overflow: hidden;"&gt;&lt;a href="http://i2.sitepoint.com/graphics/1714_axure.thumb.jpg" class="beatbox"&gt;&lt;em&gt;Axure RP Pro (click to view image)&lt;/em&gt;&lt;img style="float: left;" src="http://i2.sitepoint.com/graphics/1714_axure.thumb.jpg" alt="" height="297" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;  &lt;p&gt;Axure allows for complete flexibility when designing an interface, from standard widgets, to custom builds, to an open-ended canvas. However, Axure’s true killer functionality is the generation of rich HTML prototypes and Ajax-like interactions between states. It’s a little like Dreamweaver and its JavaScript generation, but good.&lt;/p&gt;  &lt;p&gt;A word of caution: if you do go looking under the hood of your HTML prototype, the code it generates is not for the fainthearted and should never be considered for use beyond a testing prototype. &lt;/p&gt;  &lt;p&gt;The one downside for me is that it only runs on Windows. If it was available on the Mac, I would be very happy with Axure. &lt;/p&gt;  &lt;h5&gt;4. iRise Pro&lt;/h5&gt;    &lt;p&gt;&lt;a class="sublink" href="http://www.irise.com/"&gt;iRise&lt;/a&gt; (price: from $US6,995, demo available) was the first product of its type to market, and has advantages and disadvantages. The iRise product suite is very Axure-like in its functionality, and as the price suggests, is clearly geared towards an enterprise-level market. &lt;/p&gt;  &lt;p&gt;Price aside, the one thing that I dislike about iRise is the user interface. Now this may be a personal preference, but you would think that if you’re going to be producing a tool for user experience professionals that you would at least make sure the UI is right. iRise suffers from a legacy interface that uses older GUI methods and techniques. I believe it’s in real need of an overhaul. &lt;/p&gt;  &lt;p&gt;Still, if you’re looking at working with very large teams and need a comprehensive suite of products to span your entire prototype development life cycle, then iRise is worth a look. &lt;/p&gt;  &lt;h5&gt;5. Pencil &lt;/h5&gt;    &lt;p&gt;&lt;a class="sublink" href="http://www.evolus.vn/Pencil/"&gt;Pencil&lt;/a&gt; (free) is a Firefox plugin that professes to enable you to build wireframes and prototypes. As a prototyping tool it’s quite good, allowing you to quickly put together a reasonably high fidelity mockup. However, be aware you’ll still need to produce the visual design elements for Pencil, as it relies on dragging and dropping pre-made graphical elements.&lt;/p&gt;  &lt;p&gt;The output wireframe elements that ship with Pencil do tend to be based on the look and feel of a Windows desktop application. This really is undesirable for a web application, however you could modify this with your own page elements. &lt;/p&gt;  &lt;p&gt;Another downside of Pencil is that its export functionality provides only a few image formats. This means that Pencil falls short of being a real interactive prototype development tool. &lt;/p&gt;  &lt;h5&gt;6. SmartDraw&lt;/h5&gt;    &lt;p&gt;&lt;a class="sublink" href="http://www.smartdraw.com/"&gt;SmartDraw&lt;/a&gt; (price: from $US297, demo available) is marketed at the business diagramming sector, it’s not really a dedicated wireframing and prototyping application, but that doesn’t mean that it should be dismissed completely.&lt;/p&gt;  &lt;p&gt;It’s at the lower end of the market so you would expect a reduced functionality set compared to Axure. SmartDraw is aimed at the same market as Visio or OmniGraffle, with a standard template suite and few auto generation features. For prototyping SmartDraw allows you to add simple dynamic elements to a page, with the final output being a PDF file. There is no allowance for the representation of the transition between states on prototypes. &lt;/p&gt;  &lt;p&gt;SmartDraw is only available for Windows, offering a degree of MS-Office Suite integration. &lt;/p&gt;  &lt;h5&gt;7. MockupScreens&lt;/h5&gt;    &lt;p&gt;&lt;a class="sublink" href="http://mockupscreens.com/"&gt;MockupScreens&lt;/a&gt; (price: from $US79, demo available) is a Windows-only application. It focuses primarily on the building of simple prototypes from a series of wireframes based around common screen elements.&lt;/p&gt;  &lt;p&gt;The prototyping functionality is limited, and only available within the MockupScreens application. I do note that you can export the screens as image files or as a very limited HTML rendering. This product is very much at the bottom end of the market in terms of cost and functionality, however this may suit your needs.&lt;/p&gt;&lt;h5&gt;8. Balsamiq Mockups&lt;/h5&gt;    &lt;p&gt;&lt;a class="sublink" href="http://www.balsamiq.com/products/mockups"&gt;Balsamiq Mockups&lt;/a&gt; (price:$US79, demo available) is an interesting product running on Adobe AIR. Its representation of the interface elements has a refreshing hand-drawn, sketch-like quality to them. This does help promote the degree of changeability of the wireframes, as they look very much like a draft. If you lack the skills to create hand-drawn sketches then Balsamiq is a useful tool, as it allows you to produce quality roughs. Balsamiq also offers a standard collection of interactive screen elements, which is helpful to start off with. &lt;/p&gt;  &lt;p&gt;&lt;img src="http://i2.sitepoint.com/graphics/1714_balsamiq.thumb.jpg" alt="Balsamiq Mockups" height="265" width="400" /&gt;&lt;/p&gt; &lt;div id="adz" class="vertical"&gt;&lt;div class="ad"&gt;&lt;iframe src="http://om.1and1.com/postview/?ac=OM.US.USa09K16030H7072a" style="border: 1px hidden rgb(255, 255, 255);" name="sitepoint 1&amp;amp;1 Cookie" marginheight="1px" marginwidth="1px" scrolling="no" frameborder="0" height="1px" width="1px"&gt;&lt;/iframe&gt; &lt;div id="beacon_969" style="position: absolute; left: 0px; top: 0px; visibility: hidden;"&gt;&lt;img src="http://ads.aws.sitepoint.com/phpadsnew/www/delivery/lg.php?bannerid=969&amp;amp;campaignid=568&amp;amp;zoneid=74&amp;amp;loc=http%3A%2F%2Fads.aws.sitepoint.com%2Fadjs.php%3Fregion%3D74%26did%3Dadz%26adtype%3Dvertical&amp;amp;referer=http%3A%2F%2Fwww.sitepoint.com%2Farticle%2Ftools-prototyping-wireframing%2F2%2F&amp;amp;cb=ad42c33744" alt="" style="width: 0px; height: 0px;" height="0" width="0" /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Balsamiq Mockups also introduces BMML (a flavor of XML), which enables you to export your wireframes.  &lt;/p&gt;  &lt;p&gt;However rather than a prototyping tool, it’s more targeted at communicating the concepts of a proposed interface via wireframing and the like.&lt;/p&gt;  &lt;h5&gt;9. Lucid Spec&lt;/h5&gt;    &lt;p&gt;&lt;a class="sublink" href="http://www.elegancetech.com/ls/ls.aspx"&gt;Lucid Spec&lt;/a&gt; (price: from $US499, demo available) from Elegance Technologies is a windows-based .NET application that’s almost a clone of Axure. It has a series of rapid screen drawing tools and a prototyping testing mode they call “instant application simulation,” which can be viewed in full screen mode or within the application. There’s even a stand-alone Lucid Spec Player that allows you to conduct prototype testing without the need for the full application. &lt;/p&gt;  &lt;p&gt;The one aspect that Lucid Spec lacks is allowance for those between state, Ajax-like interactions. The interactions presented are very much based on the traditional, page-by-page screen flow. &lt;/p&gt;  &lt;h5&gt;10. ConceptDraw Pro&lt;/h5&gt;    &lt;p&gt;&lt;a class="sublink" href="http://www.conceptdraw.com/en/"&gt;ConceptDraw Pro&lt;/a&gt; (price: from $US249, demo available) promotes itself as an alternative to OmniGraffle and Visio, aiming at the business and technical diagramming market. &lt;/p&gt;  &lt;p&gt;I’m pleased to say it does live up to these claims with various functions that are comparable to its competitors. &lt;/p&gt;  &lt;p&gt;In addition to this it also has its own scripting language, ConceptDraw Basic. It’s possible with this scripting language to generate complex, customized prototypes. If you want to avoid going down the custom scripting route, you can use the standard export to HTML or PDF for prototyping. &lt;/p&gt;  &lt;p&gt;ConceptDraw also has a Web Design plugin for us web designers, WebWave Plugin (price: from $US99, demo available). The WebWave Plugin streamlines the design process, allowing easier development of site and content structures to the mocking up of medium resolution wireframes—a bit like coloring in the gray boxes. You can then take the final mockup pages from ConceptDraw and produce the CSS and pages for a prototype. &lt;/p&gt;  &lt;p&gt;ConceptDraw Pro is available for both Windows and Mac platforms.&lt;/p&gt;  &lt;h5&gt;11. iPlotz&lt;/h5&gt;    &lt;p&gt;&lt;a class="sublink" href="http://iplotz.com/"&gt;iPlotz&lt;/a&gt; (price: free to $US99 per year) is a new web application aimed at the design and developmental life cycle market. It bundles project management functionality as well as wireframing, collaborative commenting, and testing prototype generation. Being browser-based this application can be used anywhere there is web access, allowing for true remote collaborative design and implementation. &lt;/p&gt;  &lt;p&gt;Visually, iPlotz presents itself as an attractive and professional application. It’s a Flex application, and hence all the features from management to preview are accessed via the Flash interface, and so will require the latest Flash plugin, an easy ask for a designer or developer.&lt;/p&gt;  &lt;p&gt;&lt;img src="http://i2.sitepoint.com/graphics/1714_iplotz.thumb.jpg" alt="iPlotz" height="272" width="400" /&gt;&lt;/p&gt;  &lt;p&gt;The rapid generation of wireframes within this application reminds me of the speed and ease of use of Axure, but with the hand-sketched look of Balsamiq Mockups. Another nice thing about iPlotz is that its focus is just on web design, nothing else. This is a pleasant change.&lt;/p&gt;  &lt;p&gt;It’s early days for iPlotz, however there are a few items that do need addressing. The commenting interface can be a little distracting; the comments tend to crowd the screen if you have more than a few. There is only one font face available—comic sans. Also, at present the export functionality is limited to JPG, PNG, or a PDF. Still, this application is definitely one to watch. &lt;/p&gt;  &lt;h5&gt;12. Go Analogue &lt;/h5&gt;    &lt;p&gt;There is a lot to be said for stepping away from the computer and doing the initial wireframing with pencil and paper. You could extend this to paper prototyping as well. It’s fast (to build), cheap, and easy to use. For groups you can prototype using a large whiteboard, markers, and a few magnetic page elements. Both these methods tend to be highly interactive with the audience and can bring a moderate degree of humor to an otherwise dry process.&lt;/p&gt;  &lt;p&gt;There are downsides to these analogue methods as it can be slow to produce and revise the screens, as well as being easily disregarded by management because of the disposable nature of the final product. I have found that they’re useful for the generation process, but the final documentation needs to be presented in a formal tool. &lt;/p&gt;  &lt;h4&gt;Multifunctional Applications &lt;/h4&gt;    &lt;h5&gt;13. PowerPoint or Keynote &lt;/h5&gt;    &lt;p&gt;Often an organization is unable to afford specialized software for prototyping or wireframing. This is where applications like &lt;a class="sublink" href="http://office.microsoft.com/en-us/powerpoint/default.aspx"&gt;Microsoft PowerPoint&lt;/a&gt; (price: from $US229, demo available) or &lt;a class="sublink" href="http://www.apple.com/iwork/keynote/"&gt;Apple Keynote&lt;/a&gt; (price: from $US79, with iWorks, demo available) can be used. Both these packages are well-suited to producing very rapid, simple prototypes. They’re highly compatible with the wide range of most teams’ skill set, so by using these tools you can usually have any team members immediately contributing to a prototype with ease. The downside is many of the finer interactive elements, such as between states, are missing, and your logic path tends to be of a very linear nature. Still, you can often export the prototype to HTML or Flash as well.&lt;/p&gt;  &lt;h5&gt;14. Adobe Fireworks&lt;/h5&gt;    &lt;p&gt;&lt;a class="sublink" href="http://www.adobe.com/products/fireworks/"&gt;Adobe Fireworks&lt;/a&gt; (price: from $US299, demo available) is a tool I’d yet to consider using for prototyping—being a Photoshop devotee from way back. However, all this has changed with the release of Adobe CS4. &lt;/p&gt;  &lt;p&gt;Fireworks is now shaping up to be a viable tool for producing both low and high fidelity prototypes, using nested symbols, smart guides, as well as generating all the code behind it. As usual this code suffers a little from the automatic generation process, so it’s going to need to be massaged into a reasonable shape if you want to reuse it for your final production model. &lt;/p&gt;  &lt;p&gt;A side note here is that I know of others also using Adobe Photoshop and Illustrator, or Microsoft Expression Studio as wireframe documentation tools. These tend to work well when you have a good library of objects, such as brushes and the like. &lt;/p&gt;  &lt;h5&gt;15. HTML&lt;/h5&gt;    &lt;p&gt;If you have the skills, you could just &lt;a class="sublink" href="http://www.boxesandarrows.com/view/prototyping-with"&gt;develop your prototypes in HTML&lt;/a&gt;. This is especially useful if you are producing the front-end code that’s going to be consistent in standards with the final web site anyway. Doing it this way will save you time and money by reducing the overall production time. &lt;/p&gt;  &lt;p&gt;These days—with the use of various jQuery plugins—you can represent nearly all the between states or major interface types that have been proposed with ease. &lt;/p&gt;  &lt;p&gt;Functionality is also available via the use of &lt;a class="sublink" href="http://www.protonotes.com/"&gt;Protonotes&lt;/a&gt;; you can have your team collaborate on your prototype by leaving dynamic, virtual Post-it like notes. &lt;/p&gt;  &lt;p&gt;Even if your skill set falls short of being technically-oriented, you could use developmental platforms like &lt;a class="sublink" href="http://www.sitepoint.com/newsletter/viewissue.php?id=3&amp;amp;issue=218&amp;amp;format=html#5"&gt;Adobe Dreamweaver and its improved JavaScript Spry components&lt;/a&gt; to produce a reasonable visual representation. &lt;/p&gt;  &lt;p&gt;I will admit I have often done this, quickly pulling together a working HTML prototype from within Dreamweaver with little regard to the underlying code. Remember, it’s just a prototype and so only has to be visually representative of the final product. I shudder to say this, but in this case the code is secondary.&lt;/p&gt;  &lt;h5&gt;16. Adobe Flash&lt;/h5&gt;    &lt;p&gt;&lt;a class="sublink" href="http://www.adobe.com/products/flash/"&gt;Adobe Flash&lt;/a&gt; (price: from $US699, demo available) is a great tool for producing &lt;a class="sublink" href="http://skyrize.com/"&gt;rapid interactive prototypes&lt;/a&gt; or screen flows, but is unsuitable for wireframing and specification documentation. However, via the use of the library objects, the timeline, and drawing tools it’s &lt;a class="sublink" href="http://www.boxesandarrows.com/view/quick-and-easy-flash"&gt;very easy&lt;/a&gt; to produce an interactive prototype. The one area Flash is especially suited is in the reproduction of those in-between states that many static presentation tools lack.&lt;/p&gt;  &lt;p&gt;This functionality of using Flash as a prototyping tool is now being extended by Adobe with the introduction of an entirely new tool for designers and developers alike: &lt;a class="sublink" href="http://tv.adobe.com/#vi+f15384v1003"&gt;Catalyst&lt;/a&gt;. Now I’ve yet to try Catalyst personally—I can only go off the Adobe video and the reports from colleagues that have seen it in operation. But trust me here, this application is going to change the marketplace and make RIA development the next big thing! There, I said it; some of you will disagree, and that’s fine. &lt;/p&gt;  &lt;p&gt;Another thing that Catalyst is promising for people like me that use a Mac, is that it will give us the jump on our colleagues using Axure. We shall see on that one.&lt;/p&gt;&lt;p style="text-align: right;"&gt;&lt;span style="font-weight: bold;"&gt;www.sitepoint.com&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4004158710435644696-1579382246906776154?l=eswebsoft.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/1579382246906776154/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/16-design-tools-for-prototyping-and.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/1579382246906776154'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/1579382246906776154'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/16-design-tools-for-prototyping-and.html' title='16 Design Tools for Prototyping and Wireframing'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4004158710435644696.post-414755280082157880</id><published>2009-08-02T22:40:00.000+07:00</published><updated>2009-08-02T23:19:48.907+07:00</updated><title type='text'>Learn Apache mod_rewrite: 13 Real-world Examples</title><content type='html'>&lt;strong&gt;Apache's low-cost, powerful set of features make it the server of choice for organizations around the world. One of its most valuable treasures is the &lt;a href="http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html"&gt;mod_rewrite module&lt;/a&gt;, the purpose of which is to rewrite a visitor's request URI in the manner specified by a set of rules.&lt;a name='more'&gt;&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;This article will lead you through rewrite rules, regular expressions, and rewrite conditions, and provide a great list of examples.&lt;br /&gt;&lt;br /&gt;First off, I'm going to assume that you understand the common reasons for wanting a URI rewriting feature for your web site. If you'd like information about this field, there's a good primer in the SitePoint article, &lt;a href="http://www.sitepoint.com/article/guide-url-rewriting/"&gt;mod_rewrite: A Beginner's Guide to URL Rewriting&lt;/a&gt;. There, you'll also find instructions on how to enable it on your own server.&lt;br /&gt;&lt;h5&gt;Testing Your Server Setup&lt;/h5&gt;&lt;br /&gt;Some hosts do not have mod_rewrite enabled (by default it is not enabled). You can find out if your server has mod_rewrite enabled by creating a PHP script with one simple line of PHP code:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;phpinfo();&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;If you load the script with a browser, look in the Apache Modules section. If mod_rewrite isn't listed there, you'll have to ask your host to enable it -- or find a "good host". Most hosts will have it enabled, so you'll be good to go.&lt;br /&gt;&lt;h5&gt;The Magic of mod_rewrite&lt;/h5&gt;&lt;br /&gt;Here's a simple example for you: create three text files named &lt;code&gt;test.html&lt;/code&gt;, &lt;code&gt;test.php&lt;/code&gt;, and &lt;code&gt;.htaccess&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;In the &lt;code&gt;test.html&lt;/code&gt; file, enter the following:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&amp;lt;h1&amp;gt;This is the HTML file.&amp;lt;/h1&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;In the &lt;code&gt;test.php&lt;/code&gt; file, add this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&amp;lt;h1&amp;gt;This is the PHP file.&amp;lt;/h1&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Create the third file, &lt;code&gt;.htaccess&lt;/code&gt;, with the following:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteEngine on&lt;br /&gt;RewriteRule ^/?test\.html$ test.php [L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Upload all three files (in ASCII mode) to a directory on your server, and type:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;http://www.example.com/path/to/test.html&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;into the location box -- using your own domain and directory path of course! If the page shows "This is the PHP file", it's working properly! If it shows "This is the HTML file," something's gone wrong.&lt;br /&gt;&lt;br /&gt;If your test worked, you'll notice that the &lt;code&gt;test.html&lt;/code&gt; URI has remained in the browser's location box, yet we've seen the contents of the &lt;code&gt;test.php&lt;/code&gt; file. You've just witnessed the magic of mod_rewrite!&lt;br /&gt;&lt;h5&gt;mod-rewrite Regular Expressions&lt;/h5&gt;&lt;br /&gt;Now we can begin rewriting your URIs! Let's imagine we have a web site that displays city information. The city is selected via the URI like this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;http://www.example.com/display.php?country=USA&amp;amp;state=California&amp;amp;city=San_Diego&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Our problem is that this is way too long an unfriendly to users. We'd much prefer it if visitors could use:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;http://www.example.com/USA/California/San_Diego&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;We need to be able to tell Apache to rewrite the latter URI into the former. In order for the &lt;code&gt;display.php&lt;/code&gt; script to read and parse the query string, we'll need to use regular expressions to tell mod_rewrite how to match the two URIs. If you're not familiar with regular expressions (regex), many sites provide excellent tutorials. At the end of this article, I've listed the best pages I've found on the topic. If you're not able to follow my explanations, I recommend reviewing the first two of those links.&lt;br /&gt;&lt;br /&gt;A very common approach is to use the expression &lt;code&gt;(.*)&lt;/code&gt;. This expression combines two metacharacters: the dot character, which means ANY character, and the asterisk character, which specifies zero or more of the preceding character. Thus, &lt;code&gt;(.*)&lt;/code&gt; matches everything in the &lt;code&gt;{REQUEST_URI}&lt;/code&gt; string. &lt;code&gt;{REQUEST_URI}&lt;/code&gt; is that part of the URI which follows the domain up to but not including the &lt;code&gt;?&lt;/code&gt; character of a query string, and is the only Apache variable that a rewrite rule attempts to match.&lt;br /&gt;&lt;br /&gt;Wrapping the expression in brackets stores it in an "atom," which is a variable that allows the matched characters to be reused within the rule. Thus, the expression above would store USA/California/San_Diego in the atom. To solve our problem, we'd need three of these atoms, separated by the subdirectory slashes (&lt;code&gt;/&lt;/code&gt;), so the regex would become:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;(.*)/(.*)/(.*)&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Given the above expression, the regex engine will match (and save) three values separated by two slashes anywhere in the &lt;code&gt;{REQUEST_URI}&lt;/code&gt; string. To solve our specific problem, though, we'll need to restrict this somewhat -- after all, the first and last atoms above could match anything!&lt;br /&gt;&lt;br /&gt;To begin with, we can add the start and end anchor characters. The &lt;code&gt;^&lt;/code&gt; character matches matching characters at the start of a string, and the &lt;code&gt;$&lt;/code&gt; character matches characters at the end of a string.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;^(.*)/(.*)/(.*)$&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This expression specifies that the whole string must be matched by our regex; there cannot be anything else before or after it.&lt;br /&gt;&lt;br /&gt;However, this approach still allows too many matches. We're storing our matches as atoms, and will be passing them to a query string, so we have to be able to trust what we match. Matching anything with &lt;code&gt;(.*)&lt;/code&gt; is too much of a potential security hazard, and, when used inappropriately, could even cause mod_rewrite to get stuck in a loop!&lt;br /&gt;&lt;br /&gt;To avoid unnecessary problems, let's change the atoms to specify precisely the characters that we will allow. Because the atoms represent location names, we should limit the matched characters to upper and lowercase letters from A to Z, and because we use it to represent spaces in the name, the underscore character (&lt;code&gt;_&lt;/code&gt;) should also be allowed. We specify a set using square brackets, and a range using the &lt;code&gt;-&lt;/code&gt; character. So the set of allowed characters is written as &lt;code&gt;[a-zA-Z_]&lt;/code&gt;. And because we want to avoid matching blank names, we add the &lt;code&gt;+&lt;/code&gt; metacharacter, which specifies a match only on one or more of the preceding character. Thus, our regex is now:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;^([a-zA-Z_]+)/([a-zA-Z_]+)/([a-zA-Z_]+)$&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;{REQUEST_URI}&lt;/code&gt; string starts with a &lt;code&gt;/&lt;/code&gt; character. Apache changed regex engines when it changed versions, so Apache version 1 requires the leading slash while Apache 2 forbids it! We can satisfy both versions by making the leading slash optional with the expression &lt;code&gt;^/?&lt;/code&gt; (&lt;code&gt;?&lt;/code&gt; is the metacharacter for zero or one of the preceding character). So now we have:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;^/?([a-zA-Z_]+)/([a-zA-Z_]+)/([a-zA-Z_]+)$&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;With regex in hand, we can now map the atoms to the query string:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;display.php?country=$1&amp;amp;state=$2&amp;amp;city=$3&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;$1&lt;/code&gt; is the first (country) atom, &lt;code&gt;$2&lt;/code&gt; is the second (state) atom and &lt;code&gt;$3&lt;/code&gt; is the third (city) atom. Note that there can only be nine atoms created, in the order in which the opening brackets appear -- &lt;code&gt;$1 ... $9&lt;/code&gt; in a regular expression.&lt;br /&gt;&lt;br /&gt;We're almost there! Create a new &lt;code&gt;.htaccess&lt;/code&gt; file with the text:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteRule ^/?([a-zA-Z_]+)/([a-zA-Z_]+)/([a-zA-Z_]+)$ display.php?country=$1&amp;amp;state=$2&amp;amp;city=$3 [L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Save this to the directory in which &lt;code&gt;display.php&lt;/code&gt; resides. The rewrite rule must go on one line with one space between the &lt;code&gt;RewriteRule&lt;/code&gt; statement, the regex, and the redirection (and before any optional flags). We've used the &lt;code&gt;[L]&lt;/code&gt;, or 'last' flag, which is the terminating flag (more on flags later).&lt;br /&gt;&lt;br /&gt;Our rewrite rule is now complete! The atom values are being extracted from the request string and added to the query string of our rewritten URI. The &lt;code&gt;display.php&lt;/code&gt; script will likely extract these values from the query string and use them in a database query or something similar.&lt;br /&gt;&lt;br /&gt;If, however, you have only a short list of allowable countries, it might be best to avoid potential database problems by specifying the acceptable values within the regex. Here's an example:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;^/?(USA|Canada|Mexico)/([a-zA-Z_]+)/([a-zA-Z_]+)$&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;If you're concerned about capitalization because the values in your database are strictly lowercase, you can make the regex engine ignore the case by adding the No Case flag, &lt;code&gt;[NC]&lt;/code&gt;, after the rewritten URI. Just don't forget to convert the values to lowercase in your script after you obtain the &lt;code&gt;$_GET&lt;/code&gt; array.&lt;br /&gt;&lt;br /&gt;If you want to use numbers (0, 1, ... 9) for, say, Congressional Districts, then you'll need to change an atom's specification from (&lt;code&gt;[a-zA-Z_]+&lt;/code&gt;) to (&lt;code&gt;[0-9]&lt;/code&gt;) to match a single digit, (&lt;code&gt;[0-9]{1,2}&lt;/code&gt;) to match one or two digits (0 through 99), or (&lt;code&gt;[0-9]+&lt;/code&gt;) for one or more digits, which is useful for database IDs.&lt;br /&gt;&lt;h5&gt;The &lt;code&gt;RewriteCond&lt;/code&gt; Statement&lt;/h5&gt;&lt;br /&gt;Now that you've learned how to use mod_rewrite's basic &lt;code&gt;RewriteRule&lt;/code&gt; statement with the &lt;code&gt;{REQUEST_URI}&lt;/code&gt; string, it's time to see how we can use conditionals to access other variables with the &lt;code&gt;RewriteCond&lt;/code&gt; statement. The &lt;code&gt;RewriteCond&lt;/code&gt; statement is used to specify the conditions under which a &lt;code&gt;RewriteRule&lt;/code&gt; statement should be applied.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond&lt;/code&gt; is similar in format to &lt;code&gt;RewriteRule&lt;/code&gt; in that you have the command name, &lt;code&gt;RewriteCond&lt;/code&gt;, a variable to be matched, the regex, and flags. The logical OR flag, &lt;code&gt;[OR]&lt;/code&gt;, is a useful flag to remember because all &lt;code&gt;RewriteCond&lt;/code&gt; and &lt;code&gt;RewriteRule&lt;/code&gt; statements are inclusive, in the sense of a logical AND relationship, until terminated by the Last, &lt;code&gt;[L]&lt;/code&gt;, flag.&lt;br /&gt;&lt;br /&gt;You can test many server variables with a &lt;code&gt;RewriteCond&lt;/code&gt; statement. You can find a list in the SitePoint article I mentioned previously, but &lt;a href="http://www.php.net/reserved.variables#reserved.variables.server"&gt;this is the best list of server variables&lt;/a&gt; I've found.&lt;br /&gt;&lt;br /&gt;As an example, let's assume that we want to force the www in your domain name. To do this, you'll need to test the Apache &lt;code&gt;{HTTP_HOST}&lt;/code&gt; variable to see if the www. is already there and, if it's not, redirect to the desired host name:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{HTTP_HOST} !^www\.example\.com$ [NC]&lt;br /&gt;RewriteRule .? http://www.example.com%{REQUEST_URI} [R=301,L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Here, to denote that &lt;code&gt;{HTTP_HOST}&lt;/code&gt; is an Apache variable, we must prepend a &lt;code&gt;%&lt;/code&gt; character to it. The regex begins with the &lt;code&gt;!&lt;/code&gt; character, which will cause the condition to be true if it doesn't match the pattern. We also have to escape the dot character so that it matches a literal dot and not any character, as is the case with the dot metacharacter. We've also added the No Case flag to make this operation case-insensitive.&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;RewriteRule&lt;/code&gt; will match zero or one of any character, and will redirect to &lt;code&gt;http://www.example.com&lt;/code&gt; plus the original &lt;code&gt;{REQUEST_URI}&lt;/code&gt; value. The &lt;code&gt;R=301&lt;/code&gt;, or redirect, flag will cause Apache to issue a HTTP 301 response, which indicates that this is a permanent redirection; the Last flag tells mod_rewrite that you've completed this block statement.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond&lt;/code&gt; statements can also create atoms, but these are denoted with &lt;code&gt;%1 ... %9&lt;/code&gt; in the same way that &lt;code&gt;RewriteRule&lt;/code&gt; atoms are denoted with &lt;code&gt;$1 ... $9&lt;/code&gt;. You'll see these atom variables in operation in the examples later on.&lt;br /&gt;&lt;br /&gt;&lt;!--nextpage--&gt;&lt;br /&gt;&lt;h5&gt;mod_rewrite Flags&lt;/h5&gt;&lt;br /&gt;mod_rewrite uses "flags" to give our rewrite conditions and rules additional features. We add flags at the end of a condition or rule using square brackets, and separate multiple flags with a comma. The main flags with which you'll need to be familiar are:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt; &lt;li&gt;&lt;code&gt;last|L&lt;/code&gt; - The Last flag tells Apache to terminate a series of rewrite conditions and rewrite rules in the same way that &lt;code&gt;}&lt;/code&gt; will terminate a statement block in PHP. Note that this flag does not terminate mod_rewrite processing!&lt;/li&gt;&lt;br /&gt; &lt;li&gt;&lt;code&gt;nocase|NC&lt;/code&gt; - The No Case flag tells Apache to ignore the case of the string in the regex and is often necessary when writing a &lt;code&gt;RewriteCond&lt;/code&gt; statement that matches the &lt;code&gt;{HTTP_HOST}&lt;/code&gt; variable for a domain name, which is not case sensitive.&lt;/li&gt;&lt;br /&gt; &lt;li&gt;&lt;code&gt;redirect|R&lt;/code&gt; - The Redirect flag is used to trigger an external (visible) redirection. By default, this means that Apache will issue an HTTP 302 response to indicate that the document has been moved temporarily, but you can specify the HTTP code if you like. For example, you could use &lt;code&gt;[R=301]&lt;/code&gt; to make Apache issue a HTTP 301 response (moved permanently), which is often useful if you need search engines to reindex a changed URI.&lt;/li&gt;&lt;br /&gt; &lt;li&gt;&lt;code&gt;qsappend|QSA&lt;/code&gt; - The Query String Appended flag is used to "pass through" existing query strings. You can also define a new query sting to which the old string will be appended, just be careful not to replicate key names. Failure to use the &lt;code&gt;QSA&lt;/code&gt; flag will cause the creation of a query string during a redirection to destroy an existing query string.&lt;/li&gt;&lt;br /&gt; &lt;li&gt;&lt;code&gt;forbidden|F&lt;/code&gt; - The Forbidden flag is used to tell Apache when not to provide a page in response to a request. As a result, Apache will issue a HTTP 403 response, which can be used to protect files from being viewed by unauthorized visitors, bandwidth leeches, and so on.&lt;/li&gt;&lt;br /&gt; &lt;li&gt;&lt;code&gt;ornext|OR&lt;/code&gt; - The OR flag allows you to combine rewrite conditions with a logical OR relationship as opposed to the default AND.&lt;/li&gt;&lt;br /&gt; &lt;li&gt;&lt;code&gt;next|N&lt;/code&gt; - The Next flag tells mod_rewrite to restart the rewriting process from the first rule, but to use the URI that was returned from the last processed rewrite rule. This is useful for replacing characters in the &lt;code&gt;{REQUEST_URI}&lt;/code&gt; when you don't know how many there will be.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;You can read about the other available flags at &lt;a href="http://httpd.apache.org/docs-2.0/mod/mod_rewrite.html"&gt;Apache.org's mod_rewrite documentation page&lt;/a&gt;.&lt;br /&gt;&lt;h5&gt;mod_rewrite Comments&lt;/h5&gt;&lt;br /&gt;The &lt;code&gt;RewriteEngine on&lt;/code&gt; statement is needed at the start of any mod_rewrite code, but it is also useful in another role. As a good programmer, you know how important comments are in your code. mod_rewrite allows you to comment out an entire block of mod_rewrite code by wrapping the code in &lt;code&gt;RewriteEngine off&lt;/code&gt; and &lt;code&gt;RewriteEngine on&lt;/code&gt; statements:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteEngine off&lt;br /&gt;RewriteCond %{HTTP_HOST} !^www\.example\.com$ [NC]&lt;br /&gt;RewriteRule .? http://www.example.com%{REQUEST_URI} [R=301,L]&lt;br /&gt;RewriteEngine on&lt;/code&gt;&lt;br /&gt;&lt;div id="adz"&gt;&lt;br /&gt;&lt;div&gt;&lt;a href="http://ads.aws.sitepoint.com/phpadsnew/www/delivery/ck.php?oaparams=2__bannerid=970__zoneid=90__cb=415577f084__maxdest=http://order.1and1.com/xml/order/Hosting?ac=OM.US.USa09K16337B7023a" target="_top"&gt;&lt;/a&gt;&lt;br /&gt;&lt;div id="beacon_970" style="position: absolute; left: 0px; top: 0px; visibility: hidden;"&gt;&lt;img style="width: 0px; height: 0px;" src="http://ads.aws.sitepoint.com/phpadsnew/www/delivery/lg.php?bannerid=970&amp;amp;campaignid=568&amp;amp;zoneid=90&amp;amp;loc=http%3A%2F%2Fads.aws.sitepoint.com%2Fadjs.php%3Fregion%3D90%26did%3Dadz%26adtype%3Dvertical&amp;amp;referer=http%3A%2F%2Fwww.sitepoint.com%2Farticle%2Fapache-mod_rewrite-examples%2F2%2F&amp;amp;cb=415577f084" alt="" width="0" height="0" /&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;None of the statements above will be read by the mod_rewrite engine. &lt;code&gt;RewriteEngine&lt;/code&gt; statements can be very helpful when developing new mod_rewrite code -- just use them as you would the &lt;code&gt;/* ... */&lt;/code&gt; wrapper for PHP comments.&lt;br /&gt;&lt;h5&gt;mod_rewrite Tips&lt;/h5&gt;&lt;br /&gt;As a webmaster, it's up to you to determine how your pages will be identified to visitors, as well as how to rewrite those URIs so that Apache can serve the appropriate content. Be sure to put some careful consideration into the creation of your new URI scheme. And don't forget that after implementing your new URI scheme, you may have to go back over old content, updating existing links to match the new scheme.&lt;br /&gt;&lt;br /&gt;When you design your new URI scheme, make use of unique keys whenever you can. In a previous example, I used countries, states and cities as keys -- items that would be unique in a database. But as I build web sites for clients to update themselves, it's unreasonable for me to insist that they provide unique titles for all their articles. Articles in the database are typically identified by an auto-incremented ID, which would be perfect for my friendly URI scheme. It makes your rewrite rules a lot simpler because you can map a URI atom to a query string value directly.&lt;br /&gt;&lt;br /&gt;People often attempt to use a database to redirect from a title or other such field to a specific ID value. mod_rewrite has a &lt;code&gt;RewriteMap&lt;/code&gt; statement for this purpose, but you need to have access to your Apache main configuration file: &lt;code&gt;httpd.conf&lt;/code&gt;. Typically, you'll only have access to this file if you own and operate the server. Instead, avoid the problem completely, and use the ID field to create your links.&lt;br /&gt;&lt;br /&gt;Remember that spaces appear as &lt;code&gt;%20&lt;/code&gt; in URIs, so you'll need to replace them in your PHP code. PHP's &lt;code&gt;str_replace&lt;/code&gt; function is perfect for this task. Generally, we need to replace spaces with &lt;code&gt;%20&lt;/code&gt; when generating links, and convert &lt;code&gt;%20&lt;/code&gt; back to spaces when reading in query string values from the &lt;code&gt;$_GET&lt;/code&gt; array. However, when working with unique database field values that contain spaces, I prefer to use the underscore character to replace the spaces in resulting links. To do so, you can use the following PHP code:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;$name = str_replace ( ' ', '_', $name );&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Be careful not to break existing relative links when you implement your new URI scheme. Developers are often surprised when suddenly their CSS, JavaScript files, and images cease to work. Remember that relative links are relative to the current URI at the browser end -- that's the requested URI, not the rewritten one. Depending on your site's configuration, you may need to use absolute URIs, or the HTML &lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt; tag, for all your static resources.&lt;br /&gt;&lt;h5&gt;13 mod_rewrite Examples&lt;/h5&gt;&lt;br /&gt;Earlier, we looked at an example that forced the inclusion of the www part of a domain name for every request. Let's have a look at some more examples and see how useful mod_rewrite can be.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;1. Forcing www for a domain while preserving subdomains&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{HTTP_HOST} ^([a-z.]+)?example\.com$ [NC]&lt;br /&gt;RewriteCond %{HTTP_HOST} !^www\. [NC]&lt;br /&gt;RewriteRule .? http://www.%1example.com%{REQUEST_URI} [R=301,L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This rule captures the optional subdomain using the &lt;code&gt;%1&lt;/code&gt; variable, and, if it doesn't start with www., redirects with www. prepended to the subdomain. The domain and the original &lt;code&gt;{REQUEST_URI}&lt;/code&gt; are appended to the result.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;2. Eliminating www from a domain&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{HTTP_HOST} !^example\.com$ [NC]&lt;br /&gt;RewriteRule .? http://example.com%{REQUEST_URI} [R=301,L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;3. Getting rid of the www but preserving a subdomain&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{HTTP_HOST} ^www\.(([a-z0-9_]+\.)?example\.com)$ [NC]&lt;br /&gt;RewriteRule .? http://%1%{REQUEST_URI} [R=301,L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Here, the subdomain is captured in &lt;code&gt;%2&lt;/code&gt; (the inner atom) but, since it's optional and already captured in the &lt;code&gt;%1&lt;/code&gt; variable, all you need is the &lt;code&gt;%1&lt;/code&gt; for the subdomain.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;4. Preventing image hotlinking&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;If some unscrupulous webmasters are leeching your bandwidth by linking to images from your site to post on theirs, you can use the following rule to block the requests:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{HTTP_REFERER} !^$&lt;br /&gt;RewriteCond %{HTTP_REFERER} !^http://(www\.)?example\.com/ [NC]&lt;br /&gt;RewriteRule \.(gif|jpg|png)$ - [F]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;If the &lt;code&gt;{HTTP_REFERER}&lt;/code&gt; value is not blank, or from your own domain (example.com), this rule will block the viewing of URIs ending in &lt;code&gt;.gif&lt;/code&gt;, &lt;code&gt;.jpg&lt;/code&gt;, or &lt;code&gt;.png&lt;/code&gt; using the forbidden flag, &lt;code&gt;F&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;If you are upset enough at these hotlinkers, you could change the image and let visitors to the site know that you know that they're hotlinking:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{HTTP_REFERER} !^$&lt;br /&gt;RewriteCond %{HTTP_REFERER} !^http://(www\.)?example\.com/.*$ [NC]&lt;br /&gt;RewriteRule \.(gif|jpg|png)$ http://www.example.com/hotlinked.gif [R=301,L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Instead of blocking the URI, the above rule rewrites it to a specific image in our domain. What appears in this image is completely up to your imagination!&lt;br /&gt;&lt;br /&gt;You can block specific domains using:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{HTTP_REFERER} !^http://(www\.)?leech_site\.com/ [NC]&lt;br /&gt;RewriteRule \.(gif|jpg|png)$ - [F,L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This rule blocks all requests where the &lt;code&gt;{HTTP_REFERER}&lt;/code&gt; field is set to the bad domain.&lt;br /&gt;&lt;br /&gt;Of course, the above rules rely on the &lt;code&gt;{HTTP_REFERER}&lt;/code&gt; value being set correctly. It usually is, but if you'd rather rely on the IP Address, use &lt;code&gt;{REMOTE_ADDR}&lt;/code&gt; instead.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;5. Redirecting to a 404 page if the directory and file do not exist&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;If your host doesn't provide for a "file not found" redirection, create it yourself!&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{REQUEST_FILENAME} !-f&lt;br /&gt;RewriteCond %{REQUEST_FILENAME} !-d&lt;br /&gt;RewriteRule .? /404.php [L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Here, &lt;code&gt;-f&lt;/code&gt; matches an existing filename and &lt;code&gt;-d&lt;/code&gt; matches an existing directory name. This script checks to see that the requested filename is not an existing filename or directory name before it redirects to the &lt;code&gt;404.php&lt;/code&gt; script. You can extend this script: include the URI in a query string by adding &lt;code&gt;?url=$1&lt;/code&gt; immediately after the URI:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteRule ^/?(.*)$ /404.php?url=$1 [L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This way, your &lt;code&gt;404.php&lt;/code&gt; script can do something with the requested URL: display it in a message, send it in an email alert, perform a search, and so on.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;6. Renaming your directories&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;If you've shifted files around on your site, changing directory names, try this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteRule ^/?old_directory/([a-z/.]+)$ new_directory/$1 [R=301,L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;I've included the literal dot character (not the "any character" metacharacter) inside the set to allow file extensions.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;7. Converting old &lt;code&gt;.html&lt;/code&gt; links to new &lt;code&gt;.php&lt;/code&gt; links&lt;/em&gt;&lt;/strong&gt;&lt;em&gt; &lt;/em&gt;&lt;br /&gt;&lt;br /&gt;Updating your web site but need to be sure that bookmarked links will still work?&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteRule ^/?([a-z/]+)\.html$ $1.php [L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This is not a redirection, so it will be invisible to your visitors. To make it permanent (and visible), change the flag to &lt;code&gt;[R=301,L]&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;8. Creating extensionless links&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;If your site uses PHP files, and you want to make your links easier to remember -- or you just want to hide the file extension, try this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteRule ^/?([a-z]+)$ $1.php [L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;If you have a mixture of both &lt;code&gt;.html&lt;/code&gt; and &lt;code&gt;.php&lt;/code&gt; files, you can use &lt;code&gt;RewriteCond&lt;/code&gt; statements to check whether the filename with either extension exists as a file:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{REQUEST_FILENAME}.php -f&lt;br /&gt;RewriteRule ^/?([a-zA-Z0-9]+)$ $1.php [L]&lt;br /&gt;RewriteCond %{REQUEST_FILENAME}.html -f&lt;br /&gt;RewriteRule ^/?([a-zA-Z0-9]+)$ $1.html [L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;If the file name exists with the &lt;code&gt;.php&lt;/code&gt; extension, that rule will be chosen.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;&lt;strong&gt;9. Checking for a key in a query string&lt;/strong&gt;&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;If you need to have a specific key's value in your query string, you can check for its existence with a &lt;code&gt;RewriteCond&lt;/code&gt; statement:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{QUERY_STRING} !uniquekey=&lt;br /&gt;RewriteRule ^/?script_that_requires_uniquekey\.php$ other_script.php [QSA,L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The above code will check the &lt;code&gt;{QUERY_STRING}&lt;/code&gt; variable for a lack of the key &lt;code&gt;uniquekey&lt;/code&gt; and, if the &lt;code&gt;{REQUEST_URI}&lt;/code&gt; is the &lt;code&gt;script_that_requires_uniquekey&lt;/code&gt;, it will redirect to an alternative URI.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;10. Deleting the query string&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Apache's mod_rewrite automatically passes through a query string unless you do either of the following:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt; &lt;li&gt;Assign a new query string (you can keep the original query string by adding a QSA flag, e.g., &lt;code&gt;[QSA,L]&lt;/code&gt;).&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Add a &lt;code&gt;?&lt;/code&gt; after a filename (for example, &lt;code&gt;index.php?&lt;/code&gt;). The &lt;code&gt;?&lt;/code&gt; will not be shown in the browser's location field.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;11. Redirecting a working URI to a new format&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Here's a curly one. Let's say, for example, that we've got a set of working URLs that look like this: &lt;code&gt;/index.php?id=nnnn&lt;/code&gt;. However, we'd really like to change them to &lt;code&gt;/nnnn&lt;/code&gt; and make sure search engines update their indexes to the new URI format. First, we'd have to redirect the old URIs to the new ones so that search engines update their indexes, but we'd still have to rewrite the new URI back to the old one so that the &lt;code&gt;index.php&lt;/code&gt; script would run. Have I got your head spinning?&lt;br /&gt;&lt;br /&gt;The trick here is to place into the query string a marker code that will not be seen by visitors. We redirect from the old link to the new format only if the "marker" is not present in the query string. Then we rewrite the new format link back to the old format, and add a marker to the query string, using the QSA flag to ensure we're not eliminating an existing query string. Here's how it's done:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{QUERY_STRING} !marker&lt;br /&gt;RewriteCond %{QUERY_STRING} id=([-a-zA-Z0-9_+]+)&lt;br /&gt;RewriteRule ^/?index\.php$ %1? [R=301,L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;RewriteRule ^/?([-a-zA-Z0-9_+]+)$ index.php?marker&amp;amp;id=$1 [L]&lt;br /&gt;&lt;br /&gt;Here, the original URI, &lt;code&gt;http://www.example.com/index.php?id=nnnn&lt;/code&gt;, does not contain the marker, so it's redirected by the first rule to &lt;code&gt;http://www.example.com/nnnn&lt;/code&gt; with a HTTP 301 response. The second rule rewrites &lt;code&gt;http://www.example.com/nnnn&lt;/code&gt; back to &lt;code&gt;http://www.example.com/index.php?marker&amp;amp;id=nnnn&lt;/code&gt;, adding &lt;code&gt;marker&lt;/code&gt; and &lt;code&gt;id=nnnn&lt;/code&gt; in a new query string; then, the mod_rewrite process is started over.&lt;br /&gt;&lt;br /&gt;In the second iteration, the marker is matched so the first rule is ignored and, since there's a dot character in &lt;code&gt;index.php?marker&amp;amp;id=nnnn&lt;/code&gt;, the second rule is also ignored ... and we're finished!&lt;br /&gt;&lt;br /&gt;Note that, while useful, this solution does require additional processing by Apache, so be careful if you're using it on shared servers with a lot of traffic.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;12. Ensuring that a secure server is used&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Apache can determine whether you're using a secure server in two ways: using the &lt;code&gt;{HTTPS}&lt;/code&gt;, or &lt;code&gt;{SERVER_PORT}&lt;/code&gt;, variables:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{REQUEST_URI} ^secure_page\.php$&lt;br /&gt;RewriteCond %{HTTPS} !on&lt;br /&gt;RewriteRule ^/?(secure_page\.php)$ https://www.example.com/$1 [R=301,L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The above example tests that the &lt;code&gt;{REQUEST_URI}&lt;/code&gt; value is equal to our secure page script, and that the &lt;code&gt;{HTTPS}&lt;/code&gt; value is not equal to on. If both these conditions re met, the request is redirected to the secure server URI. Alternatively, you could do the same thing by testing the &lt;code&gt;{server_port}&lt;/code&gt; value, where 443 is typically the secure server port:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{REQUEST_URI} ^secure_page\.php$&lt;br /&gt;RewriteCond %{SERVER_PORT} !^443$&lt;br /&gt;RewriteRule ^/?(secure_page\.php)$ https://www.example.com/$1 [R=301,L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;13. Enforcing secure server only on selected pages&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;In situations where secure and unsecured domains share the web server's &lt;code&gt;DocumentRoot&lt;/code&gt; directory, you'll need a &lt;code&gt;RewriteCond&lt;/code&gt; statement to check that the secure server port isn't being used, and then only redirect the request if the requested script is one in the list of those that require a secure server:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{SERVER_PORT} !^443$&lt;br /&gt;RewriteRule ^/?(page1|page2|page3|page4|page5)$  https://www.example.com/%1 [R=301,L]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Here's how you'd redirect requests for pages not requiring a secure server back to port 80:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;RewriteCond %{ SERVER_PORT } ^443$&lt;br /&gt;RewriteRule !^/?(page6|page7|page8|page9)$ http://www.example.com%{REQUEST_URI} [R=301,L]&lt;/code&gt;&lt;br /&gt;&lt;h5&gt;Summary&lt;/h5&gt;&lt;br /&gt;Apache mod_rewrite is primarily used to allow SEO and user friendly URIs, but it's also an extremely flexible tool for other important redirection tasks. If you want to learn more, here are some very useful resources I've found:&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;Regular Expressions&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt; &lt;li&gt;Great tutorial: &lt;a href="http://gnosis.cx/publish/programming/regular_expressions.html"&gt;http://gnosis.cx/publish/programming/regular_expressions.html&lt;/a&gt;&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Cheat sheet: &lt;a href="http://regexlib.com/CheatSheet.aspx"&gt;http://regexlib.com/CheatSheet.aspx&lt;/a&gt;&lt;/li&gt;&lt;br /&gt; &lt;li&gt;A regex-capable text editor: &lt;a href="http://www.editpadpro.com/"&gt;http://www.editpadpro.com&lt;/a&gt;&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Regex Coach: &lt;a href="http://weitz.de/regex-coach/"&gt;http://weitz.de/regex-coach/&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;strong&gt;&lt;em&gt;mod_rewrite&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt; &lt;li&gt;Cheat sheet: &lt;a href="http://www.ilovejackdaniels.com/cheat-sheets/mod_rewrite-cheat-sheet/"&gt;http://www.ilovejackdaniels.com/cheat-sheets/mod_rewrite-cheat-sheet/&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;p align="right"&gt;&lt;strong&gt;www.sitepoint.com&lt;/strong&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4004158710435644696-414755280082157880?l=eswebsoft.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/414755280082157880/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/learn-apache-modrewrite-13-real-world.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/414755280082157880'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/414755280082157880'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/learn-apache-modrewrite-13-real-world.html' title='Learn Apache mod_rewrite: 13 Real-world Examples'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4004158710435644696.post-5835082698913690873</id><published>2009-08-02T17:22:00.004+07:00</published><updated>2009-08-02T17:29:45.280+07:00</updated><title type='text'>Writing the Pac-Man Game in JavaFX - Part 3</title><content type='html'>&lt;p&gt; &lt;strong&gt;Previous parts in the Pac-Man series&lt;/strong&gt;&lt;br /&gt;&lt;a href="http://eswebsoft.blogspot.com/2009/08/writing-pac-man-game-in-javafx-part-1.html"&gt;Writing the Pac-Man Game in JavaFX - Part 1&lt;/a&gt;&lt;br /&gt;&lt;a href="http://eswebsoft.blogspot.com/2009/08/writing-pac-man-game-in-javafx-part-2.html"&gt;Writing the Pac-Man Game in JavaFX - Part 2&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;  &lt;p&gt;We are now ready to create the ghosts in our game. The four ghosts, namely Blinky(red), Pinky(pink),  Inky(cyan) and Clyde(orange), are trapped inside a cage when a game starts. After some time, they get out of the cage one by one and start roaming the maze. Their goal is to catch the Pac-Man. The Pac-Man dies if he is touched by one of the ghosts. If the Pac-Man swallows a magic dot, he has the power to eat ghosts for a while. During this  time, the ghosts turn hollow and move more slowly. &lt;/p&gt;  &lt;p&gt;There are two parts for writing the code for ghosts. First part is to create the animation. The second part is to implement an algorithm to control how the ghosts move inside the maze. The second part is the most interesting and crucial thing of this game. We will elaborate the algorithm in the next article. For now, we just use a simpler one for testing the animation.&lt;/p&gt;  &lt;p&gt;&lt;b&gt;Animation of Ghosts&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;A ghost can have three kinds of appearance. One is its normal look in its original color. The second is  a hollow ghost. The third is a flashing hollow style when it is about to turn back to its original color. So we need three sets of frames for the animation. Just like the Pac-Man character, every set of frames contains   4 pictures. To make a ghost look differently, we can switch the set of frames when  the status of a ghost changes. For example, below are three set of pictures for the red ghost Blinky.&lt;/p&gt;  &lt;p&gt;&lt;img src="http://www.insideria.com/upload/2009/05/blinkyframes.png" /&gt;&lt;/p&gt;  &lt;p&gt;In terms of moving approaches, the ghosts have three styles: roaming the maze, crawling slowly when they turn hollow, and circling in the cage. Since the first two are the same except the moving speed is different, we basically need to have two kinds of logic to handle the moving of a ghost: outside and inside the cage respectively. &lt;/p&gt;  &lt;p&gt;When we wrote the code of the Pac-Man character, we subclassed from &lt;b&gt;&lt;tt&gt;MovingObject&lt;/tt&gt;&lt;/b&gt;. This class abstracts the common logic needed for a character. Let's write the &lt;b&gt;&lt;tt&gt;Ghost&lt;/tt&gt;&lt;/b&gt; class by extending  &lt;b&gt;&lt;tt&gt;MovingObject&lt;/tt&gt;&lt;/b&gt; again. Below is the code of &lt;b&gt;&lt;tt&gt;Ghost.fx&lt;/tt&gt;&lt;/b&gt;:  &lt;/p&gt;  &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/*&lt;br /&gt;* Ghost.fx&lt;br /&gt;*&lt;br /&gt;* Created on 2009-1-28, 14:26:09&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;package pacman;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; java.lang.&lt;span class="category2"&gt;Math&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.animation.KeyFrame;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.animation.Timeline;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.CustomNode;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.image.Image;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.image.ImageView;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.Node;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; pacman.MazeData;&lt;br /&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/**&lt;br /&gt;* @author Henry Zhang&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; Ghost &lt;span class="category1"&gt;extends&lt;/span&gt; CustomNode, MovingObject{&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; def TRAPPED=10;&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// the pacman character&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; pacMan: PacMan;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; hollowImage1 = Image {&lt;br /&gt;    &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/ghosthollow2.png&lt;/span&gt;"&lt;br /&gt;    }&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; hollowImage2 = Image {&lt;br /&gt;    &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/ghosthollow3.png&lt;/span&gt;"&lt;br /&gt;    }&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; hollowImage3 = Image {&lt;br /&gt;    &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/ghosthollow1.png&lt;/span&gt;"&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// images for ghosts when they become hollow&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; hollowImg =&lt;br /&gt;   [ hollowImage1,&lt;br /&gt;     hollowImage2,&lt;br /&gt;     hollowImage1,&lt;br /&gt;     hollowImage2 ];&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// images for ghosts when they become hollow and flashing&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; flashHollowImg =&lt;br /&gt;   [ hollowImage1,&lt;br /&gt;     hollowImage3,&lt;br /&gt;     hollowImage1,&lt;br /&gt;     hollowImage3 ];&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// time for a ghost to stay hollow&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;var&lt;/span&gt; hollowMaxTime: Integer = 80;&lt;br /&gt; &lt;span class="category1"&gt;var&lt;/span&gt; hollowCounter : Integer;&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// the images of animation&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; defaultImage1: Image;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; defaultImage2: Image;&lt;br /&gt;&lt;br /&gt; def  defaultImg  = [&lt;br /&gt;       defaultImage1,&lt;br /&gt;       defaultImage2,&lt;br /&gt;       defaultImage1,&lt;br /&gt;       defaultImage2,&lt;br /&gt; ];&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// animation images&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;var&lt;/span&gt; images = defaultImg;&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// initial direction and position of a ghost, used in status reset&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; initialLocationX : &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; initialLocationY : &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; initialDirectionX : &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; initialDirectionY : &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// time to stay in the cage&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; trapTime: Integer;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; trapCounter: Integer=0;&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// variables to decide if ghost should chase man, and with what probability&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; changeFactor = 0.75;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// the flag is set if a ghost becomes hollow&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; isHollow: &lt;span class="category2"&gt;Boolean&lt;/span&gt; = &lt;span class="category1"&gt;false&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// the GUI of a ghost&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;var&lt;/span&gt; ghostNode : ImageView = ImageView {&lt;br /&gt;    &lt;span class="category2"&gt;x&lt;/span&gt;: bind imageX  - 13&lt;br /&gt;    &lt;span class="category2"&gt;y&lt;/span&gt;: bind imageY  - 13&lt;br /&gt;    image: bind images[currentImage]&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt; postinit {&lt;br /&gt;    initialLocationX = &lt;span class="category2"&gt;x&lt;/span&gt;;&lt;br /&gt;    initialLocationY = &lt;span class="category2"&gt;y&lt;/span&gt;;&lt;br /&gt;    initialDirectionX = xDirection;&lt;br /&gt;    initialDirectionY = yDirection;&lt;br /&gt;&lt;br /&gt;    resetStatus();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// reset the status of a ghost and place it into the cage&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; resetStatus() {&lt;br /&gt;    &lt;span class="category2"&gt;x&lt;/span&gt; = initialLocationX;&lt;br /&gt;    &lt;span class="category2"&gt;y&lt;/span&gt; = initialLocationY;&lt;br /&gt;&lt;br /&gt;    xDirection = initialDirectionX;&lt;br /&gt;    yDirection = initialDirectionY;&lt;br /&gt;&lt;br /&gt;    isHollow = &lt;span class="category1"&gt;false&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;    moveCounter = 0;&lt;br /&gt;    trapCounter = 0;&lt;br /&gt;    currentImage = 0;&lt;br /&gt;&lt;br /&gt;    imageX = MazeData.calcGridX(&lt;span class="category2"&gt;x&lt;/span&gt;);&lt;br /&gt;    imageY = MazeData.calcGridY(&lt;span class="category2"&gt;y&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;    images = defaultImg;&lt;br /&gt;    state = TRAPPED;&lt;br /&gt;      &lt;br /&gt;    timeline.keyFrames[0].&lt;span class="category2"&gt;time&lt;/span&gt; = 50ms;&lt;br /&gt;&lt;br /&gt;    &lt;span class="category2"&gt;visible&lt;/span&gt; = &lt;span class="category1"&gt;true&lt;/span&gt;;&lt;br /&gt;    &lt;span class="category2"&gt;start&lt;/span&gt;();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; changeToHollowGhost() {&lt;br /&gt;    hollowCounter = 0;&lt;br /&gt;    isHollow = &lt;span class="category1"&gt;true&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;    &lt;span class="linecomment"&gt;// switch the animation images&lt;/span&gt;&lt;br /&gt;    images = hollowImg;&lt;br /&gt;&lt;br /&gt;    &lt;span class="linecomment"&gt;// make it moves slower&lt;/span&gt;&lt;br /&gt;    timeline.&lt;span class="category2"&gt;stop&lt;/span&gt;();&lt;br /&gt;    timeline.keyFrames[0].&lt;span class="category2"&gt;time&lt;/span&gt; = 140ms;&lt;br /&gt;    timeline.&lt;span class="category2"&gt;play&lt;/span&gt;();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// decide whether to change the current direction of a ghost&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; changeDirectionXtoY(mustChange: &lt;span class="category2"&gt;Boolean&lt;/span&gt;): Void {&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( &lt;span class="category1"&gt;not&lt;/span&gt; mustChange &lt;span class="category1"&gt;and&lt;/span&gt; &lt;span class="category2"&gt;Math&lt;/span&gt;.&lt;span class="category2"&gt;random&lt;/span&gt;() &gt; changeFactor ) {&lt;br /&gt;       &lt;span class="category1"&gt;return&lt;/span&gt;;  &lt;span class="linecomment"&gt;// no change of direction&lt;/span&gt;&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;    &lt;span class="linecomment"&gt;// will change to a Y direction if possible&lt;/span&gt;&lt;br /&gt;    &lt;span class="category1"&gt;var&lt;/span&gt; goUp = MoveDecision {&lt;br /&gt;       &lt;span class="category2"&gt;x&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;x&lt;/span&gt;&lt;br /&gt;       &lt;span class="category2"&gt;y&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;y&lt;/span&gt; - 1 };&lt;br /&gt;    &lt;span class="category1"&gt;var&lt;/span&gt; goDown = MoveDecision {&lt;br /&gt;       &lt;span class="category2"&gt;x&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;x&lt;/span&gt;&lt;br /&gt;       &lt;span class="category2"&gt;y&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;y&lt;/span&gt; + 1&lt;br /&gt;     };&lt;br /&gt;&lt;br /&gt;    &lt;span class="linecomment"&gt;// evaluate the moving choices to pick the best one&lt;/span&gt;&lt;br /&gt;    goUp.&lt;span class="category1"&gt;evaluate&lt;/span&gt;();&lt;br /&gt;    goDown.&lt;span class="category1"&gt;evaluate&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( goUp.score &lt; class="category1"&gt;and goDown.score &lt; class="category1"&gt;return&lt;/span&gt;;  &lt;span class="linecomment"&gt;// no change of direction&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;var&lt;/span&gt; continueGo =  MoveDecision {&lt;br /&gt;       &lt;span class="category2"&gt;x&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;x&lt;/span&gt; + xDirection&lt;br /&gt;       &lt;span class="category2"&gt;y&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;y&lt;/span&gt;&lt;br /&gt;     };&lt;br /&gt;&lt;br /&gt;    continueGo.&lt;span class="category1"&gt;evaluate&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( continueGo.score &gt; 0 &lt;span class="category1"&gt;and&lt;/span&gt; continueGo.score &gt; goUp.score&lt;br /&gt;         &lt;span class="category1"&gt;and&lt;/span&gt; continueGo.score &gt; goDown.score ) {&lt;br /&gt;       &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;var&lt;/span&gt; decision = -1; &lt;span class="linecomment"&gt;// make it goes up first, then decide if we need to change it&lt;/span&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( goUp.score  &lt; decision =" 1" class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;      &lt;span class="category1"&gt;if&lt;/span&gt; ( goDown.score &gt; 0 ) {&lt;br /&gt;         &lt;span class="linecomment"&gt;// random pick&lt;/span&gt;&lt;br /&gt;         &lt;span class="category1"&gt;if&lt;/span&gt; ( &lt;span class="category2"&gt;Math&lt;/span&gt;.&lt;span class="category2"&gt;random&lt;/span&gt;() &gt; 0.5 )&lt;br /&gt;           decision = 1;&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;    yDirection = decision;&lt;br /&gt;    xDirection = 0;&lt;br /&gt;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// decide whether to change the current direction of a ghost&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; changeDirectionYtoX(mustChange: &lt;span class="category2"&gt;Boolean&lt;/span&gt;): Void {&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( &lt;span class="category1"&gt;not&lt;/span&gt; mustChange &lt;span class="category1"&gt;and&lt;/span&gt; &lt;span class="category2"&gt;Math&lt;/span&gt;.&lt;span class="category2"&gt;random&lt;/span&gt;() &gt; changeFactor )&lt;br /&gt;      &lt;span class="category1"&gt;return&lt;/span&gt;;  &lt;span class="linecomment"&gt;// no change of direction&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class="linecomment"&gt;// will change to X directions if possible&lt;/span&gt;&lt;br /&gt;    &lt;span class="category1"&gt;var&lt;/span&gt; goLeft = MoveDecision {&lt;br /&gt;       &lt;span class="category2"&gt;x&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;x&lt;/span&gt; - 1&lt;br /&gt;       &lt;span class="category2"&gt;y&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;y&lt;/span&gt;&lt;br /&gt;     };&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;var&lt;/span&gt; goRight = MoveDecision {&lt;br /&gt;       &lt;span class="category2"&gt;x&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;x&lt;/span&gt; + 1&lt;br /&gt;       &lt;span class="category2"&gt;y&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;y&lt;/span&gt;&lt;br /&gt;     };&lt;br /&gt;&lt;br /&gt;    &lt;span class="linecomment"&gt;// evaluate the moving choices to pick the best one&lt;/span&gt;&lt;br /&gt;    goLeft.&lt;span class="category1"&gt;evaluate&lt;/span&gt;();&lt;br /&gt;    goRight.&lt;span class="category1"&gt;evaluate&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( goLeft.score &lt; class="category1"&gt;and goRight.score &lt; class="category1"&gt;return&lt;/span&gt;;  &lt;span class="linecomment"&gt;// no change of direction&lt;/span&gt;&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;var&lt;/span&gt; continueGo = MoveDecision {&lt;br /&gt;       &lt;span class="category2"&gt;x&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;x&lt;/span&gt;&lt;br /&gt;       &lt;span class="category2"&gt;y&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;y&lt;/span&gt; + yDirection&lt;br /&gt;     };&lt;br /&gt;&lt;br /&gt;    continueGo.&lt;span class="category1"&gt;evaluate&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( continueGo.score &gt; 0 &lt;span class="category1"&gt;and&lt;/span&gt; continueGo.score &gt; goLeft.score&lt;br /&gt;         &lt;span class="category1"&gt;and&lt;/span&gt; continueGo.score &gt; goRight.score ) {&lt;br /&gt;       &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;    &lt;span class="linecomment"&gt;// make it goes up first, then decide if we need to change it to down&lt;/span&gt;&lt;br /&gt;    &lt;span class="category1"&gt;var&lt;/span&gt; decision = -1;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( goLeft.score  &lt; decision =" 1" class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;      &lt;span class="category1"&gt;if&lt;/span&gt; ( goRight.score &gt; 0 ) {&lt;br /&gt;         &lt;span class="linecomment"&gt;// random pick&lt;/span&gt;&lt;br /&gt;         &lt;span class="category1"&gt;if&lt;/span&gt; ( &lt;span class="category2"&gt;Math&lt;/span&gt;.&lt;span class="category2"&gt;random&lt;/span&gt;() &gt; 0.5 )&lt;br /&gt;           decision = 1;&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;    xDirection=decision;&lt;br /&gt;    yDirection = 0;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// move the ghost horizontally&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; moveHorizontally() {&lt;br /&gt;&lt;br /&gt;    moveCounter++;&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( moveCounter &gt; ANIMATION_STEP - 1) {&lt;br /&gt;       moveCounter=0;&lt;br /&gt;       &lt;span class="category2"&gt;x&lt;/span&gt; += xDirection;&lt;br /&gt;       imageX= MazeData.calcGridX(&lt;span class="category2"&gt;x&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;       &lt;span class="category1"&gt;var&lt;/span&gt; nextX = xDirection + &lt;span class="category2"&gt;x&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;       &lt;span class="category1"&gt;if&lt;/span&gt; ( &lt;span class="category2"&gt;y&lt;/span&gt; == 14 &lt;span class="category1"&gt;and&lt;/span&gt; ( nextX &lt;= 1 &lt;span class="category1"&gt;or&lt;/span&gt; nextX &gt;= 28) ) {&lt;br /&gt;          &lt;span class="category1"&gt;if&lt;/span&gt; ( nextX &lt; - 1 &lt;span class="category1"&gt;and&lt;/span&gt; xDirection &lt; class="category2"&gt;x&lt;/span&gt;=MazeData.GRID_SIZE;&lt;br /&gt;             imageX= MazeData.calcGridX(&lt;span class="category2"&gt;x&lt;/span&gt;);&lt;br /&gt;           }&lt;br /&gt;          &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;            &lt;span class="category1"&gt;if&lt;/span&gt; ( nextX &gt; 30 &lt;span class="category1"&gt;and&lt;/span&gt; xDirection &gt; 0) {&lt;br /&gt;               &lt;span class="category2"&gt;x&lt;/span&gt;=0;&lt;br /&gt;               imageX= MazeData.calcGridX(&lt;span class="category2"&gt;x&lt;/span&gt;);&lt;br /&gt;             }&lt;br /&gt;        }&lt;br /&gt;       &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;         &lt;span class="category1"&gt;if&lt;/span&gt; (nextX &lt; class="category1"&gt;or nextX &gt; MazeData.GRID_SIZE) {&lt;br /&gt;            changeDirectionXtoY(&lt;span class="category1"&gt;true&lt;/span&gt;)&lt;br /&gt;          }&lt;br /&gt;       &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;         &lt;span class="category1"&gt;if&lt;/span&gt; ( MazeData.&lt;span class="category2"&gt;getData&lt;/span&gt;(nextX, &lt;span class="category2"&gt;y&lt;/span&gt;) == MazeData.BLOCK ) {&lt;br /&gt;            changeDirectionXtoY(&lt;span class="category1"&gt;true&lt;/span&gt;)&lt;br /&gt;          }&lt;br /&gt;         &lt;span class="category1"&gt;else&lt;/span&gt; {&lt;br /&gt;            changeDirectionXtoY(&lt;span class="category1"&gt;false&lt;/span&gt;);&lt;br /&gt;          }&lt;br /&gt;     }&lt;br /&gt;    &lt;span class="category1"&gt;else&lt;/span&gt; {&lt;br /&gt;       imageX += xDirection * MOVE_SPEED;&lt;br /&gt;     }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// move the ghost vertically&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; moveVertically() {&lt;br /&gt;    &lt;br /&gt;    moveCounter++;&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( moveCounter &gt; ANIMATION_STEP - 1) {&lt;br /&gt;       moveCounter = 0;&lt;br /&gt;       &lt;span class="category2"&gt;y&lt;/span&gt; += yDirection;&lt;br /&gt;       imageY = MazeData.calcGridX(&lt;span class="category2"&gt;y&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;       &lt;span class="category1"&gt;var&lt;/span&gt; nextY= yDirection + &lt;span class="category2"&gt;y&lt;/span&gt;;&lt;br /&gt;       &lt;span class="category1"&gt;if&lt;/span&gt; ( nextY &lt; class="category1"&gt;or nextY &gt; MazeData.GRID_SIZE) {&lt;br /&gt;          changeDirectionYtoX(&lt;span class="category1"&gt;true&lt;/span&gt;);&lt;br /&gt;        }&lt;br /&gt;       &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;         &lt;span class="category1"&gt;if&lt;/span&gt; ( MazeData.&lt;span class="category2"&gt;getData&lt;/span&gt;(&lt;span class="category2"&gt;x&lt;/span&gt;, nextY) == MazeData.BLOCK ) {&lt;br /&gt;            changeDirectionYtoX(&lt;span class="category1"&gt;true&lt;/span&gt;);&lt;br /&gt;          }&lt;br /&gt;         &lt;span class="category1"&gt;else&lt;/span&gt; {&lt;br /&gt;            changeDirectionYtoX(&lt;span class="category1"&gt;false&lt;/span&gt;);&lt;br /&gt;          }&lt;br /&gt;     }&lt;br /&gt;    &lt;span class="category1"&gt;else&lt;/span&gt; {&lt;br /&gt;       imageY += yDirection * MOVE_SPEED;&lt;br /&gt;     }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// move the ghost horizontally in the cage&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; moveHorizontallyInCage() {&lt;br /&gt;  &lt;br /&gt;    moveCounter++;&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( moveCounter &gt; ANIMATION_STEP - 1) {&lt;br /&gt;&lt;br /&gt;       moveCounter=0;&lt;br /&gt;       &lt;span class="category2"&gt;x&lt;/span&gt; += xDirection;&lt;br /&gt;       imageX = MazeData.calcGridX(&lt;span class="category2"&gt;x&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;       &lt;span class="category1"&gt;var&lt;/span&gt; nextX = xDirection + &lt;span class="category2"&gt;x&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;       &lt;span class="category1"&gt;if&lt;/span&gt; ( nextX &lt; xdirection =" 0;" ydirection =" 1;" class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;         &lt;span class="category1"&gt;if&lt;/span&gt; ( nextX &gt; 17) {&lt;br /&gt;            xDirection = 0;&lt;br /&gt;            yDirection = -1;&lt;br /&gt;          }&lt;br /&gt;     }&lt;br /&gt;    &lt;span class="category1"&gt;else&lt;/span&gt; {&lt;br /&gt;       imageX += xDirection * MOVE_SPEED;&lt;br /&gt;     }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// move the ghost vertically in a cage&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; moveVerticallyInCage() {&lt;br /&gt;&lt;br /&gt;    moveCounter++;&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( moveCounter &gt; ANIMATION_STEP - 1) {&lt;br /&gt;       moveCounter=0;&lt;br /&gt;       &lt;span class="category2"&gt;y&lt;/span&gt; += yDirection;&lt;br /&gt;       imageY= MazeData.calcGridX(&lt;span class="category2"&gt;y&lt;/span&gt;) + 8;&lt;br /&gt;&lt;br /&gt;       &lt;span class="category1"&gt;var&lt;/span&gt; nextY = yDirection + &lt;span class="category2"&gt;y&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;       &lt;span class="category1"&gt;if&lt;/span&gt; ( nextY &lt; ydirection =" 0;" xdirection =" -1;" class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;         &lt;span class="category1"&gt;if&lt;/span&gt; ( nextY &gt; 15) {&lt;br /&gt;            yDirection = 0;&lt;br /&gt;            xDirection = 1;&lt;br /&gt;          }&lt;br /&gt;     }&lt;br /&gt;    &lt;span class="category1"&gt;else&lt;/span&gt; {&lt;br /&gt;       imageY += yDirection * MOVE_SPEED;&lt;br /&gt;     }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; &lt;span class="category2"&gt;hide&lt;/span&gt;() {&lt;br /&gt;    &lt;span class="category2"&gt;visible&lt;/span&gt;=&lt;span class="category1"&gt;false&lt;/span&gt;;&lt;br /&gt;        timeline.&lt;span class="category2"&gt;stop&lt;/span&gt;();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// move one tick&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;function&lt;/span&gt; moveOneStep() {&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( state == MOVING &lt;span class="category1"&gt;or&lt;/span&gt; state == TRAPPED ) {&lt;br /&gt;       &lt;span class="category1"&gt;if&lt;/span&gt; ( xDirection != 0 ) {&lt;br /&gt;          &lt;span class="category1"&gt;if&lt;/span&gt; ( state == MOVING )&lt;br /&gt;            moveHorizontally()&lt;br /&gt;          &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;            moveHorizontallyInCage();&lt;br /&gt;        }&lt;br /&gt;       &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;         &lt;span class="category1"&gt;if&lt;/span&gt; ( yDirection != 0 ) {&lt;br /&gt;            &lt;span class="category1"&gt;if&lt;/span&gt; ( state == MOVING )&lt;br /&gt;              moveVertically()&lt;br /&gt;            &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;              moveVerticallyInCage();&lt;br /&gt;          }&lt;br /&gt;&lt;br /&gt;       &lt;span class="category1"&gt;if&lt;/span&gt; ( currentImage &lt; class="category1"&gt;else&lt;/span&gt; {&lt;br /&gt;          currentImage=0;&lt;br /&gt;          &lt;span class="category1"&gt;if&lt;/span&gt; ( state == TRAPPED ) {&lt;br /&gt;             trapCounter++;&lt;br /&gt; &lt;br /&gt;             &lt;span class="category1"&gt;if&lt;/span&gt; ( trapCounter &gt; trapTime &lt;span class="category1"&gt;and&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt; == 14 &lt;span class="category1"&gt;and&lt;/span&gt; &lt;span class="category2"&gt;y&lt;/span&gt; == 13) {&lt;br /&gt;                &lt;span class="linecomment"&gt;// go out of the cage&lt;/span&gt;&lt;br /&gt;                &lt;span class="category2"&gt;y&lt;/span&gt; = 12;&lt;br /&gt;  &lt;br /&gt;                xDirection = 0;&lt;br /&gt;                yDirection = -1;&lt;br /&gt;                state = MOVING;&lt;br /&gt;              }&lt;br /&gt;           }&lt;br /&gt;        }&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;    &lt;span class="linecomment"&gt;// check to see if need to switch back to a normal status&lt;/span&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( isHollow ) {&lt;br /&gt;   &lt;br /&gt;       hollowCounter++;&lt;br /&gt;&lt;br /&gt;       &lt;span class="category1"&gt;if&lt;/span&gt; ( hollowCounter == hollowMaxTime - 30 )&lt;br /&gt;         images = flashHollowImg&lt;br /&gt;       &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;         &lt;span class="category1"&gt;if&lt;/span&gt; ( hollowCounter &gt; hollowMaxTime ) {&lt;br /&gt;            isHollow = &lt;span class="category1"&gt;false&lt;/span&gt;;&lt;br /&gt;            images = defaultImg;&lt;br /&gt;                &lt;br /&gt;            timeline.&lt;span class="category2"&gt;stop&lt;/span&gt;();&lt;br /&gt;            timeline.keyFrames[0].&lt;span class="category2"&gt;time&lt;/span&gt; = 50ms;&lt;br /&gt;            timeline.&lt;span class="category2"&gt;play&lt;/span&gt;();&lt;br /&gt;          }&lt;br /&gt;     }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;function&lt;/span&gt; create(): Node {&lt;br /&gt;        &lt;span class="category1"&gt;return&lt;/span&gt; ghostNode;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt; The variable &lt;b&gt;&lt;tt&gt;defaultImg&lt;/tt&gt;&lt;/b&gt; is a sequence of images used as a ghost normal look.  The variable &lt;b&gt;&lt;tt&gt;hollowImg&lt;/tt&gt;&lt;/b&gt; and &lt;b&gt;&lt;tt&gt;flashHollowImg&lt;/tt&gt;&lt;/b&gt; store two sets of images for the states when a ghost becomes hollow and flashing. Similar to the &lt;b&gt;&lt;tt&gt;PacMan&lt;/tt&gt;&lt;/b&gt; class, the moving logic is handled in the function &lt;b&gt;&lt;tt&gt;moveOneStep()&lt;/tt&gt;&lt;/b&gt;. When a ghost is inside the cage, the function &lt;b&gt;&lt;tt&gt;moveHorizontallyInCage()&lt;/tt&gt;&lt;/b&gt; and &lt;b&gt;&lt;tt&gt;moveVerticallyInCage()&lt;/tt&gt;&lt;/b&gt; make the ghost turning around and around inside the cage. When a ghost gets out of the cage, two functions &lt;b&gt;&lt;tt&gt;moveHorizontally()&lt;/tt&gt;&lt;/b&gt; and &lt;b&gt;&lt;tt&gt;moveVertically()&lt;/tt&gt;&lt;/b&gt; control its roaming behavior. The variable &lt;b&gt;&lt;tt&gt;trapTime&lt;/tt&gt;&lt;/b&gt; determines how long a ghost stays in the cage before it gets out. Properly choosing  the values of this instance variable makes four ghosts coming out the cage in a fixed  order(Blinky-Pinky-Inky-Clyde). The below code in the function &lt;b&gt;&lt;tt&gt;moveOneStep()&lt;/tt&gt;&lt;/b&gt; sets free the ghost after a pre-defined time. &lt;/p&gt;&lt;p&gt; &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;function&lt;/span&gt; moveOneStep() {&lt;br /&gt;       . . . . . .&lt;br /&gt;&lt;br /&gt;       &lt;span class="category1"&gt;if&lt;/span&gt; ( state == TRAPPED ) {&lt;br /&gt;          trapCounter++;&lt;br /&gt;&lt;br /&gt;          &lt;span class="category1"&gt;if&lt;/span&gt; ( trapCounter &gt; trapTime &lt;span class="category1"&gt;and&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt; == 14 &lt;span class="category1"&gt;and&lt;/span&gt; &lt;span class="category2"&gt;y&lt;/span&gt; == 13) {&lt;br /&gt;             &lt;span class="linecomment"&gt;// go out of the cage&lt;/span&gt;&lt;br /&gt;             &lt;span class="category2"&gt;y&lt;/span&gt; = 12;&lt;br /&gt;&lt;br /&gt;             xDirection = 0;&lt;br /&gt;             yDirection = -1;&lt;br /&gt;             state = MOVING;&lt;br /&gt;           }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;      . . . . . .&lt;br /&gt; }&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;The function &lt;b&gt;&lt;tt&gt;changeToHollowGhost()&lt;/tt&gt;&lt;/b&gt; turns a ghost into a hollow style. What it does is switching the animation pictures and slowing down the moving speed of a ghost. &lt;/p&gt; &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; changeToHollowGhost() {&lt;br /&gt;   hollowCounter = 0;&lt;br /&gt;   isHollow = &lt;span class="category1"&gt;true&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;   &lt;span class="linecomment"&gt;// switch the animation images&lt;/span&gt;&lt;br /&gt;   images = hollowImg;&lt;br /&gt;&lt;br /&gt;   &lt;span class="linecomment"&gt;// make it moves slower&lt;/span&gt;&lt;br /&gt;   timeline.&lt;span class="category2"&gt;stop&lt;/span&gt;();&lt;br /&gt;   timeline.keyFrames[0].&lt;span class="category2"&gt;time&lt;/span&gt; = 140ms;&lt;br /&gt;   timeline.&lt;span class="category2"&gt;play&lt;/span&gt;();&lt;br /&gt; }&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;    &lt;p&gt;After a ghost becomes hollow, it resumes to its normal color after a period of time.  The second half of the function &lt;b&gt;&lt;tt&gt;moveOneStep()&lt;/tt&gt;&lt;/b&gt; uses a counter to keep track of the  time and flashes the ghost just before it turns into its normal color. From this part, we can see how the switching of 3 sets of pictures works.  &lt;/p&gt; &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;function&lt;/span&gt; moveOneStep() {&lt;br /&gt;&lt;br /&gt;   . . . . . .&lt;br /&gt;&lt;br /&gt;   &lt;span class="linecomment"&gt;// check to see if need to switch back to a normal status&lt;/span&gt;&lt;br /&gt;   &lt;span class="category1"&gt;if&lt;/span&gt; ( isHollow ) {&lt;br /&gt;  &lt;br /&gt;      hollowCounter++;&lt;br /&gt;&lt;br /&gt;      &lt;span class="category1"&gt;if&lt;/span&gt; ( hollowCounter == hollowMaxTime - 30 )&lt;br /&gt;        images = flashHollowImg&lt;br /&gt;      &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;        &lt;span class="category1"&gt;if&lt;/span&gt; ( hollowCounter &gt; hollowMaxTime ) {&lt;br /&gt;           isHollow = &lt;span class="category1"&gt;false&lt;/span&gt;;&lt;br /&gt;           images = defaultImg;&lt;br /&gt;               &lt;br /&gt;           timeline.&lt;span class="category2"&gt;stop&lt;/span&gt;();&lt;br /&gt;           timeline.keyFrames[0].&lt;span class="category2"&gt;time&lt;/span&gt; = 50ms;&lt;br /&gt;           timeline.&lt;span class="category2"&gt;play&lt;/span&gt;();&lt;br /&gt;         }&lt;br /&gt;    }&lt;br /&gt; }&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;    &lt;p&gt;&lt;b&gt;Roaming the Maze&lt;/b&gt; &lt;/p&gt;  &lt;p&gt;As we mentioned previously, the algorithm that governs the ghosts' moving is the heart of this program. For the purpose of testing the ghosts' animation, for now, we apply a "random" moving algorithm, ie. the ghosts run arbitrarily inside the maze. In next article, we will implement a more complex algorithm. The function &lt;b&gt;&lt;tt&gt;changeDirectionYtoX( Boolean )&lt;/tt&gt;&lt;/b&gt; and &lt;b&gt;&lt;tt&gt;changeDirectionXtoY( Boolean )&lt;/tt&gt;&lt;/b&gt; give out decisions of whether a ghost should keep its current direction, or make a left or right turn. For illustration, let's take an  in-depth look at the function &lt;b&gt;&lt;tt&gt;changeDirectionYtoX( Boolean )&lt;/tt&gt;&lt;/b&gt;.  &lt;/p&gt; &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// decide whether to change the current direction of a ghost&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; changeDirectionYtoX(mustChange: &lt;span class="category2"&gt;Boolean&lt;/span&gt;): Void {&lt;br /&gt;&lt;br /&gt;   &lt;span class="category1"&gt;if&lt;/span&gt; ( &lt;span class="category1"&gt;not&lt;/span&gt; mustChange &lt;span class="category1"&gt;and&lt;/span&gt; &lt;span class="category2"&gt;Math&lt;/span&gt;.&lt;span class="category2"&gt;random&lt;/span&gt;() &gt; changeFactor )&lt;br /&gt;     &lt;span class="category1"&gt;return&lt;/span&gt;;  &lt;span class="linecomment"&gt;// no change of direction&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;   &lt;span class="linecomment"&gt;// will change to a X direction if possible&lt;/span&gt;&lt;br /&gt;   &lt;span class="category1"&gt;var&lt;/span&gt; goLeft = MoveDecision {&lt;br /&gt;      &lt;span class="category2"&gt;x&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;x&lt;/span&gt; - 1&lt;br /&gt;      &lt;span class="category2"&gt;y&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;y&lt;/span&gt;&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;   &lt;span class="category1"&gt;var&lt;/span&gt; goRight = MoveDecision {&lt;br /&gt;      &lt;span class="category2"&gt;x&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;x&lt;/span&gt; + 1&lt;br /&gt;      &lt;span class="category2"&gt;y&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;y&lt;/span&gt;&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;   &lt;span class="linecomment"&gt;// evaluate the moving choices to pick the best one&lt;/span&gt;&lt;br /&gt;   goLeft.&lt;span class="category1"&gt;evaluate&lt;/span&gt;();&lt;br /&gt;   goRight.&lt;span class="category1"&gt;evaluate&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;   &lt;span class="category1"&gt;if&lt;/span&gt; ( goLeft.score &lt; class="category1"&gt;and goRight.score &lt; class="category1"&gt;return&lt;/span&gt;;  &lt;span class="linecomment"&gt;// no change of direction&lt;/span&gt;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;   &lt;span class="category1"&gt;var&lt;/span&gt; continueGo = MoveDecision {&lt;br /&gt;      &lt;span class="category2"&gt;x&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;x&lt;/span&gt;&lt;br /&gt;      &lt;span class="category2"&gt;y&lt;/span&gt;: &lt;span class="category1"&gt;this&lt;/span&gt;.&lt;span class="category2"&gt;y&lt;/span&gt; + yDirection&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;   continueGo.&lt;span class="category1"&gt;evaluate&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;   &lt;span class="category1"&gt;if&lt;/span&gt; ( continueGo.score &gt; 0 &lt;span class="category1"&gt;and&lt;/span&gt; continueGo.score &gt; goLeft.score&lt;br /&gt;        &lt;span class="category1"&gt;and&lt;/span&gt; continueGo.score &gt; goRight.score ) {&lt;br /&gt;      &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;   &lt;span class="linecomment"&gt;// make it goes up first, then decide if we need to change it to down&lt;/span&gt;&lt;br /&gt;   &lt;span class="category1"&gt;var&lt;/span&gt; decision = -1;&lt;br /&gt;   &lt;span class="category1"&gt;if&lt;/span&gt; ( goLeft.score  &lt; decision =" 1" class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;     &lt;span class="category1"&gt;if&lt;/span&gt; ( goRight.score &gt; 0 ) {&lt;br /&gt;        &lt;span class="linecomment"&gt;// random pick&lt;/span&gt;&lt;br /&gt;        &lt;span class="category1"&gt;if&lt;/span&gt; ( &lt;span class="category2"&gt;Math&lt;/span&gt;.&lt;span class="category2"&gt;random&lt;/span&gt;() &gt; 0.5 )&lt;br /&gt;          decision = 1;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;   xDirection=decision;&lt;br /&gt;   yDirection = 0;&lt;br /&gt; }&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;     When a ghost is moving vertically, this function determines the next direction. Possible decisions include: turning left, turning right, and continue with the current direction. A class &lt;b&gt;&lt;tt&gt;MoveDecision&lt;/tt&gt;&lt;/b&gt; is used to model a tentative decision. See the below code:  &lt;/p&gt; &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/*&lt;br /&gt;* MoveDecision.fx&lt;br /&gt;*&lt;br /&gt;* Created on 2009-1-28, 14:42:00&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;package pacman;&lt;br /&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/**&lt;br /&gt;* @author Henry Zhang&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; MoveDecision {&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// x and y of an intended move&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt;: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; &lt;span class="category2"&gt;y&lt;/span&gt;: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; score: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// evaluate if the move is valid,&lt;/span&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// if it is invalid, returns -1;&lt;/span&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// if it is valid, compute its score for ranking the final decision&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; &lt;span class="category1"&gt;evaluate&lt;/span&gt;( ):Void {&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( &lt;span class="category2"&gt;x&lt;/span&gt; &lt; class="category1"&gt;or &lt;span class="category2"&gt;y&lt;/span&gt; &lt; class="category1"&gt;or &lt;span class="category2"&gt;y&lt;/span&gt; &gt;= MazeData.GRID_SIZE &lt;span class="category1"&gt;or&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt; &gt;= MazeData.GRID_SIZE){&lt;br /&gt;       score = -1;&lt;br /&gt;       &lt;span class="category1"&gt;return&lt;/span&gt; ;&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;var&lt;/span&gt; &lt;span class="category2"&gt;status&lt;/span&gt; = MazeData.&lt;span class="category2"&gt;getData&lt;/span&gt;(&lt;span class="category2"&gt;x&lt;/span&gt;, &lt;span class="category2"&gt;y&lt;/span&gt;);&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( &lt;span class="category2"&gt;status&lt;/span&gt; == MazeData.BLOCK ) {&lt;br /&gt;       score = -1;&lt;br /&gt;       &lt;span class="category1"&gt;return&lt;/span&gt; ;&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;    &lt;span class="linecomment"&gt;// rank it as a default score&lt;/span&gt;&lt;br /&gt;    score = 1;&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;  &lt;p&gt;The &lt;b&gt;&lt;tt&gt;evaluate()&lt;/tt&gt;&lt;/b&gt; function evaluates a moving decision and gives a score.  A ghost simply picks the decision with highest ranking score. In a random moving algorithm, all decisions are given an equal score 1. If a move leads the ghost to hitting a wall, the ranking score is (-1), which automatically eliminates it from being a candidate decision. If a ghost reaches a wall, the argument &lt;b&gt;&lt;tt&gt;mustChange&lt;/tt&gt;&lt;/b&gt; of &lt;b&gt;&lt;tt&gt;changeDirectionYtoX(Boolean)&lt;/tt&gt;&lt;/b&gt; is set so that a ghost always gets a change of direction. If this argument is false, the decision to change direction is affected by a random factor &lt;b&gt;&lt;tt&gt;changeFactor&lt;/tt&gt;&lt;/b&gt;. This allows the moving behavior of a ghosts more unpredictable, hence the player cannot guess the moving pattern of a ghost. The &lt;b&gt;&lt;tt&gt;changeDirectionXtoY(Boolean)&lt;/tt&gt;&lt;/b&gt; has a similar logic and it determines the moving decision when a ghost is going horizontally.  &lt;/p&gt;  &lt;p&gt;&lt;b&gt;Running the Game&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;Now we are ready to put things together and have some fun running the program.  We add in some code to &lt;b&gt;&lt;tt&gt;Maze.fx&lt;/tt&gt;&lt;/b&gt;, putting four ghosts on stage:  &lt;/p&gt; &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; Maze &lt;span class="category1"&gt;extends&lt;/span&gt; CustomNode {&lt;br /&gt;&lt;br /&gt; . . . . .&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; ghostBlinky = Ghost {&lt;br /&gt;    defaultImage1: Image {&lt;br /&gt;       &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/ghostred1.png&lt;/span&gt;"&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;    defaultImage2: Image {&lt;br /&gt;       &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/ghostred2.png&lt;/span&gt;"&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;     maze: &lt;span class="category1"&gt;this&lt;/span&gt;&lt;br /&gt;     pacMan: pacMan&lt;br /&gt;     &lt;span class="category2"&gt;x&lt;/span&gt;: 17&lt;br /&gt;     &lt;span class="category2"&gt;y&lt;/span&gt;: 15&lt;br /&gt;     xDirection: 0&lt;br /&gt;     yDirection: -1&lt;br /&gt;     trapTime: 1&lt;br /&gt;    };&lt;br /&gt;&lt;br /&gt;  &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; ghostPinky = Ghost {&lt;br /&gt;     defaultImage1:Image {&lt;br /&gt;         &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/ghostpink1.png&lt;/span&gt;"&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;     defaultImage2:Image {&lt;br /&gt;        &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/ghostpink2.png&lt;/span&gt;"&lt;br /&gt;      }&lt;br /&gt;   &lt;br /&gt;     maze: &lt;span class="category1"&gt;this&lt;/span&gt;&lt;br /&gt;     pacMan: pacMan&lt;br /&gt;     &lt;span class="category2"&gt;x&lt;/span&gt;: 12&lt;br /&gt;     &lt;span class="category2"&gt;y&lt;/span&gt;: 14&lt;br /&gt;     xDirection: 0&lt;br /&gt;     yDirection: 1&lt;br /&gt;     trapTime: 10&lt;br /&gt;   };&lt;br /&gt;&lt;br /&gt;  &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; ghostInky = Ghost {&lt;br /&gt;     defaultImage1:Image {&lt;br /&gt;        &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/ghostcyan1.png&lt;/span&gt;"&lt;br /&gt;      }&lt;br /&gt;     defaultImage2:Image {&lt;br /&gt;        &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/ghostcyan2.png&lt;/span&gt;"&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;     maze: &lt;span class="category1"&gt;this&lt;/span&gt;&lt;br /&gt;     pacMan: pacMan&lt;br /&gt;     &lt;span class="category2"&gt;x&lt;/span&gt;: 13&lt;br /&gt;     &lt;span class="category2"&gt;y&lt;/span&gt;: 15&lt;br /&gt;     xDirection: 1&lt;br /&gt;     yDirection: 0&lt;br /&gt;     trapTime: 40&lt;br /&gt;   };&lt;br /&gt;&lt;br /&gt;  &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; ghostClyde = Ghost {&lt;br /&gt;     defaultImage1:Image {&lt;br /&gt;         &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/ghostorange1.png&lt;/span&gt;"&lt;br /&gt;      }&lt;br /&gt;     defaultImage2:Image {&lt;br /&gt;        &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/ghostorange2.png&lt;/span&gt;"&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;     maze: &lt;span class="category1"&gt;this&lt;/span&gt;&lt;br /&gt;     pacMan: pacMan&lt;br /&gt;     &lt;span class="category2"&gt;x&lt;/span&gt;: 15&lt;br /&gt;     &lt;span class="category2"&gt;y&lt;/span&gt;: 14&lt;br /&gt;     xDirection: -1&lt;br /&gt;     yDirection: 0&lt;br /&gt;     trapTime: 60&lt;br /&gt;   };&lt;br /&gt;&lt;br /&gt;  &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; ghosts = [ghostBlinky, ghostPinky, ghostInky, ghostClyde];&lt;br /&gt;&lt;br /&gt;   . . . . . .&lt;br /&gt;&lt;br /&gt;  postinit {&lt;br /&gt;&lt;br /&gt;    . . . . . .&lt;br /&gt;&lt;br /&gt;     insert pacMan into group.content;&lt;br /&gt;&lt;br /&gt;     insert ghosts into group.content;&lt;br /&gt;&lt;br /&gt;     insert WallBlackRectangle{ x1:-3, y1:13, x2:0, y2:15 } into group.content;&lt;br /&gt;     insert WallBlackRectangle{ x1:29, y1:13, x2:31, y2:15 } into group.content;&lt;br /&gt;   }&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;    &lt;p&gt;Run the program and you can see four ghosts roaming the maze. You can control the Pac-Man character by keyboard to eat dots. However, the ghosts cannot eat the Pac-Man even they meet each other. We will implement this  part in next article. Click on the below screenshot and see it for yourself:  &lt;/p&gt;  &lt;p&gt;&lt;a href="http://www.javafxgame.com/v7/pacman.jnlp"&gt; &lt;img src="http://www.insideria.com/upload/2009/05/maze8.png" border="0" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.insideria.com/upload/2009/05/launch.gif" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://www.insideria.com/upload/2009/05/javafxsource3.zip"&gt;Download Source Code&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;&lt;p align="right"&gt;&lt;span style="font-weight: bold;"&gt;www.insideria.com&lt;/span&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4004158710435644696-5835082698913690873?l=eswebsoft.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/5835082698913690873/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/writing-pac-man-game-in-javafx-part-3.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/5835082698913690873'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/5835082698913690873'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/writing-pac-man-game-in-javafx-part-3.html' title='Writing the Pac-Man Game in JavaFX - Part 3'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4004158710435644696.post-6853226021561570632</id><published>2009-08-02T17:17:00.002+07:00</published><updated>2009-08-02T17:20:48.247+07:00</updated><title type='text'>Writing the Pac-Man Game in JavaFX - Part 2</title><content type='html'>&lt;p&gt;In the &lt;a href="http://eswebsoft.blogspot.com/2009/08/writing-pac-man-game-in-javafx-part-1.html"&gt;last article&lt;/a&gt;, we designed a data model and drew a maze with dots spread into the maze. Now we are ready to create the Pac-Man character. The Pac-Man character is controlled by the game player to move  around the maze. While he is moving, he keeps gobbling dots along the path. To implement the Pac-Man character, we divide the coding into a few tasks so that we can create it bit by bit:  &lt;/p&gt;&lt;ol&gt;&lt;li&gt;Basic animation: the Pac-Man character continually open and close mouth, but he does not move&lt;/li&gt;&lt;li&gt;Moving animation: the Pac-Man character moves inside the maze&lt;/li&gt;&lt;li&gt;Player Controlling: the player controls the moving direction of the Pac-Man character&lt;/li&gt;&lt;li&gt;Gobbling dots: the Pac-Man gobbles dots&lt;/li&gt;&lt;/ol&gt;   &lt;p&gt;&lt;b&gt;Basic Animation&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;Let's start from the simplest thing first. We create the basic animation of the Pac-Man character.  The Pac-Man character at this phase does not move but can keep opening  and closing his mouth. The &lt;b&gt;&lt;tt&gt;javafx.animation&lt;/tt&gt;&lt;/b&gt; package, part of the JavaFX API, provides  the easy-to-use functionality for animation. We are going to use the &lt;b&gt;&lt;tt&gt;Timeline&lt;/tt&gt;&lt;/b&gt; class to implement the animation. During an animation, properties such as speed, shape, color and location  are constantly changing to achieve the desired behavior.  The &lt;b&gt;&lt;tt&gt;Timeline&lt;/tt&gt;&lt;/b&gt; class allows us to update the values of animation properties along the progression of time. The &lt;b&gt;&lt;tt&gt;Timeline.Keyframes&lt;/tt&gt;&lt;/b&gt; attribute can be used to define the order of frames. We create four pictures shown below for Pac-Man's animation:&lt;/p&gt;  &lt;p&gt;&lt;img src="http://www.insideria.com/upload/2009/05/fourframes.png" /&gt; &lt;/p&gt;  &lt;p&gt; When we keep switching the above pictures(frames) of the Pac-Man character, it generates the animation effect of opening and closing the mouth. We are going to write two classes:  &lt;b&gt;MovingObject.fx&lt;/b&gt; and &lt;b&gt;PacMan.fx&lt;/b&gt;. The &lt;b&gt;&lt;tt&gt;MovingObject&lt;/tt&gt;&lt;/b&gt; class abstracts some common attributes that we could later use to implement the &lt;b&gt;&lt;tt&gt;Ghost&lt;/tt&gt;&lt;/b&gt; class. The &lt;b&gt;&lt;tt&gt;PacMan&lt;/tt&gt;&lt;/b&gt; class extends &lt;b&gt;&lt;tt&gt;MovingObject&lt;/tt&gt;&lt;/b&gt; to display the Pac-Man character. Here is the code:  &lt;/p&gt; &lt;p&gt;&lt;b&gt;MovingObject.fx:&lt;/b&gt;  &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/*&lt;br /&gt;* MovingObject.fx&lt;br /&gt;*&lt;br /&gt;* Created on 2009-1-1, 11:40:49&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;package pacman;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.animation.KeyFrame;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.animation.Timeline;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; pacman.Maze;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; pacman.MazeData;&lt;br /&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/**&lt;br /&gt;* @author Henry Zhang&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; abstract &lt;span class="category1"&gt;class&lt;/span&gt; MovingObject {&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// animation frames total and movement distance&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; def ANIMATION_STEP=4;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; def MOVE_SPEED = MazeData.GRID_GAP / ANIMATION_STEP;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; def MOVING = 1;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; def STOP =0;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; def MOVE_LEFT=0;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; def MOVE_UP=1;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; def MOVE_RIGHT=2;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; def MOVE_DOWN=3;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; maze: Maze;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; state : Integer;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; currentImage=0;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; moveCounter: Integer=0;&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// grid coordinates&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt;: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; &lt;span class="category2"&gt;y&lt;/span&gt;: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// graphical coordinates&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; imageX: &lt;span class="category2"&gt;Number&lt;/span&gt; ;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; imageY: &lt;span class="category2"&gt;Number&lt;/span&gt; ;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; xDirection: &lt;span class="category2"&gt;Number&lt;/span&gt; = 0;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; yDirection: &lt;span class="category2"&gt;Number&lt;/span&gt; = 0;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; timeline: Timeline =  createTimeline();&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; &lt;span class="category2"&gt;stop&lt;/span&gt;() {&lt;br /&gt;  timeline.&lt;span class="category2"&gt;stop&lt;/span&gt;();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; &lt;span class="category2"&gt;pause&lt;/span&gt;() {&lt;br /&gt;  timeline.&lt;span class="category2"&gt;pause&lt;/span&gt;();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; &lt;span class="category2"&gt;start&lt;/span&gt;() {&lt;br /&gt;  timeline.&lt;span class="category2"&gt;play&lt;/span&gt;();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// animation time line, moving the pacman&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; createTimeline(): Timeline {&lt;br /&gt;  Timeline {&lt;br /&gt;     repeatCount: Timeline.INDEFINITE&lt;br /&gt;     keyFrames: [&lt;br /&gt;       KeyFrame {&lt;br /&gt;          &lt;span class="category2"&gt;time&lt;/span&gt;: 250ms&lt;br /&gt;          action: &lt;span class="category1"&gt;function&lt;/span&gt;() {&lt;br /&gt;             moveOneStep();&lt;br /&gt;           }&lt;br /&gt;        }&lt;br /&gt;     ]&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; abstract &lt;span class="category1"&gt;function&lt;/span&gt; moveOneStep(): Void;&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;&lt;b&gt;PacMan.fx:&lt;/b&gt;  &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/*&lt;br /&gt;* PacMan.fx&lt;br /&gt;*&lt;br /&gt;* Created on 2009-1-1, 11:50:58&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;package pacman;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.CustomNode;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.image.Image;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.image.ImageView;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.Node;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; pacman.MazeData;&lt;br /&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/**&lt;br /&gt;* @author Henry Zhang&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; PacMan &lt;span class="category1"&gt;extends&lt;/span&gt; CustomNode, MovingObject {&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; defaultImage: Image = Image {&lt;br /&gt;  &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/left1.png&lt;/span&gt;"&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// images for animation&lt;/span&gt;&lt;br /&gt;def images = [&lt;br /&gt; defaultImage,&lt;br /&gt; Image {&lt;br /&gt;    &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/left2.png&lt;/span&gt;"&lt;br /&gt;  },&lt;br /&gt; defaultImage,&lt;br /&gt; Image {&lt;br /&gt;    &lt;span class="category2"&gt;url&lt;/span&gt;: "&lt;span class="quote"&gt;{__DIR__}images/round.png&lt;/span&gt;"&lt;br /&gt;  }&lt;br /&gt;];&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// GUI image of the man&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;var&lt;/span&gt; pacmanImage : ImageView = ImageView {&lt;br /&gt;  &lt;span class="category2"&gt;x&lt;/span&gt;: bind imageX  - 13&lt;br /&gt;  &lt;span class="category2"&gt;y&lt;/span&gt;: bind imageY  - 13&lt;br /&gt;  image: bind images[currentImage]&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;postinit {&lt;br /&gt;  imageX = MazeData.calcGridX(&lt;span class="category2"&gt;x&lt;/span&gt;);&lt;br /&gt;  imageY = MazeData.calcGridX(&lt;span class="category2"&gt;y&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;  state = MOVING;&lt;br /&gt;  &lt;span class="category2"&gt;start&lt;/span&gt;();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;function&lt;/span&gt; create(): Node {&lt;br /&gt;  &lt;span class="category1"&gt;return&lt;/span&gt; pacmanImage;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// handle animation of one tick&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;function&lt;/span&gt; moveOneStep() {&lt;br /&gt;&lt;br /&gt;  &lt;span class="category1"&gt;if&lt;/span&gt; ( state == MOVING) {&lt;br /&gt;&lt;br /&gt;     &lt;span class="linecomment"&gt;// switch to the image of the next frame&lt;/span&gt;&lt;br /&gt;     &lt;span class="category1"&gt;if&lt;/span&gt; ( currentImage &lt; class="category1"&gt;else {&lt;br /&gt;        currentImage=0;&lt;br /&gt;      }&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;The &lt;b&gt;&lt;tt&gt;MovingObject&lt;/tt&gt;&lt;/b&gt; class defines an abstract function &lt;b&gt;&lt;tt&gt;moveOneStep()&lt;/tt&gt;&lt;/b&gt; which is called every 200 millisecond. Subclasses should implement this function to create frames of the animation. The &lt;b&gt;&lt;tt&gt;PacMan&lt;/tt&gt;&lt;/b&gt; class extends both the  &lt;b&gt;&lt;tt&gt;CustomNode&lt;/tt&gt;&lt;/b&gt; and &lt;b&gt;&lt;tt&gt;MovingObject&lt;/tt&gt;&lt;/b&gt; classes. In Java, a class can implement a few interfaces. In JavaFX's grammar, there is no interface, so multiple inheritance is used here. The attribute &lt;b&gt;&lt;tt&gt;images&lt;/tt&gt;&lt;/b&gt; is a sequence containing four pictures of the animation frames.  When the function &lt;b&gt;&lt;tt&gt;moveOneStep()&lt;/tt&gt;&lt;/b&gt; is invoked every 200ms, the value of the attribute &lt;b&gt;&lt;tt&gt;pacmanImage&lt;/tt&gt;&lt;/b&gt;  is rotated to the next picture in the sequence. In this way, the animation of Pac-Man's opening and closing his  mouth is accomplished. &lt;/p&gt;  &lt;p&gt;Let's add in some code to the &lt;b&gt;&lt;tt&gt;Maze&lt;/tt&gt;&lt;/b&gt; class so that we can see the result of our animation. First,  add a statement to create an instance of &lt;b&gt;&lt;tt&gt;PacMan&lt;/tt&gt;&lt;/b&gt;: &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; Maze &lt;span class="category1"&gt;extends&lt;/span&gt; CustomNode {&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// Pac Man Character&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; pacMan : PacMan = PacMan{ maze:&lt;span class="category1"&gt;this&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt;:23 &lt;span class="category2"&gt;y&lt;/span&gt;:5};&lt;br /&gt;&lt;br /&gt;. . . .&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   Then in the postinit block, we put the &lt;b&gt;&lt;tt&gt;PacMan&lt;/tt&gt;&lt;/b&gt; instance into the maze:  &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;postinit {&lt;br /&gt;&lt;br /&gt; . . . . .&lt;br /&gt;&lt;br /&gt; &lt;b&gt;insert pacMan into group.content;&lt;/b&gt;&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   Now, let's run the program and you can see that the Pac-Man character keeps biting. For illustration and testing purpose, we set an interval of 200ms between frames. This is a relatively large interval and it is kind of slow for playing. We will reduce this interval a bit later as we move forward. Click on the below image to view how the program runs so far:  &lt;p&gt;&lt;a href="http://www.javafxgame.com/v3/pacman.jnlp"&gt; &lt;img src="http://www.insideria.com/upload/2009/05/maze4.png" border="0" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.insideria.com/upload/2009/05/launch.gif" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;b&gt;Animation of Pac-Man Moving&lt;/b&gt;&lt;/p&gt; &lt;p&gt;We now can make the Pac-Man character moving inside the maze. First, let me explain the purpose of a pair of variables in  the &lt;b&gt;&lt;tt&gt;MovingObject&lt;/tt&gt;&lt;/b&gt; class: &lt;b&gt;&lt;tt&gt;xDirection&lt;/tt&gt;&lt;/b&gt; and &lt;b&gt;&lt;tt&gt;yDirection&lt;/tt&gt;&lt;/b&gt;. They are used to store the horizontal and vertical direction of a character. See below table: &lt;/p&gt; &lt;p&gt; &lt;/p&gt;&lt;table border="1"&gt; &lt;tbody&gt;&lt;tr&gt; &lt;td width="200"&gt;&lt;b&gt;Moving Direction&lt;/b&gt;&lt;/td&gt;&lt;td width="100"&gt;&lt;b&gt;&lt;tt&gt;xDirection&lt;/tt&gt;&lt;/b&gt;&lt;/td&gt; &lt;td width="100"&gt;&lt;b&gt;&lt;tt&gt;yDirection&lt;/tt&gt;&lt;/b&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Left&lt;/td&gt;&lt;td&gt;-1&lt;/td&gt;&lt;td&gt;0&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Right&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Up&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;-1&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Down&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;1&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt; Since we have four frames for a cycle of animation,  we can change the position (i.e. x or y coordinates) of the character when we update the picture of a frame. So a constant variable &lt;b&gt;&lt;tt&gt;MOVE_SPEED&lt;/tt&gt;&lt;/b&gt;, the moving speed of a character is defined in the &lt;b&gt;&lt;tt&gt;MovingObject&lt;/tt&gt;&lt;/b&gt; class and is computed as: &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; def ANIMATION_STEP=4;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; def MOVE_SPEED = MazeData.GRID_GAP / ANIMATION_STEP;&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;  During every animation clock cycle, we update the x or y coordinates of a character by a delta of &lt;b&gt;&lt;tt&gt;MOVE_SPEED&lt;/tt&gt;&lt;/b&gt;. After 4 clock cycles, the Pac-Man character moves to the next point of the grid either horizontally or vertically. Based on this algorithm, we add two functions &lt;b&gt;&lt;tt&gt;moveHorizontally()&lt;/tt&gt;&lt;/b&gt; and &lt;b&gt;&lt;tt&gt;moveVertically()&lt;/tt&gt;&lt;/b&gt; into  &lt;b&gt;&lt;tt&gt;PacMan&lt;/tt&gt;&lt;/b&gt; class.  &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; PacMan &lt;span class="category1"&gt;extends&lt;/span&gt; CustomNode, MovingObject {&lt;br /&gt;&lt;br /&gt;. . . . . .&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// moving horizontally&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; moveHorizontally() {&lt;br /&gt;&lt;br /&gt;  moveCounter++;&lt;br /&gt;&lt;br /&gt;  &lt;span class="category1"&gt;if&lt;/span&gt; ( moveCounter &lt; class="category1"&gt;else {&lt;br /&gt;     moveCounter = 0;&lt;br /&gt;     &lt;span class="category2"&gt;x&lt;/span&gt; += xDirection;&lt;br /&gt; &lt;br /&gt;     imageX = MazeData.calcGridX(&lt;span class="category2"&gt;x&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;     &lt;span class="linecomment"&gt;// the X coordinate of the next point in the grid&lt;/span&gt;&lt;br /&gt;     &lt;span class="category1"&gt;var&lt;/span&gt; nextX = xDirection + &lt;span class="category2"&gt;x&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;     &lt;span class="linecomment"&gt;// check if the character hits a wall&lt;/span&gt;&lt;br /&gt;     &lt;span class="category1"&gt;if&lt;/span&gt; ( MazeData.&lt;span class="category2"&gt;getData&lt;/span&gt;(nextX, &lt;span class="category2"&gt;y&lt;/span&gt;) == MazeData.BLOCK ) {&lt;br /&gt;        state = STOP;&lt;br /&gt;      }&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// moving vertically&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; moveVertically() {&lt;br /&gt;&lt;br /&gt;  moveCounter++;&lt;br /&gt;&lt;br /&gt;  &lt;span class="category1"&gt;if&lt;/span&gt; ( moveCounter &lt; class="category1"&gt;else {&lt;br /&gt;     moveCounter = 0;&lt;br /&gt;     &lt;span class="category2"&gt;y&lt;/span&gt; += yDirection;&lt;br /&gt;     imageY = MazeData.calcGridX(&lt;span class="category2"&gt;y&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;     &lt;span class="linecomment"&gt;// the Y coordinate of the next point in the grid&lt;/span&gt;&lt;br /&gt;     &lt;span class="category1"&gt;var&lt;/span&gt; nextY = yDirection + &lt;span class="category2"&gt;y&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;     &lt;span class="linecomment"&gt;// check if the character hits a wall&lt;/span&gt;&lt;br /&gt;     &lt;span class="category1"&gt;if&lt;/span&gt; ( MazeData.&lt;span class="category2"&gt;getData&lt;/span&gt;(&lt;span class="category2"&gt;x&lt;/span&gt;, nextY) == MazeData.BLOCK ) {&lt;br /&gt;        state = STOP;&lt;br /&gt;      }&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;. . . . . .&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;  &lt;p&gt; The two functions are similar to each other, so let's take a look at the function &lt;b&gt;&lt;tt&gt;moveHorizontally()&lt;/tt&gt;&lt;/b&gt;. When the character's position is between two points of the grid, we use this statement to move it: &lt;/p&gt;  &lt;p&gt; &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;imageX += xDirection * MOVE_SPEED;&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;  &lt;p&gt; When the character reaches a point, we check whether it hits a wall of the maze. If it does, we make it stop: &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// check if the character hits a wall&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;if&lt;/span&gt; ( MazeData.&lt;span class="category2"&gt;getData&lt;/span&gt;(nextX, &lt;span class="category2"&gt;y&lt;/span&gt;) == MazeData.BLOCK ) {&lt;br /&gt; state = STOP;&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt; Now, we can write some codes to test the moving of the Pac-man character. In &lt;b&gt;&lt;tt&gt;PacMan&lt;/tt&gt;&lt;/b&gt; class,  we add in the moving code in the function &lt;b&gt;&lt;tt&gt;moveOnetStep()&lt;/tt&gt;&lt;/b&gt;:  &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// handle animation of one tick&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;function&lt;/span&gt; moveOneStep() {&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( state == MOVING) {&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( xDirection != 0 )&lt;br /&gt;      moveHorizontally();&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( yDirection != 0 )&lt;br /&gt;      moveVertically();&lt;br /&gt;&lt;br /&gt;    &lt;span class="linecomment"&gt;// switch to the image of the next frame&lt;/span&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( currentImage &lt; class="category1"&gt;else {&lt;br /&gt;       currentImage=0;&lt;br /&gt;     }&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   In the &lt;b&gt;&lt;tt&gt;postinit&lt;/tt&gt;&lt;/b&gt; block, we set the initial direction of the pac-man character:   &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;postinit {&lt;br /&gt; imageX = MazeData.calcGridX(&lt;span class="category2"&gt;x&lt;/span&gt;);&lt;br /&gt; imageY = MazeData.calcGridX(&lt;span class="category2"&gt;y&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt; xDirection = -1;&lt;br /&gt; yDirection = 0;&lt;br /&gt;&lt;br /&gt; state = MOVING;&lt;br /&gt; &lt;span class="category2"&gt;start&lt;/span&gt;();&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;Run the program now and we can see the Pac-Man moving to the left and stop at the border of the maze.  &lt;/p&gt;&lt;p&gt;&lt;a href="http://www.javafxgame.com/v4/pacman.jnlp"&gt; &lt;img src="http://www.insideria.com/upload/2009/05/maze5.png" border="0" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.insideria.com/upload/2009/05/launch.gif" border="0" /&gt;&lt;/a&gt; &lt;/p&gt; &lt;p&gt;You can try other moving directions by changing the values of &lt;b&gt;&lt;tt&gt;xDirection&lt;/tt&gt;&lt;/b&gt; and &lt;b&gt;&lt;tt&gt;yDirection&lt;/tt&gt;&lt;/b&gt;  in the &lt;b&gt;&lt;tt&gt;postinit&lt;/tt&gt;&lt;/b&gt; block. Refer to the table in previous section for possible combinations. Since we have not yet handled the part to turn the Pac-man's mouth, he always faces to  the left no matter which direction he moves in.  &lt;/p&gt; &lt;p&gt; One last thing is to deal with a special case in the Pac-man's passing through the tunnel. The Pac-man character can walk into the tunnel to reach the other side of the maze. We put in some handling in function &lt;b&gt;&lt;tt&gt;moveHorizontally()&lt;/tt&gt;&lt;/b&gt; for this purpose.   &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; moveHorizontally() {&lt;br /&gt;&lt;br /&gt; moveCounter++;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( moveCounter &lt; class="category1"&gt;else {&lt;br /&gt;    moveCounter = 0;&lt;br /&gt;    &lt;span class="category2"&gt;x&lt;/span&gt; += xDirection;&lt;br /&gt;&lt;br /&gt;    imageX = MazeData.calcGridX(&lt;span class="category2"&gt;x&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;    &lt;span class="linecomment"&gt;// the X coordinate of the next point in the grid&lt;/span&gt;&lt;br /&gt;    &lt;span class="category1"&gt;var&lt;/span&gt; nextX = xDirection + &lt;span class="category2"&gt;x&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( &lt;span class="category2"&gt;y&lt;/span&gt; == 14 &lt;span class="category1"&gt;and&lt;/span&gt; ( nextX &lt;= 1 &lt;span class="category1"&gt;or&lt;/span&gt; nextX &gt;= 28) ) {&lt;br /&gt;       &lt;span class="category1"&gt;if&lt;/span&gt; ( nextX &lt; -1 &lt;span class="category1"&gt;and&lt;/span&gt; xDirection &lt; class="category2"&gt;x = MazeData.GRID_SIZE;&lt;br /&gt;          imageX = MazeData.calcGridX(&lt;span class="category2"&gt;x&lt;/span&gt;);&lt;br /&gt;        }&lt;br /&gt;       &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;         &lt;span class="category1"&gt;if&lt;/span&gt; ( nextX &gt; 30 &lt;span class="category1"&gt;and&lt;/span&gt; xDirection &gt; 0) {&lt;br /&gt;            &lt;span class="category2"&gt;x&lt;/span&gt; = 0;&lt;br /&gt;            imageX = MazeData.calcGridX(&lt;span class="category2"&gt;x&lt;/span&gt;);&lt;br /&gt;           }&lt;br /&gt;     }&lt;br /&gt;    &lt;span class="category1"&gt;else&lt;/span&gt; &lt;span class="linecomment"&gt;// check if the character hits a wall&lt;/span&gt;&lt;br /&gt;      &lt;span class="category1"&gt;if&lt;/span&gt; ( MazeData.&lt;span class="category2"&gt;getData&lt;/span&gt;(nextX, &lt;span class="category2"&gt;y&lt;/span&gt;) == MazeData.BLOCK ) {&lt;br /&gt;         state = STOP;&lt;br /&gt;     }&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   In &lt;b&gt;Maze.fx&lt;/b&gt;, we add two &lt;b&gt;&lt;tt&gt;WallBlackRectangle&lt;/tt&gt;&lt;/b&gt; objects to create the clipping effect of the Pac-man passing the tunnel. &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;postinit {&lt;br /&gt;&lt;br /&gt;. . . . .&lt;br /&gt;&lt;br /&gt; insert pacMan into group.content;&lt;br /&gt; insert WallBlackRectangle{ x1:-3, y1:13, x2:0, y2:15} into group.content;&lt;br /&gt; insert WallBlackRectangle{ x1:29, y1:13, x2:31, y2:15} into group.content;&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;Then we can test this code by placing the Pac-Man character at the position (5,14):  &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; pacMan : PacMan = PacMan{ maze:&lt;span class="category1"&gt;this&lt;/span&gt; &lt;b&gt;&lt;span class="category2"&gt;x&lt;/span&gt;:5 &lt;span class="category2"&gt;y&lt;/span&gt;:14&lt;/b&gt; };&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   So far, we completed the moving part of the Pac-man character.   &lt;p&gt;&lt;b&gt;Player's Controlling&lt;/b&gt;&lt;/p&gt; &lt;p&gt;Now, we start to work on the player's keyboard controlling. In &lt;b&gt;&lt;tt&gt;PacMan&lt;/tt&gt;&lt;/b&gt; class, we define two attributes:   &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// buffer to keep the keyboard input&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;var&lt;/span&gt; keyboardBuffer: Integer = -1;&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// current direction of Pacman&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;var&lt;/span&gt; currentDirection: Integer = MOVE_LEFT;&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;The &lt;b&gt;&lt;tt&gt;keyboardBuffer&lt;/tt&gt;&lt;/b&gt; attribute is used to store the keyboard input(keypress event). It will be consumed when the pac-man character's position is valid for the turn. Buffered keyboard input can be overwritten by subsequent keypressed event. For this reason, an experienced player usually presses a key well before the Pac-man reaches a turning point.&lt;/p&gt;  &lt;p&gt;The &lt;b&gt;&lt;tt&gt;currentDirection&lt;/tt&gt;&lt;/b&gt; is an attribute to determine which direction the pac-man faces to. &lt;/p&gt;  &lt;p&gt;In &lt;b&gt;PacMan.fx&lt;/b&gt;, we create a few functions as below.  The function &lt;b&gt;&lt;tt&gt;moveRight()&lt;/tt&gt;&lt;/b&gt;, &lt;b&gt;&lt;tt&gt;moveLeft()&lt;/tt&gt;&lt;/b&gt;, &lt;b&gt;&lt;tt&gt;moveUp()&lt;/tt&gt;&lt;/b&gt; and &lt;b&gt;&lt;tt&gt;moveDown()&lt;/tt&gt;&lt;/b&gt; are to change the direction of the Pac-Man character based on keyboard events.  &lt;/p&gt; &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// turn pac-man to the right&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; moveRight(): Void {&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( currentDirection == MOVE_RIGHT ) &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;var&lt;/span&gt; nextX = &lt;span class="category2"&gt;x&lt;/span&gt; + 1;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( nextX &gt;= MazeData.GRID_SIZE) &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( MazeData.&lt;span class="category2"&gt;getData&lt;/span&gt;(nextX, &lt;span class="category2"&gt;y&lt;/span&gt;) == MazeData.BLOCK ) &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; xDirection = 1;&lt;br /&gt; yDirection = 0;&lt;br /&gt;&lt;br /&gt; keyboardBuffer = -1;&lt;br /&gt; currentDirection = MOVE_RIGHT;&lt;br /&gt;&lt;br /&gt; state = MOVING;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// turn pac-man to the left&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; moveLeft(): Void {&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( currentDirection == MOVE_LEFT ) &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;var&lt;/span&gt; nextX = &lt;span class="category2"&gt;x&lt;/span&gt; - 1;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( nextX &lt;= 1) &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( MazeData.&lt;span class="category2"&gt;getData&lt;/span&gt;(nextX, &lt;span class="category2"&gt;y&lt;/span&gt;) == MazeData.BLOCK ) &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; xDirection = -1;&lt;br /&gt; yDirection = 0;&lt;br /&gt;&lt;br /&gt; keyboardBuffer = -1;&lt;br /&gt; currentDirection = MOVE_LEFT;&lt;br /&gt;&lt;br /&gt; state = MOVING;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// turn pac-man going up&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; moveUp(): Void {&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( currentDirection == MOVE_UP ) &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;var&lt;/span&gt; nextY = &lt;span class="category2"&gt;y&lt;/span&gt; - 1;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( nextY &lt;= 1) &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( MazeData.&lt;span class="category2"&gt;getData&lt;/span&gt;(&lt;span class="category2"&gt;x&lt;/span&gt;,nextY) == MazeData.BLOCK ) &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; xDirection = 0;&lt;br /&gt; yDirection = -1;&lt;br /&gt;&lt;br /&gt; keyboardBuffer = -1;&lt;br /&gt; currentDirection = MOVE_UP;&lt;br /&gt;&lt;br /&gt; state = MOVING;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// turn pac-man going down&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; moveDown(): Void {&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( currentDirection == MOVE_DOWN ) &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;var&lt;/span&gt; nextY = &lt;span class="category2"&gt;y&lt;/span&gt; + 1;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( nextY &gt;= MazeData.GRID_SIZE ) &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( MazeData.&lt;span class="category2"&gt;getData&lt;/span&gt;(&lt;span class="category2"&gt;x&lt;/span&gt;,nextY) == MazeData.BLOCK ) &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; xDirection = 0;&lt;br /&gt; yDirection = 1;&lt;br /&gt;&lt;br /&gt; keyboardBuffer = -1;&lt;br /&gt; currentDirection = MOVE_DOWN;&lt;br /&gt;&lt;br /&gt; state = MOVING;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// handle keyboard input&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; handleKeyboardInput(): Void {&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( keyboardBuffer &lt; class="category1"&gt;return;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( keyboardBuffer == MOVE_LEFT )&lt;br /&gt;   moveLeft()&lt;br /&gt; &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;   &lt;span class="category1"&gt;if&lt;/span&gt; ( keyboardBuffer == MOVE_RIGHT )&lt;br /&gt;     moveRight()&lt;br /&gt;   &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;     &lt;span class="category1"&gt;if&lt;/span&gt; ( keyboardBuffer == MOVE_UP )&lt;br /&gt;       moveUp()&lt;br /&gt;     &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;       &lt;span class="category1"&gt;if&lt;/span&gt; ( keyboardBuffer == MOVE_DOWN )&lt;br /&gt;         moveDown();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; setKeyboardBuffer( k: Integer): Void {&lt;br /&gt; keyboardBuffer = k;&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   In &lt;b&gt;&lt;tt&gt;moveOneSteop()&lt;/tt&gt;&lt;/b&gt; function, add in two lines of code to handle keyboard events during each tick of the animation:  &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;function&lt;/span&gt; moveOneStep() {&lt;br /&gt;&lt;br /&gt; &lt;span class="linecomment"&gt;// handle keyboard input only when pac-man is at a point of the grid&lt;/span&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( currentImage==0 )&lt;br /&gt;   handleKeyboardInput();&lt;br /&gt;&lt;br /&gt; . . . . . .&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;    &lt;p&gt;As we mentioned previously, the Pac-Man always faces to the left in our code. Let's modify our codes a bit to enable the Pac-Man to "turn" his mouth. A common approach is to use a separate set of pictures when  the character is moving in a particular direction. However, we going to  utilize the transformation feature of  JavaFX to achieve this goal. Instead of switching to another set of pictures, we just rotate the picture of each frame to face to the correct direction. JavaFX makes it very simple to accomplish. Let's change some  code in &lt;b&gt;&lt;tt&gt;PacMan&lt;/tt&gt;&lt;/b&gt;. &lt;/p&gt; &lt;p&gt; &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; PacMan &lt;span class="category1"&gt;extends&lt;/span&gt; CustomNode, MovingObject {&lt;br /&gt;&lt;br /&gt;. . . . .&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// angles of rotating the images&lt;/span&gt;&lt;br /&gt;def rotationDegree = [0, 90, 180, 270];&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// GUI image of the man&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;var&lt;/span&gt; pacmanImage : ImageView = ImageView {&lt;br /&gt;  &lt;span class="category2"&gt;x&lt;/span&gt;: bind imageX - 13&lt;br /&gt;  &lt;span class="category2"&gt;y&lt;/span&gt;: bind imageY - 13&lt;br /&gt;  image: bind images[currentImage]&lt;br /&gt;  transforms: Rotate {&lt;br /&gt;     angle: bind rotationDegree[currentDirection]&lt;br /&gt;     pivotX: bind imageX&lt;br /&gt;     pivotY: bind imageY&lt;br /&gt;     }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;. . . . . &lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;The &lt;b&gt;&lt;tt&gt;transforms&lt;/tt&gt;&lt;/b&gt; attribute of the &lt;b&gt;&lt;tt&gt;ImageView&lt;/tt&gt;&lt;/b&gt; class allows us to apply the rotation we need. An instance of &lt;b&gt;&lt;tt&gt;Rotate&lt;/tt&gt;&lt;/b&gt; defines the angle and the center of the rotation. Binding is used again to automatically update the GUI of the character. &lt;/p&gt;  &lt;p&gt;To accept keyboard events, we override the &lt;b&gt;&lt;tt&gt;onKeyPressed&lt;/tt&gt;&lt;/b&gt; attribute of the &lt;b&gt;&lt;tt&gt;Maze&lt;/tt&gt;&lt;/b&gt; class:   &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;var&lt;/span&gt; onKeyPressed = &lt;span class="category1"&gt;function&lt;/span&gt; ( e: KeyEvent ) : Void {&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( e.code == KeyCode.VK_DOWN )&lt;br /&gt;   pacMan.setKeyboardBuffer( pacMan.MOVE_DOWN )&lt;br /&gt; &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;   &lt;span class="category1"&gt;if&lt;/span&gt; ( e.code == KeyCode.VK_UP )&lt;br /&gt;     pacMan.setKeyboardBuffer( pacMan.MOVE_UP )&lt;br /&gt;   &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;     &lt;span class="category1"&gt;if&lt;/span&gt; ( e.code == KeyCode.VK_RIGHT )&lt;br /&gt;       pacMan.setKeyboardBuffer( pacMan.MOVE_RIGHT )&lt;br /&gt;     &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;       &lt;span class="category1"&gt;if&lt;/span&gt; ( e.code == KeyCode.VK_LEFT )&lt;br /&gt;         pacMan.setKeyboardBuffer( pacMan.MOVE_LEFT );&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;To get better visual effect, we now reduce the interval of animation keyframes. In function &lt;b&gt;&lt;tt&gt;MovingObject.createTimeline()&lt;/tt&gt;&lt;/b&gt;, we change the &lt;b&gt;&lt;tt&gt;time&lt;/tt&gt;&lt;/b&gt; attribute to 50ms.  &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; createTimeline(): Timeline {&lt;br /&gt; Timeline {&lt;br /&gt;    repeatCount: Timeline.INDEFINITE&lt;br /&gt;    keyFrames: [&lt;br /&gt;      KeyFrame {&lt;br /&gt;         &lt;span class="category2"&gt;time&lt;/span&gt;: &lt;b&gt;50ms&lt;/b&gt;&lt;br /&gt;         action: &lt;span class="category1"&gt;function&lt;/span&gt;() {&lt;br /&gt;            moveOneStep();&lt;br /&gt;          }&lt;br /&gt;       }&lt;br /&gt;    ]&lt;br /&gt;   }&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;Run the program and you can control the pac-man character's moving by arrow keys. Click the below button to view it online:&lt;/p&gt; &lt;p&gt; &lt;a href="http://www.javafxgame.com/v5/pacman.jnlp"&gt; &lt;img src="http://www.insideria.com/upload/2009/05/maze6.png" alt="click to run" border="0" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.insideria.com/upload/2009/05/launch.gif" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;b&gt;Gobbling Dots&lt;/b&gt;&lt;/p&gt; &lt;p&gt;The last part of the Pac-man's animation is gobbling the dots. We first create two attributes in the  &lt;b&gt;&lt;tt&gt;PacMan&lt;/tt&gt;&lt;/b&gt; class: &lt;b&gt;&lt;tt&gt;dotEatenCount&lt;/tt&gt;&lt;/b&gt; and &lt;b&gt;&lt;tt&gt;scores&lt;/tt&gt;&lt;/b&gt;.  &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// the number of dots eaten&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; dotEatenCount : Integer = 0;&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// scores of the game&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; scores: Integer = 0;&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;Then we write a function &lt;b&gt;&lt;tt&gt;updateScores()&lt;/tt&gt;&lt;/b&gt; to check if a dot is gobbled by the Pac-man character. The scores is updated accordingly.  &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; updateScores() : Void {&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( &lt;span class="category2"&gt;y&lt;/span&gt; != 14 &lt;span class="category1"&gt;or&lt;/span&gt; ( &lt;span class="category2"&gt;x&lt;/span&gt; &gt; 0 &lt;span class="category1"&gt;and&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt; &lt; class="category1"&gt;var dot : Dot = MazeData.getDot( &lt;span class="category2"&gt;x&lt;/span&gt;, &lt;span class="category2"&gt;y&lt;/span&gt; ) as Dot ;&lt;br /&gt;&lt;br /&gt;    &lt;span class="category1"&gt;if&lt;/span&gt; ( dot != &lt;span class="category1"&gt;null&lt;/span&gt; &lt;span class="category1"&gt;and&lt;/span&gt; dot.&lt;span class="category2"&gt;visible&lt;/span&gt; ) {&lt;br /&gt;       scores += 10;&lt;br /&gt;       dot.&lt;span class="category2"&gt;visible&lt;/span&gt; = &lt;span class="category1"&gt;false&lt;/span&gt;;&lt;br /&gt;       dotEatenCount ++;&lt;br /&gt;     }&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;    &lt;p&gt;In function &lt;b&gt;&lt;tt&gt;moveOneStep()&lt;/tt&gt;&lt;/b&gt;, we invoke the &lt;b&gt;&lt;tt&gt;updateScores()&lt;/tt&gt;&lt;/b&gt; as below: &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;function&lt;/span&gt; moveOneStep() {&lt;br /&gt;&lt;br /&gt;   . . . . . .&lt;br /&gt;&lt;br /&gt;   &lt;span class="category1"&gt;if&lt;/span&gt; ( currentImage &lt; class="category1"&gt;else {&lt;br /&gt;      currentImage=0;&lt;br /&gt;      updateScores();&lt;br /&gt;    }&lt;br /&gt;   . . . . . .&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;    &lt;p&gt;Finally, we add a &lt;b&gt;&lt;tt&gt;Text&lt;/tt&gt;&lt;/b&gt; instance as a scoreboard under the maze. It displays the scores as Pac-man eats the dots. The content of the &lt;b&gt;&lt;tt&gt;Text&lt;/tt&gt;&lt;/b&gt; instance is bound to &lt;b&gt;&lt;tt&gt;pacMan.scores&lt;/tt&gt;&lt;/b&gt;. &lt;/p&gt;&lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; Maze &lt;span class="category1"&gt;extends&lt;/span&gt; CustomNode {&lt;br /&gt;&lt;br /&gt;. . . . . .&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;var&lt;/span&gt; group : Group =&lt;br /&gt;Group {&lt;br /&gt;  content: [&lt;br /&gt;&lt;br /&gt;    . . . . . .&lt;br /&gt;&lt;br /&gt;    Text {&lt;br /&gt;       &lt;span class="category2"&gt;font&lt;/span&gt;: Font {&lt;br /&gt;              &lt;span class="category2"&gt;size&lt;/span&gt;: 20&lt;br /&gt;              }&lt;br /&gt;       &lt;span class="category2"&gt;x&lt;/span&gt;: MazeData.calcGridX(0),&lt;br /&gt;       &lt;span class="category2"&gt;y&lt;/span&gt;: MazeData.calcGridY(MazeData.GRID_SIZE + 2)&lt;br /&gt;       content: bind "&lt;span class="quote"&gt;SCORES: {pacMan.scores} &lt;/span&gt;"&lt;br /&gt;       fill: &lt;span class="category2"&gt;Color&lt;/span&gt;.YELLOW&lt;br /&gt;     }&lt;br /&gt;   ]&lt;br /&gt; . . . . . .&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;By now, we completed the all the animation part of the Pac-man character. A player can control the pac-man character by keyboard and score by gobbling the dots. Click on the below button to run and play it. &lt;/p&gt;&lt;p&gt; &lt;a href="http://www.javafxgame.com/v6/pacman.jnlp"&gt; &lt;img src="http://www.insideria.com/upload/2009/05/maze7.png" alt="click to run" border="0" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.insideria.com/upload/2009/05/launch.gif" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://www.insideria.com/upload/2009/05/javafxsource2.zip"&gt;Download Source Code&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;&lt;p align="right"&gt;&lt;span style="font-weight:bold;"&gt;www.insideria.com&lt;/span&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4004158710435644696-6853226021561570632?l=eswebsoft.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/6853226021561570632/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/writing-pac-man-game-in-javafx-part-2.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/6853226021561570632'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/6853226021561570632'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/writing-pac-man-game-in-javafx-part-2.html' title='Writing the Pac-Man Game in JavaFX - Part 2'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4004158710435644696.post-2585925875404527861</id><published>2009-08-02T17:10:00.003+07:00</published><updated>2009-08-02T17:16:54.870+07:00</updated><title type='text'>Writing the Pac-Man Game in JavaFX - Part 1</title><content type='html'>&lt;p&gt;&lt;b&gt;&lt;a href="http://learnjavafx.typepad.com/" target="_blank"&gt;&lt;/a&gt;&lt;/b&gt;When I was young I was fascinated by arcade games. One of my favorites was the Pac-Man game.  Recently, when I was learning the JavaFX language, I decided to write the game in JavaFX.  Based on my experience in other programming languages, I assumed there would be some amount of work in building a game such as Pac-Man, giving me a good feel for RIA development in JavaFX.&lt;/p&gt;  &lt;h3&gt;Data Model of the Maze&lt;/h3&gt;   &lt;p&gt;Before writing the JavaFX code, it is first necessary to design the data model. A data model is a way to represent physical objects with data structures. The functional programming style of JavaFX makes it easy to bind the UI to the model. When designing a data model, I usually consider two aspects: performance and space. Performance means that the data should be accessed via an efficient approach. For example, a hash table is usually faster than a linked list when a keyword-based search is performed. Performance is an important consideration for games that are constantly taking a player's input and updating graphical objects. Games like Pac-Man or Space Invaders fall into this category.  The other design consideration for a data model is memory space. I still remember the time when I was programming on an APPLE II with only 48KB RAM. Much effort was spent on minimizing memory consumption. Fortunately, our Pac-Man game is not data-intensive, so I wasn't very concerned about the space issue. &lt;/p&gt;  &lt;p&gt;Keeping the above analysis in mind, we will now start building the data model. We can treat the board as an NxN grid. The wall of the maze can be drawn by lines connecting the points of the grid. Naturally, a 2-dimensional array is the best way to model this grid. Each point of the grid may have one of the following four types: &lt;/p&gt;  &lt;p&gt; &lt;/p&gt;&lt;table border="1"&gt; &lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;b&gt;Type&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;Value&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;Explanation&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;BLOCK&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;The point is part of a "wall" of the maze&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt; NORMAL_DOT &lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt; The point contains a normal dot &lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt; MAGIC_DOT &lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt; The point contains a magic dot &lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt; EMPTY &lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt; The point does not have any of the above objects at it &lt;/td&gt;&lt;/tr&gt; &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;For example, the picture shown below is the upper-left corner of the maze, and the corresponding data in the array is shown on the right:&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;&lt;table&gt; &lt;tbody&gt;&lt;tr&gt;&lt;td&gt; &lt;img src="http://www.insideria.com/upload/2009/04/corner.jpg" /&gt; &lt;/td&gt; &lt;td width="50"&gt;&lt;br /&gt;&lt;/td&gt; &lt;td&gt;  &lt;table border="1"&gt; &lt;tbody&gt;&lt;tr&gt;&lt;td width="30"&gt;&lt;br /&gt;&lt;/td&gt;&lt;td width="30"&gt;&lt;b&gt;0&lt;/b&gt;&lt;/td&gt;&lt;td width="30"&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;&lt;td width="30"&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;&lt;td width="30"&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;&lt;td width="30"&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;&lt;td width="30"&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;&lt;td width="30"&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;b&gt;0&lt;/b&gt;&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;&lt;b&gt;9&lt;/b&gt;&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt;  &lt;/tbody&gt;&lt;/table&gt; &lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt;&lt;/table&gt;   &lt;p&gt;This data model will be accessed very frequently when the Pac-Man character and ghosts are moving inside the maze, therefore, an efficient data structure should be used. A 2-dimensional (2D) array is a good choice because its access time is almost a constant. Though it is common in modern programming languages to have 2D arrays, JavaFX sequences (an array-like structure in JavaFX) are single dimensional. For this reason, I decided to use a Java class to hold this array. I created a few methods for accessing the model or converting the grid data into drawing coordinates.  Note that the ability to leverage Java from within JavaFX is one of the very powerful features of JavaFX.&lt;/p&gt;   &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;package pacman;&lt;br /&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/**&lt;br /&gt;* MazeData.java&lt;br /&gt;*&lt;br /&gt;* @author Henry Zhang&lt;br /&gt;*&lt;br /&gt;* a 2D array for data model of the maze&lt;br /&gt;*&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; MazeData {&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; final &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt; GRID_SIZE = 29;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; final &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt; EMPTY = 0;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; final &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt; BLOCK = 1;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; final &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt; NORMAL_DOT = 2;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; final &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt; MAGIC_DOT = 3;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt; DOT_TOTAL = 0;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt; mazeData[][] = &lt;span class="category1"&gt;new&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt;[GRID_SIZE + 1][GRID_SIZE + 1];&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; final &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt; GRID_GAP = 16;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; final &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt; GRID_STROKE = 2;&lt;br /&gt;final &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt; xoffset = GRID_GAP * 2;&lt;br /&gt;final &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt; yoffset = GRID_GAP * 2;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt; makeInRange(&lt;span class="category1"&gt;int&lt;/span&gt; a) {&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; (a &lt; a =" 0;" class="category1"&gt;else &lt;span class="category1"&gt;if&lt;/span&gt; (a &gt; GRID_SIZE) {&lt;br /&gt;    a = GRID_SIZE;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;return&lt;/span&gt; a;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// set the grid of maze data to be BLOCK&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;void&lt;/span&gt; setBlockMazeData(&lt;span class="category1"&gt;int&lt;/span&gt; x1, &lt;span class="category1"&gt;int&lt;/span&gt; y1, &lt;span class="category1"&gt;int&lt;/span&gt; x2, &lt;span class="category1"&gt;int&lt;/span&gt; y2) {&lt;br /&gt; x1 = makeInRange(x1);&lt;br /&gt; y1 = makeInRange(y1);&lt;br /&gt; x2 = makeInRange(x2);&lt;br /&gt; y2 = makeInRange(y2);&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;for&lt;/span&gt; (&lt;span class="category1"&gt;int&lt;/span&gt; i = x1; i &lt;= x2; i++) {         mazeData[i][y1] = BLOCK;         mazeData[i][y2] = BLOCK;       }       &lt;span class="category1"&gt;for&lt;/span&gt; (&lt;span class="category1"&gt;int&lt;/span&gt; i = y1; i &lt;= y2; i++) {         mazeData[x1][i] = BLOCK;         mazeData[x2][i] = BLOCK;       }    }    &lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;static&lt;/span&gt; double calcGridX(double &lt;span class="category2"&gt;x&lt;/span&gt;) {&lt;br /&gt; &lt;span class="category1"&gt;return&lt;/span&gt; GRID_GAP * &lt;span class="category2"&gt;x&lt;/span&gt; + xoffset;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;static&lt;/span&gt; double calcGridY(double &lt;span class="category2"&gt;y&lt;/span&gt;) {&lt;br /&gt; &lt;span class="category1"&gt;return&lt;/span&gt; GRID_GAP * &lt;span class="category2"&gt;y&lt;/span&gt; + yoffset;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;int&lt;/span&gt; &lt;span class="category2"&gt;getData&lt;/span&gt;(&lt;span class="category1"&gt;int&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt;, &lt;span class="category1"&gt;int&lt;/span&gt; &lt;span class="category2"&gt;y&lt;/span&gt;) {&lt;br /&gt; &lt;span class="category1"&gt;return&lt;/span&gt; mazeData[&lt;span class="category2"&gt;x&lt;/span&gt;][&lt;span class="category2"&gt;y&lt;/span&gt;];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;void&lt;/span&gt; &lt;span class="category2"&gt;setData&lt;/span&gt;(&lt;span class="category1"&gt;int&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt;, &lt;span class="category1"&gt;int&lt;/span&gt; &lt;span class="category2"&gt;y&lt;/span&gt;, &lt;span class="category1"&gt;int&lt;/span&gt; value) {&lt;br /&gt; mazeData[&lt;span class="category2"&gt;x&lt;/span&gt;][&lt;span class="category2"&gt;y&lt;/span&gt;] = value;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ((value == MAGIC_DOT) || (value == NORMAL_DOT)) {&lt;br /&gt;    DOT_TOTAL++;&lt;br /&gt;  }&lt;br /&gt;} &lt;span class="linecomment"&gt;// end setData&lt;/span&gt;&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;    &lt;h3&gt;Drawing the Maze&lt;/h3&gt;  &lt;p&gt;Once we have the data model of the maze, we can start drawing the maze based on this model. There are basically two approaches for drawing the maze. One approach is to draw the maze directly with JavaFX code.  Another approach is use an image file such as a PNG or JPG. The image could then be used as a background picture in our Pac-Man game. I choose the first approach because it is easier to link the GUI to our data model.&lt;/p&gt;  &lt;p&gt;JavaFX provides some standard APIs for basic shapes such as lines, circles and rectangles.  We can use &lt;tt&gt;&lt;b&gt;Line&lt;/b&gt;&lt;/tt&gt; and &lt;tt&gt;&lt;b&gt;Rectangle&lt;/b&gt;&lt;/tt&gt; classes as building blocks for  most parts of the maze. First, we write a class named &lt;tt&gt;&lt;b&gt;WallRectangle&lt;/b&gt;&lt;/tt&gt; to draw the walls of the maze. In the code shown below, (x1,y1) and (x2,y2) are the coordinates of the upper-left and bottom-right corner of a rectangle. Here is the code: &lt;/p&gt;   &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/*&lt;br /&gt;* WallRectangle.fx&lt;br /&gt;*&lt;br /&gt;* Created on 2008-12-25, 16:08:28&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;package pacman;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.CustomNode;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.Node;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.paint.&lt;span class="category2"&gt;Color&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.shape.Rectangle;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; pacman.MazeData;&lt;br /&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/**&lt;br /&gt;* @author Henry Zhang&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; WallRectangle &lt;span class="category1"&gt;extends&lt;/span&gt; CustomNode {&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; x1: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; y1: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; x2: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; y2: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;function&lt;/span&gt; create(): Node {&lt;br /&gt; Rectangle {&lt;br /&gt;    &lt;span class="category2"&gt;x&lt;/span&gt;: MazeData.calcGridX(x1)&lt;br /&gt;    &lt;span class="category2"&gt;y&lt;/span&gt;: MazeData.calcGridY(y1)&lt;br /&gt;    &lt;span class="category2"&gt;width&lt;/span&gt;: MazeData.calcGridX(x2) - MazeData.calcGridX(x1)&lt;br /&gt;    &lt;span class="category2"&gt;height&lt;/span&gt;: MazeData.calcGridY(y2) - MazeData.calcGridY(y1)&lt;br /&gt;    strokeWidth: MazeData.GRID_STROKE&lt;br /&gt;    stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLUE&lt;br /&gt;    arcWidth: 12&lt;br /&gt;    arcHeight: 12&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;    &lt;p&gt;Next, we start to work on the &lt;tt&gt;&lt;b&gt;Maze&lt;/b&gt;&lt;/tt&gt; class. The &lt;tt&gt;&lt;b&gt;Maze&lt;/b&gt;&lt;/tt&gt; class  extends &lt;tt&gt;&lt;b&gt;CustomNode&lt;/b&gt;&lt;/tt&gt; class and  overrides the &lt;tt&gt;&lt;b&gt;create()&lt;/b&gt;&lt;/tt&gt; function. In the &lt;tt&gt;&lt;b&gt;create()&lt;/b&gt;&lt;/tt&gt; function,  we place &lt;tt&gt;&lt;b&gt;WallRectangle&lt;/b&gt;&lt;/tt&gt; and  &lt;tt&gt;&lt;b&gt;Line&lt;/b&gt;&lt;/tt&gt; instances to construct the maze. Here is the source code of the &lt;tt&gt;&lt;b&gt;Maze&lt;/b&gt;&lt;/tt&gt; class: &lt;/p&gt;  &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/*&lt;br /&gt;* Maze.fx&lt;br /&gt;*&lt;br /&gt;* Created on 2008-12-20, 20:22:15&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;package pacman;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.CustomNode;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.Group;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.Node;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.paint.&lt;span class="category2"&gt;Color&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.shape.Line;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.shape.Rectangle;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; pacman.MazeData;&lt;br /&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/**&lt;br /&gt;* @author Henry Zhang&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; Maze &lt;span class="category1"&gt;extends&lt;/span&gt; CustomNode {&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;var&lt;/span&gt; group : Group =&lt;br /&gt;Group {&lt;br /&gt; content: [&lt;br /&gt;   Rectangle {&lt;br /&gt;      &lt;span class="category2"&gt;x&lt;/span&gt;:0&lt;br /&gt;      &lt;span class="category2"&gt;y&lt;/span&gt;:0&lt;br /&gt;      &lt;span class="category2"&gt;width&lt;/span&gt;: MazeData.calcGridX(MazeData.GRID_SIZE + 2)&lt;br /&gt;      &lt;span class="category2"&gt;height&lt;/span&gt;: MazeData.calcGridY(MazeData.GRID_SIZE + 3)&lt;br /&gt;      fill: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLACK&lt;br /&gt;    },&lt;br /&gt;&lt;br /&gt;   WallRectangle{ x1:0 y1:0 x2:MazeData.GRID_SIZE y2:MazeData.GRID_SIZE },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:14 y1:-0.5 x2:15 y2:4 },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:2 y1:2 x2:5 y2:4 },&lt;br /&gt;   WallRectangle { x1:7 y1:2 x2:12 y2:4 },&lt;br /&gt;   WallRectangle { x1:17 y1:2 x2:22 y2:4 },&lt;br /&gt;   WallRectangle { x1:24 y1:2 x2:27 y2:4 },&lt;br /&gt;   WallRectangle { x1:2 y1:6 x2:5 y2:7 },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:14 y1:6.2 x2:15 y2:10 },&lt;br /&gt;   WallRectangle { x1:10 y1:6 x2:19 y2:7 },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:7.5 y1:9 x2:12 y2:10 },&lt;br /&gt;   WallRectangle { x1:7 y1:6 x2:8 y2:13 },&lt;br /&gt;   WallBlackLine { x1:8 y1:9 x2:8 y2:10 },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:17 y1:9 x2:21.5 y2:10 },&lt;br /&gt;   WallRectangle { x1:21 y1:6 x2:22 y2:13 },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:24 y1:6 x2:27 y2:7 },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:-1 y1:9 x2:5 y2:13 },&lt;br /&gt;   WallRectangle { x1:24 y1:9 x2:MazeData.GRID_SIZE + 1 y2:13 },&lt;br /&gt;&lt;br /&gt;   &lt;span class="linecomment"&gt;//cage and the gate&lt;/span&gt;&lt;br /&gt;   WallRectangle { x1:10 y1:12 x2:19 y2:17 },&lt;br /&gt;   WallRectangle { x1:10.5 y1:12.5 x2:18.5 y2:16.5 },&lt;br /&gt;   Rectangle {&lt;br /&gt;      &lt;span class="category2"&gt;x&lt;/span&gt;: MazeData.calcGridX(13)&lt;br /&gt;      &lt;span class="category2"&gt;width&lt;/span&gt;: MazeData.GRID_GAP * 3&lt;br /&gt;      &lt;span class="category2"&gt;y&lt;/span&gt;: MazeData.calcGridY(12)&lt;br /&gt;      &lt;span class="category2"&gt;height&lt;/span&gt;: MazeData.GRID_GAP / 2&lt;br /&gt;      stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.GREY&lt;br /&gt;      fill: &lt;span class="category2"&gt;Color&lt;/span&gt;.GREY&lt;br /&gt;    },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:7.5 y1:19 x2:12 y2:20 },&lt;br /&gt;   WallRectangle { x1:7 y1:15 x2:8 y2:23 },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:17 y1:19 x2:21.5 y2:20 },&lt;br /&gt;   WallRectangle { x1:21 y1:15 x2:22 y2:23 },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:14 y1:19 x2:15 y2:27 },&lt;br /&gt;   WallRectangle { x1:10 y1:22 x2:19 y2:23 },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:2 y1:25 x2:5 y2:27 },&lt;br /&gt;   WallRectangle { x1:17 y1:25 x2:22 y2:27 },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:7 y1:25 x2:12 y2:27 },&lt;br /&gt;   WallRectangle { x1:24 y1:25 x2:27 y2:27 },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:-1 y1:15 x2:5 y2:17 },&lt;br /&gt;   WallRectangle { x1:4 y1:19 x2:5 y2:23 },&lt;br /&gt;   WallRectangle { x1:2 y1:19 x2:4.5 y2:20 },&lt;br /&gt;   WallRectangle { x1:-1 y1:22 x2:2 y2:23 },&lt;br /&gt;&lt;br /&gt;   WallRectangle { x1:24 y1:15 x2:MazeData.GRID_SIZE + 1 y2:17 },&lt;br /&gt;   WallRectangle { x1:24 y1:19 x2:25 y2:23 },&lt;br /&gt;   WallRectangle { x1:24.5 y1:19 x2:27 y2:20 },&lt;br /&gt;   WallRectangle { x1:27 y1:22 x2:MazeData.GRID_SIZE + 1 y2:23 },&lt;br /&gt;&lt;br /&gt;   WallBlackRectangle { x1:-2 y1:8 x2:0 y2:MazeData.GRID_SIZE },&lt;br /&gt;   WallBlackRectangle {&lt;br /&gt;       x1:MazeData.GRID_SIZE&lt;br /&gt;       y1:8&lt;br /&gt;       x2:MazeData.GRID_SIZE + 2&lt;br /&gt;       y2:MazeData.GRID_SIZE&lt;br /&gt;    },&lt;br /&gt;&lt;br /&gt;   Rectangle {&lt;br /&gt;      &lt;span class="category2"&gt;x&lt;/span&gt;: MazeData.calcGridX(-0.5)&lt;br /&gt;      &lt;span class="category2"&gt;y&lt;/span&gt;: MazeData.calcGridY(-0.5)&lt;br /&gt;      &lt;span class="category2"&gt;width&lt;/span&gt;: (MazeData.GRID_SIZE + 1) * MazeData.GRID_GAP&lt;br /&gt;      &lt;span class="category2"&gt;height&lt;/span&gt;: (MazeData.GRID_SIZE + 1) * MazeData.GRID_GAP&lt;br /&gt;      strokeWidth: MazeData.GRID_STROKE&lt;br /&gt;      stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLUE&lt;br /&gt;      fill: &lt;span class="category1"&gt;null&lt;/span&gt;&lt;br /&gt;      arcWidth: 12&lt;br /&gt;      arcHeight: 12&lt;br /&gt;    },&lt;br /&gt;   Line {&lt;br /&gt;      startX: MazeData.calcGridX(-0.5)&lt;br /&gt;      endX: MazeData.calcGridX(-0.5)&lt;br /&gt;      startY: MazeData.calcGridY(13)&lt;br /&gt;      endY: MazeData.calcGridY(15)&lt;br /&gt;      stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLACK&lt;br /&gt;      strokeWidth: MazeData.GRID_STROKE + 1&lt;br /&gt;    },&lt;br /&gt;   Line {&lt;br /&gt;      startX: MazeData.calcGridX(MazeData.GRID_SIZE + 0.5)&lt;br /&gt;      endX: MazeData.calcGridX(MazeData.GRID_SIZE + 0.5)&lt;br /&gt;      startY: MazeData.calcGridY(13)&lt;br /&gt;      endY: MazeData.calcGridY(15)&lt;br /&gt;      stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLACK&lt;br /&gt;      strokeWidth: MazeData.GRID_STROKE + 1&lt;br /&gt;    },&lt;br /&gt;   Line {&lt;br /&gt;      startX: MazeData.calcGridX(-0.5)&lt;br /&gt;      endX: MazeData.calcGridX(0)&lt;br /&gt;      startY: MazeData.calcGridY(13)&lt;br /&gt;      endY: MazeData.calcGridY(13)&lt;br /&gt;      stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLUE&lt;br /&gt;      strokeWidth: MazeData.GRID_STROKE&lt;br /&gt;    },&lt;br /&gt;   Line {&lt;br /&gt;      startX: MazeData.calcGridX(-0.5)&lt;br /&gt;      endX: MazeData.calcGridX(0)&lt;br /&gt;      startY: MazeData.calcGridY(15)&lt;br /&gt;      endY: MazeData.calcGridY(15)&lt;br /&gt;      stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLUE&lt;br /&gt;      strokeWidth: MazeData.GRID_STROKE&lt;br /&gt;    },&lt;br /&gt;   Line {&lt;br /&gt;      startX: MazeData.calcGridX(MazeData.GRID_SIZE + 0.5)&lt;br /&gt;      endX: MazeData.calcGridX(MazeData.GRID_SIZE)&lt;br /&gt;      startY: MazeData.calcGridY(13)&lt;br /&gt;      endY: MazeData.calcGridY(13)&lt;br /&gt;      stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLUE&lt;br /&gt;      strokeWidth: MazeData.GRID_STROKE&lt;br /&gt;    },&lt;br /&gt;   Line {&lt;br /&gt;      startX: MazeData.calcGridX(MazeData.GRID_SIZE + 0.5)&lt;br /&gt;      endX: MazeData.calcGridX(MazeData.GRID_SIZE)&lt;br /&gt;      startY: MazeData.calcGridY(15)&lt;br /&gt;      endY: MazeData.calcGridY(15)&lt;br /&gt;      stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLUE&lt;br /&gt;      strokeWidth: MazeData.GRID_STROKE&lt;br /&gt;    },&lt;br /&gt;&lt;br /&gt; ]&lt;br /&gt;}; &lt;span class="linecomment"&gt;// end Group&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;function&lt;/span&gt; create(): Node {&lt;br /&gt;    &lt;span class="category1"&gt;return&lt;/span&gt; group;&lt;br /&gt;} &lt;span class="linecomment"&gt;// end create()&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;Now we'll write a Main class to put the maze onto the stage and display it:&lt;/p&gt;   &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/*&lt;br /&gt;* Main.fx&lt;br /&gt;*&lt;br /&gt;* Created on 2008-12-20, 12:02:26&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;package pacman;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.Scene;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.stage.&lt;span class="category2"&gt;Stage&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/**&lt;br /&gt;* @author Henry Zhang&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category2"&gt;Stage&lt;/span&gt;{&lt;br /&gt;title: "&lt;span class="quote"&gt;PACMAN&lt;/span&gt;"&lt;br /&gt;&lt;span class="category2"&gt;width&lt;/span&gt;: MazeData.calcGridX(MazeData.GRID_SIZE + 2)&lt;br /&gt;&lt;span class="category2"&gt;height&lt;/span&gt;: MazeData.calcGridY(MazeData.GRID_SIZE + 5)&lt;br /&gt;scene: Scene{&lt;br /&gt;         content: [ Maze {}&lt;br /&gt;         ]&lt;br /&gt;        }&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;    &lt;p&gt;Run the program and we have the first version of our maze: &lt;/p&gt; &lt;p&gt; &lt;img src="http://www.insideria.com/upload/2009/04/maze1.jpg" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;a href="http://www.javafxgame.com/v1/pacman.jnlp"&gt;Launch&lt;/a&gt;&lt;/strong&gt; &lt;/p&gt;  &lt;p&gt;The maze is almost done except that some lines are overlapping each other.  This is not a problem, because we can put some black lines and rectangles to hide  those overlapping areas so that the maze looks nice. Let's write two classes,  &lt;tt&gt;&lt;b&gt;WallBlackRectangle&lt;/b&gt;&lt;/tt&gt; and &lt;tt&gt;&lt;b&gt;WallBlackLine&lt;/b&gt;&lt;/tt&gt;, for this purpose. The &lt;tt&gt;&lt;b&gt;WallBlackRectangle&lt;/b&gt;&lt;/tt&gt;  class covers a rectangular area. The &lt;tt&gt;&lt;b&gt;WallBlackLine&lt;/b&gt;&lt;/tt&gt; class draws a black line in the  maze. In the previous section, our &lt;tt&gt;&lt;b&gt;WallRectangle&lt;/b&gt;&lt;/tt&gt; class extends the &lt;tt&gt;&lt;b&gt;CustomNode&lt;/b&gt;&lt;/tt&gt; class.  We do the same thing for the &lt;tt&gt;&lt;b&gt;WallBlackRectangle&lt;/b&gt;&lt;/tt&gt; here. The &lt;tt&gt;&lt;b&gt;WallBlackLine&lt;/b&gt;&lt;/tt&gt; class demonstrates  another way to achieve the same functionality. We subclass the JavaFX &lt;tt&gt;&lt;b&gt;Line&lt;/b&gt;&lt;/tt&gt; class and use  a &lt;tt&gt;&lt;b&gt;postinit&lt;/b&gt;&lt;/tt&gt; block (which is invoked upon instantiation after the instance variables have been  assigned values) to change the details of the &lt;tt&gt;&lt;b&gt;Line&lt;/b&gt;&lt;/tt&gt; object.&lt;/p&gt;   &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/*&lt;br /&gt;* WallBlackRectangle.fx&lt;br /&gt;*&lt;br /&gt;* Created on 2008-12-27, 16:35:42&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;package pacman;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.CustomNode;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.Node;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.paint.&lt;span class="category2"&gt;Color&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.shape.Rectangle;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; pacman.MazeData;&lt;br /&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/**&lt;br /&gt;* @author Henry Zhang&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; WallBlackRectangle &lt;span class="category1"&gt;extends&lt;/span&gt; CustomNode {&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; x1: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; y1: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; x2: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; y2: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;function&lt;/span&gt; create(): Node {&lt;br /&gt; Rectangle {&lt;br /&gt;    &lt;span class="category2"&gt;x&lt;/span&gt;: MazeData.calcGridX(x1) + MazeData.GRID_STROKE&lt;br /&gt;    &lt;span class="category2"&gt;y&lt;/span&gt;: MazeData.calcGridY(y1) + MazeData.GRID_STROKE&lt;br /&gt;    &lt;span class="category2"&gt;width&lt;/span&gt;: MazeData.GRID_GAP * (x2-x1) - MazeData.GRID_STROKE * 2&lt;br /&gt;    &lt;span class="category2"&gt;height&lt;/span&gt;: MazeData.GRID_GAP * (y2-y1) - MazeData.GRID_STROKE * 2&lt;br /&gt;    strokeWidth: MazeData.GRID_STROKE&lt;br /&gt;    stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLACK&lt;br /&gt;    arcWidth: 3&lt;br /&gt;    arcHeight: 3&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/*&lt;br /&gt;* WallBlackLine.fx&lt;br /&gt;*&lt;br /&gt;* Created on 2008-12-27, 17:52:58&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;package pacman;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.paint.&lt;span class="category2"&gt;Color&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.shape.Line;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; pacman.MazeData;&lt;br /&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/**&lt;br /&gt;* @author Henry Zhang&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; WallBlackLine &lt;span class="category1"&gt;extends&lt;/span&gt; Line {&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; x1: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; y1: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; x2: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; y2: &lt;span class="category2"&gt;Number&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;postinit {&lt;br /&gt; strokeWidth = MazeData.GRID_STROKE + 1;&lt;br /&gt; stroke = &lt;span class="category2"&gt;Color&lt;/span&gt;.BLACK;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( x1 == x2 ) { &lt;span class="linecomment"&gt;// vertically line&lt;/span&gt;&lt;br /&gt;    startX = MazeData.calcGridX(x1);&lt;br /&gt;    startY = MazeData.calcGridY(y1) + MazeData.GRID_STROKE;&lt;br /&gt;    endX = MazeData.calcGridX(x2);&lt;br /&gt;    endY = MazeData.calcGridY(y2) - MazeData.GRID_STROKE;&lt;br /&gt;  }&lt;br /&gt; &lt;span class="category1"&gt;else&lt;/span&gt;  { &lt;span class="linecomment"&gt;// horizontal line&lt;/span&gt;&lt;br /&gt;    startX = MazeData.calcGridX(x1) + MazeData.GRID_STROKE;&lt;br /&gt;    startY = MazeData.calcGridY(y1);&lt;br /&gt;    endX = MazeData.calcGridX(x2) - MazeData.GRID_STROKE;&lt;br /&gt;    endY = MazeData.calcGridY(y2);&lt;br /&gt;  }&lt;br /&gt;} &lt;span class="linecomment"&gt;// end postinit&lt;/span&gt;&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;In the &lt;tt&gt;&lt;b&gt;Maze&lt;/b&gt;&lt;/tt&gt; class, we put in some instances of the above classes into the group variable:&lt;/p&gt;  &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;var&lt;/span&gt; group : Group =&lt;br /&gt;Group {&lt;br /&gt;content: [&lt;br /&gt;  Rectangle {&lt;br /&gt;     &lt;span class="category2"&gt;x&lt;/span&gt;:0&lt;br /&gt;     &lt;span class="category2"&gt;y&lt;/span&gt;:0&lt;br /&gt;     &lt;span class="category2"&gt;width&lt;/span&gt;: MazeData.calcGridX(MazeData.GRID_SIZE + 2)&lt;br /&gt;     &lt;span class="category2"&gt;height&lt;/span&gt;: MazeData.calcGridY(MazeData.GRID_SIZE + 3)&lt;br /&gt;     fill: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLACK&lt;br /&gt;   },&lt;br /&gt;&lt;br /&gt;  WallRectangle{ x1:0 y1:0 x2:MazeData.GRID_SIZE y2:MazeData.GRID_SIZE },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:14 y1:-0.5 x2:15 y2:4 },&lt;br /&gt;  WallBlackRectangle { x1:13.8 y1:-1 x2:15.3 y2:0 },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:2 y1:2 x2:5 y2:4 },&lt;br /&gt;  WallRectangle { x1:7 y1:2 x2:12 y2:4 },&lt;br /&gt;  WallRectangle { x1:17 y1:2 x2:22 y2:4 },&lt;br /&gt;  WallRectangle { x1:24 y1:2 x2:27 y2:4 },&lt;br /&gt;  WallRectangle { x1:2 y1:6 x2:5 y2:7 },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:14 y1:6.2 x2:15 y2:10 },&lt;br /&gt;  WallRectangle { x1:10 y1:6 x2:19 y2:7 },&lt;br /&gt;  WallBlackLine { x1:14 y1:7 x2:15 y2:7 },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:7.5 y1:9 x2:12 y2:10 },&lt;br /&gt;  WallRectangle { x1:7 y1:6 x2:8 y2:13 },&lt;br /&gt;  WallBlackLine { x1:8 y1:9 x2:8 y2:10 },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:17 y1:9 x2:21.5 y2:10 },&lt;br /&gt;  WallRectangle { x1:21 y1:6 x2:22 y2:13 },&lt;br /&gt;  WallBlackLine { x1:21 y1:9 x2:21 y2:10 },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:24 y1:6 x2:27 y2:7 },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:-1 y1:9 x2:5 y2:13 },&lt;br /&gt;  WallRectangle { x1:24 y1:9 x2:MazeData.GRID_SIZE + 1 y2:13 },&lt;br /&gt;  WallBlackLine { x1:0 y1:13 x2:0 y2:15  },&lt;br /&gt;  WallBlackLine { x1:MazeData.GRID_SIZE y1:13 x2:MazeData.GRID_SIZE y2:15},&lt;br /&gt;&lt;br /&gt;  &lt;span class="linecomment"&gt;//cage and the gate&lt;/span&gt;&lt;br /&gt;  WallRectangle { x1:10 y1:12 x2:19 y2:17 },&lt;br /&gt;  WallRectangle { x1:10.5 y1:12.5 x2:18.5 y2:16.5 },&lt;br /&gt;  Rectangle {&lt;br /&gt;     &lt;span class="category2"&gt;x&lt;/span&gt;: MazeData.calcGridX(13)&lt;br /&gt;     &lt;span class="category2"&gt;width&lt;/span&gt;: MazeData.GRID_GAP * 3&lt;br /&gt;     &lt;span class="category2"&gt;y&lt;/span&gt;: MazeData.calcGridY(12)&lt;br /&gt;     &lt;span class="category2"&gt;height&lt;/span&gt;: MazeData.GRID_GAP / 2&lt;br /&gt;     stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.GREY&lt;br /&gt;     fill: &lt;span class="category2"&gt;Color&lt;/span&gt;.GREY&lt;br /&gt;   },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:7.5 y1:19 x2:12 y2:20 },&lt;br /&gt;  WallRectangle { x1:7 y1:15 x2:8 y2:23 },&lt;br /&gt;  WallBlackLine { x1:8 y1:19 x2:8 y2:20 },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:17 y1:19 x2:21.5 y2:20 },&lt;br /&gt;  WallRectangle { x1:21 y1:15 x2:22 y2:23 },&lt;br /&gt;  WallBlackLine { x1:21 y1:19 x2:21 y2:20 },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:14 y1:19 x2:15 y2:27 },&lt;br /&gt;  WallRectangle { x1:10 y1:22 x2:19 y2:23 },&lt;br /&gt;  WallBlackLine { x1:14 y1:22 x2:15 y2:22 },&lt;br /&gt;  WallBlackLine { x1:14 y1:23 x2:15 y2:23 },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:2 y1:25 x2:5 y2:27 },&lt;br /&gt;  WallRectangle { x1:17 y1:25 x2:22 y2:27 },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:7 y1:25 x2:12 y2:27 },&lt;br /&gt;  WallRectangle { x1:24 y1:25 x2:27 y2:27 },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:-1 y1:15 x2:5 y2:17 },&lt;br /&gt;  WallRectangle { x1:4 y1:19 x2:5 y2:23 },&lt;br /&gt;  WallRectangle { x1:2 y1:19 x2:4.5 y2:20 },&lt;br /&gt;  WallBlackRectangle { x1:4 y1:19.05 x2:5 y2:20.2 },&lt;br /&gt;  WallRectangle { x1:-1 y1:22 x2:2 y2:23 },&lt;br /&gt;&lt;br /&gt;  WallRectangle { x1:24 y1:15 x2:MazeData.GRID_SIZE + 1 y2:17 },&lt;br /&gt;  WallRectangle { x1:24 y1:19 x2:25 y2:23 },&lt;br /&gt;  WallRectangle { x1:24.5 y1:19 x2:27 y2:20 },&lt;br /&gt;  WallBlackRectangle { x1:24 y1:19.05 x2:25 y2:20.2 },&lt;br /&gt;  WallRectangle { x1:27 y1:22 x2:MazeData.GRID_SIZE + 1 y2:23 },&lt;br /&gt;&lt;br /&gt;  WallBlackRectangle { x1:-2 y1:8 x2:0 y2:MazeData.GRID_SIZE },&lt;br /&gt;  WallBlackRectangle {&lt;br /&gt;      x1:MazeData.GRID_SIZE&lt;br /&gt;      y1:8&lt;br /&gt;      x2:MazeData.GRID_SIZE + 2&lt;br /&gt;      y2:MazeData.GRID_SIZE&lt;br /&gt;   },&lt;br /&gt;&lt;br /&gt;  Rectangle {&lt;br /&gt;     &lt;span class="category2"&gt;x&lt;/span&gt;: MazeData.calcGridX(-0.5)&lt;br /&gt;     &lt;span class="category2"&gt;y&lt;/span&gt;: MazeData.calcGridY(-0.5)&lt;br /&gt;     &lt;span class="category2"&gt;width&lt;/span&gt;: (MazeData.GRID_SIZE + 1) * MazeData.GRID_GAP&lt;br /&gt;     &lt;span class="category2"&gt;height&lt;/span&gt;: (MazeData.GRID_SIZE + 1) * MazeData.GRID_GAP&lt;br /&gt;     strokeWidth: MazeData.GRID_STROKE&lt;br /&gt;     stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLUE&lt;br /&gt;     fill: &lt;span class="category1"&gt;null&lt;/span&gt;&lt;br /&gt;     arcWidth: 12&lt;br /&gt;     arcHeight: 12&lt;br /&gt;   },&lt;br /&gt;  Line {&lt;br /&gt;     startX: MazeData.calcGridX(-0.5)&lt;br /&gt;     endX: MazeData.calcGridX(-0.5)&lt;br /&gt;     startY: MazeData.calcGridY(13)&lt;br /&gt;     endY: MazeData.calcGridY(15)&lt;br /&gt;     stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLACK&lt;br /&gt;     strokeWidth: MazeData.GRID_STROKE + 1&lt;br /&gt;   },&lt;br /&gt;  Line {&lt;br /&gt;     startX: MazeData.calcGridX(MazeData.GRID_SIZE + 0.5)&lt;br /&gt;     endX: MazeData.calcGridX(MazeData.GRID_SIZE + 0.5)&lt;br /&gt;     startY: MazeData.calcGridY(13)&lt;br /&gt;     endY: MazeData.calcGridY(15)&lt;br /&gt;     stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLACK&lt;br /&gt;     strokeWidth: MazeData.GRID_STROKE + 1&lt;br /&gt;   },&lt;br /&gt;  Line {&lt;br /&gt;     startX: MazeData.calcGridX(-0.5)&lt;br /&gt;     endX: MazeData.calcGridX(0)&lt;br /&gt;     startY: MazeData.calcGridY(13)&lt;br /&gt;     endY: MazeData.calcGridY(13)&lt;br /&gt;     stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLUE&lt;br /&gt;     strokeWidth: MazeData.GRID_STROKE&lt;br /&gt;   },&lt;br /&gt;  Line {&lt;br /&gt;     startX: MazeData.calcGridX(-0.5)&lt;br /&gt;     endX: MazeData.calcGridX(0)&lt;br /&gt;     startY: MazeData.calcGridY(15)&lt;br /&gt;     endY: MazeData.calcGridY(15)&lt;br /&gt;     stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLUE&lt;br /&gt;     strokeWidth: MazeData.GRID_STROKE&lt;br /&gt;   },&lt;br /&gt;  Line {&lt;br /&gt;     startX: MazeData.calcGridX(MazeData.GRID_SIZE + 0.5)&lt;br /&gt;     endX: MazeData.calcGridX(MazeData.GRID_SIZE)&lt;br /&gt;     startY: MazeData.calcGridY(13)&lt;br /&gt;     endY: MazeData.calcGridY(13)&lt;br /&gt;     stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLUE&lt;br /&gt;     strokeWidth: MazeData.GRID_STROKE&lt;br /&gt;   },&lt;br /&gt;  Line {&lt;br /&gt;     startX: MazeData.calcGridX(MazeData.GRID_SIZE + 0.5)&lt;br /&gt;     endX: MazeData.calcGridX(MazeData.GRID_SIZE)&lt;br /&gt;     startY: MazeData.calcGridY(15)&lt;br /&gt;     endY: MazeData.calcGridY(15)&lt;br /&gt;     stroke: &lt;span class="category2"&gt;Color&lt;/span&gt;.BLUE&lt;br /&gt;     strokeWidth: MazeData.GRID_STROKE&lt;br /&gt;   },&lt;br /&gt;&lt;br /&gt;]&lt;br /&gt;}; &lt;span class="linecomment"&gt;// end Group&lt;/span&gt;&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;After these adjustments, we have the Pac-Man maze shown below:&lt;/p&gt; &lt;p&gt;&lt;img src="http://www.insideria.com/upload/2009/04/maze2.jpg" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;a href="http://www.javafxgame.com/v1.5/pacman.jnlp"&gt;Launch&lt;/a&gt;&lt;/strong&gt; &lt;/p&gt;   &lt;p&gt;Before moving forward to the next step, I would like to initialize the data model as we draw the maze, i.e. to set the points related to a wall to a value of &lt;tt&gt;&lt;b&gt;BLOCK&lt;/b&gt;&lt;/tt&gt;. One of the benefits of doing  so is that the data model is always in sync with the GUI. If you want to modify the layout of the  maze later, you can just change the drawing code and the data model is adjusted automatically.  This can be achieved by adding a &lt;tt&gt;&lt;b&gt;postinit&lt;/b&gt;&lt;/tt&gt; block into the &lt;tt&gt;&lt;b&gt;WallRectangle&lt;/b&gt;&lt;/tt&gt; class:&lt;/p&gt;  &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; WallRectangle &lt;span class="category1"&gt;extends&lt;/span&gt; CustomNode {&lt;br /&gt;&lt;br /&gt;. . . . . .&lt;br /&gt;&lt;br /&gt;postinit {&lt;br /&gt; &lt;span class="linecomment"&gt;// initialize the data model while drawing the maze&lt;/span&gt;&lt;br /&gt; MazeData.setBlockMazeData(x1, y1, x2, y2);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;. . . . . .&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;The method &lt;tt&gt;MazeData.setBlockMazeData(x1,y1,x2,y2)&lt;/tt&gt; updates the data model by  setting all of the points of a rectangle to the value &lt;tt&gt;&lt;b&gt;BLOCK&lt;/b&gt;&lt;/tt&gt;. The values  &lt;tt&gt;&lt;b&gt;x1&lt;/b&gt;&lt;/tt&gt;, &lt;tt&gt;&lt;b&gt;y1&lt;/b&gt;&lt;/tt&gt; and &lt;tt&gt;&lt;b&gt;x2&lt;/b&gt;&lt;/tt&gt;, &lt;tt&gt;&lt;b&gt;y2&lt;/b&gt;&lt;/tt&gt; are the coordinates of the rectangle's two corners.&lt;/p&gt;   &lt;h3&gt;Drawing the Dots&lt;/h3&gt;  &lt;p&gt;Now that the maze is drawn, let's work on the dots. There are two types of dots in the game:  normal dots and magic dots. The magic dots are bigger in size and they continually flash.  If the Pac-Man character gobbles the magic dots, he has the power to eat ghosts for a short  period of time. Our &lt;tt&gt;&lt;b&gt;Dot&lt;/b&gt;&lt;/tt&gt; class extends the &lt;tt&gt;&lt;b&gt;CustomNode&lt;/b&gt;&lt;/tt&gt; class, a nd adds functionality to achieve the desired behavior. Take a look at the source code in &lt;b&gt;Dot.fx&lt;/b&gt;,  shown below: &lt;/p&gt;  &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/*&lt;br /&gt;* Dot.fx&lt;br /&gt;*&lt;br /&gt;* Created on 2008-12-21, 21:59:45&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;package pacman;&lt;br /&gt;&lt;br /&gt;&lt;span class="blockcomment"&gt;/**&lt;br /&gt;* @author Henry Zhang&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; java.lang.&lt;span class="category2"&gt;Math&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.animation.KeyFrame;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.animation.Timeline;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.CustomNode;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.Node;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.paint.&lt;span class="category2"&gt;Color&lt;/span&gt;;&lt;br /&gt;&lt;span class="category1"&gt;import&lt;/span&gt; javafx.scene.shape.Circle;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; Dot &lt;span class="category1"&gt;extends&lt;/span&gt; CustomNode {&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; dotType: Integer;&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// location of the dot&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt; : &lt;span class="category2"&gt;Number&lt;/span&gt; ;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; &lt;span class="category2"&gt;y&lt;/span&gt; : &lt;span class="category2"&gt;Number&lt;/span&gt; ;&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// radius of the dot&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; r: &lt;span class="category2"&gt;Number&lt;/span&gt; =&lt;br /&gt;&lt;span class="category1"&gt;if&lt;/span&gt; ( dotType == MazeData.MAGIC_DOT ) 5 &lt;span class="category1"&gt;else&lt;/span&gt; 1;&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// the dot&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;var&lt;/span&gt; circle = Circle{&lt;br /&gt; centerX: &lt;span class="category2"&gt;x&lt;/span&gt;&lt;br /&gt; centerY: &lt;span class="category2"&gt;y&lt;/span&gt;&lt;br /&gt; radius: bind r&lt;br /&gt; fill: &lt;span class="category2"&gt;Color&lt;/span&gt;.YELLOW&lt;br /&gt; &lt;span class="category2"&gt;visible&lt;/span&gt;: bind &lt;span class="category2"&gt;visible&lt;/span&gt;   &lt;span class="linecomment"&gt;// bind to Dot.visible&lt;/span&gt;&lt;br /&gt; } ;&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// variables for magic dot's growing/shrinking animation&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; animationRadius: &lt;span class="category2"&gt;Number&lt;/span&gt; = 3;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;var&lt;/span&gt; delta: &lt;span class="category2"&gt;Number&lt;/span&gt; = -1;&lt;br /&gt;&lt;span class="category1"&gt;var&lt;/span&gt; timeline: Timeline;&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// create the animation timeline for magic dot&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; createTimeline(): Timeline {&lt;br /&gt; Timeline {&lt;br /&gt;    repeatCount: Timeline.INDEFINITE&lt;br /&gt;    keyFrames: [&lt;br /&gt;      KeyFrame {&lt;br /&gt;         &lt;span class="category2"&gt;time&lt;/span&gt;: 250ms&lt;br /&gt;         action: &lt;span class="category1"&gt;function&lt;/span&gt;() {&lt;br /&gt;            doOneTick();&lt;br /&gt;          }&lt;br /&gt;       }&lt;br /&gt;    ]&lt;br /&gt;      }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; playTimeline() {&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( timeline == &lt;span class="category1"&gt;null&lt;/span&gt; )&lt;br /&gt; timeline = createTimeline();&lt;br /&gt;&lt;br /&gt; timeline.&lt;span class="category2"&gt;play&lt;/span&gt;();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// do the animation&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; doOneTick () {&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( &lt;span class="category2"&gt;visible&lt;/span&gt; == &lt;span class="category1"&gt;false&lt;/span&gt; )&lt;br /&gt; &lt;span class="category1"&gt;return&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt; animationRadius += delta;&lt;br /&gt; &lt;span class="category1"&gt;var&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt; = &lt;span class="category2"&gt;Math&lt;/span&gt;.&lt;span class="category2"&gt;abs&lt;/span&gt;(animationRadius) + 3;&lt;br /&gt;&lt;br /&gt; &lt;span class="category1"&gt;if&lt;/span&gt; ( &lt;span class="category2"&gt;x&lt;/span&gt; &gt; 5 ) {&lt;br /&gt;    delta = -delta;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; r = &lt;span class="category2"&gt;x&lt;/span&gt;;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; override &lt;span class="category1"&gt;function&lt;/span&gt; create(): Node {&lt;br /&gt;     &lt;span class="category1"&gt;return&lt;/span&gt; circle;&lt;br /&gt;}&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;    &lt;p&gt;The &lt;tt&gt;&lt;b&gt;Circle&lt;/b&gt;&lt;/tt&gt; class is used to display the dots. For a magic dot,  we continually change its radius to create the blinking effect. A &lt;tt&gt;&lt;b&gt;Timeline&lt;/b&gt;&lt;/tt&gt; instance generates  an animation frame every 250ms. The &lt;tt&gt;&lt;b&gt;doOneTick()&lt;/b&gt;&lt;/tt&gt; function is invoked each time  to adjust the value of the radius. Binding, an important feature of JavaFX, is used in  the &lt;tt&gt;&lt;b&gt;Circle&lt;/b&gt;&lt;/tt&gt; object to link it with the data model. During an animation  process, there is no need to manually update the GUI object because its radius is bound  to the model. &lt;/p&gt;   &lt;p&gt;Now that the &lt;tt&gt;&lt;b&gt;Dot&lt;/b&gt;&lt;/tt&gt; class is ready, we can put dots into the maze.  To accomplish this, we'll add the three functions shown below to the &lt;tt&gt;&lt;b&gt;Maze&lt;/b&gt;&lt;/tt&gt; class:&lt;/p&gt;   &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// create a Dot GUI object&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; createDot(  x1: &lt;span class="category2"&gt;Number&lt;/span&gt;,  y1:&lt;span class="category2"&gt;Number&lt;/span&gt;, &lt;span class="category2"&gt;type&lt;/span&gt;:Integer ): Dot {&lt;br /&gt;&lt;span class="category1"&gt;var&lt;/span&gt; d = Dot {&lt;br /&gt;   &lt;span class="category2"&gt;x&lt;/span&gt;: MazeData.calcGridX(x1)&lt;br /&gt;   &lt;span class="category2"&gt;y&lt;/span&gt;: MazeData.calcGridY(y1)&lt;br /&gt;   dotType: &lt;span class="category2"&gt;type&lt;/span&gt;&lt;br /&gt;   &lt;span class="category2"&gt;visible&lt;/span&gt;: &lt;span class="category1"&gt;true&lt;/span&gt;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;if&lt;/span&gt; ( d.dotType == MazeData.MAGIC_DOT )&lt;br /&gt;  d.playTimeline();&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// set the dot type in data model&lt;/span&gt;&lt;br /&gt;MazeData.&lt;span class="category2"&gt;setData&lt;/span&gt;( &lt;span class="category2"&gt;x&lt;/span&gt;, &lt;span class="category2"&gt;y&lt;/span&gt;, dotType ) ;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;return&lt;/span&gt; d;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// put dots into the maze as a horizontal line&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; putDotHorizontally(x1: Integer, x2: Integer, &lt;span class="category2"&gt;y&lt;/span&gt;: &lt;span class="category2"&gt;Number&lt;/span&gt; ) {&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;var&lt;/span&gt; dots =&lt;br /&gt;&lt;span class="category1"&gt;for&lt;/span&gt; ( &lt;span class="category2"&gt;x&lt;/span&gt; &lt;span class="category1"&gt;in&lt;/span&gt; [ x1..x2] )&lt;br /&gt;  &lt;span class="category1"&gt;if&lt;/span&gt; ( MazeData.&lt;span class="category2"&gt;getData&lt;/span&gt;(&lt;span class="category2"&gt;x&lt;/span&gt;,&lt;span class="category2"&gt;y&lt;/span&gt;) == MazeData.EMPTY ) {&lt;br /&gt;     &lt;span class="category1"&gt;var&lt;/span&gt; dotType: Integer;&lt;br /&gt;&lt;br /&gt;     &lt;span class="category1"&gt;if&lt;/span&gt; ( (&lt;span class="category2"&gt;x&lt;/span&gt; == 28 &lt;span class="category1"&gt;or&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt; == 1) &lt;span class="category1"&gt;and&lt;/span&gt; (&lt;span class="category2"&gt;y&lt;/span&gt; == 3 &lt;span class="category1"&gt;or&lt;/span&gt; &lt;span class="category2"&gt;y&lt;/span&gt; == 26) )&lt;br /&gt;       dotType = MazeData.MAGIC_DOT&lt;br /&gt;     &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;       dotType = MazeData.NORMAL_DOT;&lt;br /&gt;&lt;br /&gt;     createDot( &lt;span class="category2"&gt;x&lt;/span&gt;, &lt;span class="category2"&gt;y&lt;/span&gt;, dotType )&lt;br /&gt;   }&lt;br /&gt;  &lt;span class="category1"&gt;else&lt;/span&gt;   [] ;&lt;br /&gt;&lt;br /&gt;insert dots into group.content;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// put dots into the maze as a vertical line&lt;/span&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; putDotVertically(&lt;span class="category2"&gt;x&lt;/span&gt;: Integer, y1: Integer, y2: &lt;span class="category2"&gt;Number&lt;/span&gt; ) {&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;var&lt;/span&gt; dots =&lt;br /&gt;&lt;span class="category1"&gt;for&lt;/span&gt; ( &lt;span class="category2"&gt;y&lt;/span&gt; &lt;span class="category1"&gt;in&lt;/span&gt; [ y1..y2] )&lt;br /&gt;  &lt;span class="category1"&gt;if&lt;/span&gt; ( MazeData.&lt;span class="category2"&gt;getData&lt;/span&gt;(&lt;span class="category2"&gt;x&lt;/span&gt;,&lt;span class="category2"&gt;y&lt;/span&gt;) == MazeData.EMPTY ) {&lt;br /&gt;     &lt;span class="category1"&gt;var&lt;/span&gt; dotType: Integer;&lt;br /&gt;&lt;br /&gt;     &lt;span class="category1"&gt;if&lt;/span&gt; ( (&lt;span class="category2"&gt;x&lt;/span&gt; == 28 &lt;span class="category1"&gt;or&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt; == 1) &lt;span class="category1"&gt;and&lt;/span&gt; (&lt;span class="category2"&gt;y&lt;/span&gt; == 3 &lt;span class="category1"&gt;or&lt;/span&gt; &lt;span class="category2"&gt;y&lt;/span&gt; == 26) )&lt;br /&gt;       dotType = MazeData.MAGIC_DOT&lt;br /&gt;     &lt;span class="category1"&gt;else&lt;/span&gt;&lt;br /&gt;       dotType = MazeData.NORMAL_DOT;&lt;br /&gt;&lt;br /&gt;     createDot( &lt;span class="category2"&gt;x&lt;/span&gt;, &lt;span class="category2"&gt;y&lt;/span&gt;, dotType )&lt;br /&gt;   }&lt;br /&gt;  &lt;span class="category1"&gt;else&lt;/span&gt;  [];&lt;br /&gt;&lt;br /&gt;insert dots into group.content;&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;     &lt;p&gt;The &lt;tt&gt;&lt;b&gt;createDot()&lt;/b&gt;&lt;/tt&gt; function creates a &lt;tt&gt;&lt;b&gt;Dot&lt;/b&gt;&lt;/tt&gt; object based on its  coordinates (x,y) and its type (&lt;tt&gt;&lt;b&gt;NORMAL_DOT&lt;/b&gt;&lt;/tt&gt; or &lt;tt&gt;&lt;b&gt;MAGIC_DOT&lt;/b&gt;&lt;/tt&gt;). Again, while we are creating the dots,  we bind the dot status to our data model with the following statement:&lt;/p&gt; &lt;pre&gt;  // set the dot type to data model&lt;br /&gt;MazeData.setData( x, y, dotType ) ;&lt;/pre&gt;   &lt;p&gt;The &lt;tt&gt;&lt;b&gt;putDotHorizontally()&lt;/b&gt;&lt;/tt&gt; function places a horizontal line of dots into the maze and makes four of them the magic dots. &lt;/p&gt;  &lt;p&gt;The &lt;tt&gt;&lt;b&gt;putDotVertically()&lt;/b&gt;&lt;/tt&gt; function is almost the same as &lt;tt&gt;&lt;b&gt;putDotHorizontally()&lt;/b&gt;&lt;/tt&gt; except that it puts dots in a vertical fashion.&lt;/p&gt;  &lt;p&gt;Last thing is to put all the dots into the maze. We add some code to the &lt;tt&gt;&lt;b&gt;postinit&lt;/b&gt;&lt;/tt&gt; block of the &lt;tt&gt;&lt;b&gt;Maze&lt;/b&gt;&lt;/tt&gt; class:&lt;/p&gt;  &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; Maze &lt;span class="category1"&gt;extends&lt;/span&gt; CustomNode {&lt;br /&gt;&lt;br /&gt;. . . . . .&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// put dots into the maze &lt;/span&gt;&lt;br /&gt;postinit {&lt;br /&gt; putDotHorizontally(2,13,1);&lt;br /&gt; putDotHorizontally(16,27,1);&lt;br /&gt; putDotHorizontally(2,27,5);&lt;br /&gt; putDotHorizontally(2,27,28);&lt;br /&gt;&lt;br /&gt; putDotHorizontally(2,13,24);&lt;br /&gt; putDotHorizontally(16,27,24);&lt;br /&gt;&lt;br /&gt; putDotHorizontally(2,5,8);&lt;br /&gt; putDotHorizontally(9,13,8);&lt;br /&gt; putDotHorizontally(16,20,8);&lt;br /&gt; putDotHorizontally(24,27,8);&lt;br /&gt;&lt;br /&gt; putDotHorizontally(2,5,18);&lt;br /&gt; putDotHorizontally(9,13,21);&lt;br /&gt; putDotHorizontally(16,20,21);&lt;br /&gt; putDotHorizontally(24,27,18);&lt;br /&gt;&lt;br /&gt; putDotHorizontally(2,3,21);&lt;br /&gt; putDotHorizontally(26,27,21);&lt;br /&gt;&lt;br /&gt; putDotVertically(1,1,8);&lt;br /&gt; putDotVertically(1,18,21);&lt;br /&gt; putDotVertically(1,24,28);&lt;br /&gt;&lt;br /&gt; putDotVertically(28,1,8);&lt;br /&gt; putDotVertically(28,18,21);&lt;br /&gt; putDotVertically(28,24,28);&lt;br /&gt;&lt;br /&gt; putDotVertically(6,2,27);&lt;br /&gt; putDotVertically(23,2,27);&lt;br /&gt;&lt;br /&gt; putDotVertically(3,22,23);&lt;br /&gt; putDotVertically(9,22,23);&lt;br /&gt; putDotVertically(20,22,23);&lt;br /&gt; putDotVertically(26,22,23);&lt;br /&gt;&lt;br /&gt; putDotVertically(13,25,27);&lt;br /&gt; putDotVertically(16,25,27);&lt;br /&gt;&lt;br /&gt; putDotVertically(9,6,7);&lt;br /&gt; putDotVertically(20,6,7);&lt;br /&gt;&lt;br /&gt; putDotVertically(13,2,4);&lt;br /&gt; putDotVertically(16,2,4);&lt;br /&gt;}&lt;br /&gt;. . . . .&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;If you'd like to see the result so far, run the program and you'll get a maze populated with dots, four of which are the flashing magic dots: &lt;/p&gt; &lt;p&gt;&lt;img src="http://www.insideria.com/upload/2009/04/maze3.jpg" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;&lt;a href="http://www.javafxgame.com/v2/pacman.jnlp"&gt;Launch&lt;/a&gt;&lt;/strong&gt; &lt;/p&gt;  &lt;h3&gt;Building an Index of Dot References&lt;/h3&gt;  &lt;p&gt;In preparation for the next phase,we need to do one more thing. During the game, we need a fast way to get the reference of a &lt;tt&gt;&lt;b&gt;Dot&lt;/b&gt;&lt;/tt&gt; object at point (x, y). Our current code does not support an efficient reference. So in &lt;b&gt;MazeData.java&lt;/b&gt;,  we define a 2D array dotPointers to store the references to these dots.  Two accessor methods are added as well, using the &lt;tt&gt;&lt;b&gt;Object&lt;/b&gt;&lt;/tt&gt; type to store the references to JavaFX Dot instances.&lt;/p&gt;  &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;class&lt;/span&gt; MazeData {&lt;br /&gt;&lt;br /&gt;. . . . . .&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category2"&gt;Object&lt;/span&gt; dotPointers[][] = &lt;span class="category1"&gt;new&lt;/span&gt; &lt;span class="category2"&gt;Object&lt;/span&gt;[GRID_SIZE + 1][GRID_SIZE + 1];&lt;br /&gt;&lt;br /&gt;. . . . . .&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category2"&gt;Object&lt;/span&gt; getDot(&lt;span class="category1"&gt;int&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt;, &lt;span class="category1"&gt;int&lt;/span&gt; &lt;span class="category2"&gt;y&lt;/span&gt;) {&lt;br /&gt; &lt;span class="category1"&gt;return&lt;/span&gt; dotPointers[&lt;span class="category2"&gt;x&lt;/span&gt;][&lt;span class="category2"&gt;y&lt;/span&gt;];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;static&lt;/span&gt; &lt;span class="category1"&gt;void&lt;/span&gt; setDot(&lt;span class="category1"&gt;int&lt;/span&gt; &lt;span class="category2"&gt;x&lt;/span&gt;, &lt;span class="category1"&gt;int&lt;/span&gt; &lt;span class="category2"&gt;y&lt;/span&gt;, &lt;span class="category2"&gt;Object&lt;/span&gt; dot) {&lt;br /&gt; dotPointers[&lt;span class="category2"&gt;x&lt;/span&gt;][&lt;span class="category2"&gt;y&lt;/span&gt;] = dot;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;. . . . . .&lt;br /&gt;}&lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;   &lt;p&gt;In &lt;tt&gt;&lt;b&gt;Maze.createDot()&lt;/b&gt;&lt;/tt&gt;, we add a line to update the pointer in the data model:&lt;/p&gt;  &lt;div class="acode" style="padding: 10px; overflow: auto;"&gt;&lt;div style="overflow-x: visible;"&gt; &lt;code language="perl"&gt; &lt;/code&gt;&lt;pre&gt;&lt;br /&gt;&lt;span class="category1"&gt;public&lt;/span&gt; &lt;span class="category1"&gt;function&lt;/span&gt; createDot( x1: &lt;span class="category2"&gt;Number&lt;/span&gt;, y1:&lt;span class="category2"&gt;Number&lt;/span&gt;, &lt;span class="category2"&gt;type&lt;/span&gt;:Integer ): Dot {&lt;br /&gt;&lt;span class="category1"&gt;var&lt;/span&gt; d = Dot {&lt;br /&gt;   &lt;span class="category2"&gt;x&lt;/span&gt;: MazeData.calcGridX(x1)&lt;br /&gt;   &lt;span class="category2"&gt;y&lt;/span&gt;: MazeData.calcGridY(y1)&lt;br /&gt;   dotType: &lt;span class="category2"&gt;type&lt;/span&gt;&lt;br /&gt;   &lt;span class="category2"&gt;visible&lt;/span&gt;: &lt;span class="category1"&gt;true&lt;/span&gt;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;if&lt;/span&gt; ( d.dotType == MazeData.MAGIC_DOT )&lt;br /&gt;  d.playTimeline();&lt;br /&gt;&lt;br /&gt;&lt;span class="linecomment"&gt;// set the dot type in data model &lt;/span&gt;&lt;br /&gt;MazeData.&lt;span class="category2"&gt;setData&lt;/span&gt;( x1, y1, &lt;span class="category2"&gt;type&lt;/span&gt; );&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span class="linecomment"&gt;// set dot reference &lt;/span&gt;&lt;br /&gt;MazeData.setDot( x1, y1, d );&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="category1"&gt;return&lt;/span&gt; d;&lt;br /&gt;} &lt;/pre&gt;   &lt;/div&gt;&lt;/div&gt;  &lt;p&gt;Congratulations! You've completed the first phase of the Pac-Man game in which a maze and its dots are drawn. In subsequent articles, we will introduce the Pac-Man character and ghosts.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://www.insideria.com/upload/2009/05/javafxsource1.zip"&gt;Download Source Code&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;&lt;p align="right"&gt;&lt;span style="font-weight: bold;"&gt;www.insideria.com&lt;/span&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4004158710435644696-2585925875404527861?l=eswebsoft.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/2585925875404527861/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/writing-pac-man-game-in-javafx-part-1.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/2585925875404527861'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/2585925875404527861'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/writing-pac-man-game-in-javafx-part-1.html' title='Writing the Pac-Man Game in JavaFX - Part 1'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4004158710435644696.post-5499442217667848558</id><published>2009-08-02T16:57:00.004+07:00</published><updated>2009-08-02T20:02:33.333+07:00</updated><title type='text'>Top Ten Tomcat Configuration Tips 2 (Final)</title><content type='html'>&lt;h3&gt;6. Configuring Single Sign-On&lt;/h3&gt;&lt;br /&gt;&lt;p&gt;Once you've set up your realm and method of authentication, you'll need to deal with the actual process of logging the user in. More often than not, logging into an application is a nuisance to an end user, and you will need to minimize the number of times they must authenticate. By default, each web application will ask the user to log in the first time the user requests a protected resource. This can seem like a hassle to your users if you run multiple web applications and each application asks the user to authenticate. Users cannot tell how many separate applications make up any single web site, so they won't know when they're making a request that crosses a context boundary, and will wonder why they're being repeatedly asked to log in.&lt;/p&gt;&lt;br /&gt;The "single sign-on" feature of Tomcat 4 allows a user to authenticate only once to access all of the web applications loaded under a virtual host. To use this feature, you need only add a &lt;code&gt;SingleSignOn&lt;/code&gt; &lt;code&gt;Valve&lt;/code&gt; element at the host level. This looks like the following:&lt;p&gt;&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;Valve className="org.apache.catalina.authenticator.SingleSignOn"&lt;br /&gt;      debug="0"/&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;The Tomcat distribution's default &lt;em&gt;server.xml&lt;/em&gt; contains a commented-out single sign-on &lt;code&gt;Valve&lt;/code&gt; configuration example that you can uncomment and use. Then, any user who is considered valid in a context within the configured virtual host will be considered valid in all other contexts for that same host.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;There are several important restrictions for using the single sign-on valve:&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;!-- sidebar begins --&gt; &lt;!-- don't move sidebars --&gt; &lt;!-- sidebar ends --&gt;&lt;/p&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;The valve must be configured and nested within the same &lt;code&gt;Host&lt;/code&gt; element that the web applications (represented by &lt;code&gt;Context&lt;/code&gt; elements) are nested within.&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;The &lt;code&gt;Realm&lt;/code&gt; that contains the shared user information must be configured either at the level of the same &lt;code&gt;Host&lt;/code&gt; or in an outer nesting.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The &lt;code&gt;Realm&lt;/code&gt; cannot be overridden at the &lt;code&gt;Context&lt;/code&gt; level.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The web applications that use single sign-on must use one of Tomcat's built-in authenticators (in the &lt;code&gt;&amp;lt;auth-method&amp;gt;&lt;/code&gt; element of &lt;em&gt;web.xml&lt;/em&gt;), rather than a custom authenticator. The built-in methods are &lt;code&gt;basic&lt;/code&gt;, &lt;code&gt;digest&lt;/code&gt;, &lt;code&gt;form&lt;/code&gt;, and &lt;code&gt;client-cert&lt;/code&gt; authentication.&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;If you're using single sign-on and wish to integrate another third-party web application into your web site, and the new web application uses only its own authentication code that doesn't use container-managed security, you're basically stuck. Your users will have to log in once for all of the web applications that use single sign-on, and then once again if they make a request to the new third-party web application. Of course, if you get the source and you're a developer, you could fix it, but that's probably not so easy to do.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The single sign-on valve requires the use of HTTP cookies.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;7. Configuring Customized User Directories&lt;/h3&gt;&lt;br /&gt;&lt;p&gt;Some sites like to allow individual users to publish a directory of web pages on the server. For example, a university department might want to give each student a public area, or an ISP might make some web space available on one of its servers to customers that don't have a virtually hosted web server. In such cases, it is typical to use the tilde character (~) plus the user's name as the virtual path of that user's web site:&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;http://www.cs.myuniversity.edu/~username&lt;br /&gt;http://members.mybigisp.com/~username&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Tomcat gives you two ways to map this on a per-host basis, using a couple of special &lt;code&gt;Listener&lt;/code&gt; elements. The &lt;code&gt;Listener&lt;/code&gt;'s &lt;code&gt;className&lt;/code&gt; attribute should be &lt;code&gt;org.apache.catalina.startup.UserConfig&lt;/code&gt;, with the &lt;code&gt;userClass&lt;/code&gt; attribute specifying one of several mapping classes. If your system runs Unix, has a standard &lt;em&gt;/etc/passwd&lt;/em&gt; file that is readable by the account running Tomcat, and that file specifies users' home directories, use the &lt;code&gt;PasswdUserDatabase&lt;/code&gt; mapping class:&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;Listener className="org.apache.catalina.startup.UserConfig"&lt;br /&gt;directoryName="public_html"&lt;br /&gt;userClass="org.apache.catalina.startup.PasswdUserDatabase"/&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Web files would need to be in directories such as &lt;em&gt;/home/users/ian/public_html&lt;/em&gt; or &lt;em&gt;/users/jbrittain/public_html&lt;/em&gt;. Of course, you can change &lt;em&gt;public_html&lt;/em&gt; to be whatever subdirectory into which your users put their personal web pages.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;In fact, the directories don't have to be inside of a user's home directory at all. If you don't have a password file but want to map from a user name to a subdirectory of a common parent directory such as &lt;em&gt;/home&lt;/em&gt;, use the &lt;code&gt;HomesUserDatabase&lt;/code&gt; class:&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;Listener className="org.apache.catalina.startup.UserConfig"&lt;br /&gt;directoryName="public_html" homeBase="/home"&lt;br /&gt;userClass="org.apache.catalina.startup.HomesUserDatabase"/&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;In this case, web files would be in directories such as  &lt;em&gt;/home/ian/public_html&lt;/em&gt; or &lt;em&gt;/home/jasonb/public_html&lt;/em&gt;.  This format is more useful on Windows, where you'd likely use a directory such as &lt;em&gt;C:\home&lt;/em&gt;.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;These &lt;code&gt;Listener&lt;/code&gt; elements, if present, must be inside of a &lt;code&gt;Host&lt;/code&gt; element, but not inside of a &lt;code&gt;Context&lt;/code&gt; element, as they apply to the &lt;code&gt;Host&lt;/code&gt; itself.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;8. Using CGI Scripts with Tomcat&lt;/h3&gt;&lt;br /&gt;&lt;p&gt;Tomcat is primarily meant to be a servlet/JSP container, but it has many capabilities rivalling a traditional web server. One of these is support for the Common Gateway Interface (CGI), which provides a means for running an external program in response to a browser request, typically to process a web-based form. CGI is called "common" because it can invoke programs in almost any programming or scripting language: Perl, Python, &lt;code&gt;awk&lt;/code&gt;, Unix shell scripting, and even Java are all supported options. However, you probably wouldn't run a Java application as a CGI due to the start-up overhead; elimination of this overhead was what led to the original design of the servlet specification. Servlets are almost always more efficient than CGIs because you're not starting up a new operating-system-level process every time somebody clicks on a link or button.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Tomcat includes an optional CGI servlet that allows you to run legacy CGI scripts; the assumption is that most new back-end processing will be done by user-defined servlets and JSPs.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;To enable Tomcat's CGI servlet, you must do the following:&lt;/p&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Rename the file &lt;em&gt;servlets-cgi.renametojar&lt;/em&gt; (found in &lt;em&gt;CATALINA_HOME/server/lib/&lt;/em&gt;) to &lt;em&gt;servlets-cgi.jar&lt;/em&gt;, so that the servlet that processes CGI scripts will be on Tomcat's &lt;code&gt;CLASSPATH&lt;/code&gt;.&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;In Tomcat's &lt;em&gt;CATALINA_BASE/conf/web.xml&lt;/em&gt; file, uncomment the definition of the servlet named &lt;code&gt;cgi&lt;/code&gt; (this is around line 241 in the distribution).&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Also in Tomcat's &lt;em&gt;web.xml&lt;/em&gt;, uncomment the servlet mapping for the &lt;code&gt;cgi&lt;/code&gt; servlet (around line 299 in the distributed file). Remember, this specifies the HTML links to the CGI script.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Either place the CGI scripts under the &lt;em&gt;WEB-INF/cgi&lt;/em&gt; directory (remember that &lt;em&gt;WEB-INF&lt;/em&gt; is a safe place to hide things that you don't want the user to be able to view, for security reasons), or place them in some other directory within your context and adjust the &lt;code&gt;cgiPathPrefix&lt;/code&gt; initialization parameter of the &lt;code&gt;CGIServlet&lt;/code&gt; to identify the directory containing the files. This specifies the actual location of the CGI scripts, which typically will not be the same as the URL in the previous step.&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;Restart Tomcat, and your CGI processing should now be operational.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;p&gt;The default directory for the servlet to locate the actual scripts is &lt;em&gt;WEB-INF/cgi&lt;/em&gt;. As has been noted, the &lt;em&gt;WEB-INF&lt;/em&gt; directory is protected against casual snooping from browsers, so this is a good place to put CGI scripts, which may contain passwords or other sensitive information. For compatibility with other servers, though, you may prefer to keep the scripts in the traditional directory, &lt;em&gt;/cgi-bin&lt;/em&gt;, but be aware that files in this directory may be viewable by the curious web surfer.  Also, on Unix, be sure that the CGI script files are executable by the user under which you are running Tomcat.&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;9. Changing Tomcat's JSP Compiler&lt;/h3&gt;&lt;br /&gt;&lt;p&gt;In Tomcat 4.1 (and above, presumably), compilation of JSPs is performed by using the Ant program controller directly from within Tomcat. This sounds a bit strange, but it's part of what Ant was intended for; there is a documented API that lets developers use Ant without starting up a new JVM. This is one advantage of having Ant written in Java. Plus, it means you can now use any compiler supported by the &lt;code&gt;javac&lt;/code&gt; task within Ant; these are listed in the &lt;a href="http://ant.apache.org/manual/index.html"&gt;&lt;code&gt;javac&lt;/code&gt; page&lt;/a&gt; of the Apache Ant manual. It is easy to use because you need only an &lt;code&gt;&amp;lt;init-param&amp;gt;&lt;/code&gt; with a name of "compiler" and a value of one of the supported compiler names:&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;servlet&amp;gt;&lt;br /&gt;   &amp;lt;servlet-name&amp;gt;jsp&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;   &amp;lt;servlet-class&amp;gt;&lt;br /&gt;     org.apache.jasper.servlet.JspServlet&lt;br /&gt;   &amp;lt;/servlet-class&amp;gt;&lt;br /&gt;   &amp;lt;init-param&amp;gt;&lt;br /&gt;&lt;br /&gt;     &amp;lt;param-name&amp;gt;logVerbosityLevel&amp;lt;/param-name&amp;gt;&lt;br /&gt;     &amp;lt;param-value&amp;gt;WARNING&amp;lt;/param-value&amp;gt;&lt;br /&gt;   &amp;lt;/init-param&amp;gt;&lt;br /&gt;   &amp;lt;init-param&amp;gt;&lt;br /&gt;     &amp;lt;param-name&amp;gt;compiler&amp;lt;/param-name&amp;gt;&lt;br /&gt;&lt;br /&gt;     &amp;lt;param-value&amp;gt;jikes&amp;lt;/param-value&amp;gt;&lt;br /&gt;   &amp;lt;/init-param&amp;gt;&lt;br /&gt;   &amp;lt;load-on-startup&amp;gt;3&amp;lt;/load-on-startup&amp;gt;&lt;br /&gt;&amp;lt;/servlet&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Of course, the given compiler must be installed on your system, and the &lt;code&gt;CLASSPATH&lt;/code&gt; may need to be set, depending on which compiler you choose.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;10. Restricting Access to Specific Hosts&lt;/h3&gt;&lt;br /&gt;&lt;p&gt;Sometimes you'll only want to restrict access to Tomcat's web app to only specified host names or IP addresses. This way, only clients at those specified sites will be served content. Tomcat comes with two &lt;code&gt;Valve&lt;/code&gt;s that you can configure and use for this purpose: &lt;code&gt;RemoteHostValve&lt;/code&gt; and &lt;code&gt;RemoteAddrValve&lt;/code&gt;.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;These &lt;code&gt;Valve&lt;/code&gt;s allow you to filter requests by host name or by IP address, and to allow or deny hosts that match, similar to the per-directory Allow/Deny directives in Apache &lt;code&gt;httpd&lt;/code&gt;. If you run the Admin application, you might want to only allow access to it from localhost, as follows:&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;Context path="/path/to/secret_files" ...&amp;gt;&lt;br /&gt; &amp;lt;Valve className="org.apache.catalina.valves.RemoteAddrValve"&lt;br /&gt;        allow="127.0.0.1" deny=""/&amp;gt;&lt;br /&gt;&amp;lt;/Context&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;If no allow pattern is given, then patterns that match the deny attribute patterns will be rejected, and all others will be allowed. Similarly, if no deny pattern is given, patterns that match the &lt;code&gt;allow&lt;/code&gt; attribute will be allowed, and all others will be denied.&lt;/p&gt;&lt;p style="text-align: right;"&gt;&lt;span style="font-weight: bold;"&gt;O'Reilly Media&lt;/span&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4004158710435644696-5499442217667848558?l=eswebsoft.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/5499442217667848558/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/top-ten-configuration-tips-2-final.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/5499442217667848558'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/5499442217667848558'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/2009/08/top-ten-configuration-tips-2-final.html' title='Top Ten Tomcat Configuration Tips 2 (Final)'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4004158710435644696.post-5082840384923348555</id><published>2009-02-02T15:20:00.004+07:00</published><updated>2009-08-02T16:57:30.925+07:00</updated><title type='text'>Top Ten Tomcat Configuration Tips 1</title><content type='html'>&lt;em&gt;Coauthor's note: Now that writing Java web applications has become a common way to create and deploy new web content, people around the globe are finding the Jakarta Tomcat servlet and JSP container useful. It's free, it's multiplatform, it's rich in features, it's rapidly evolving and improving, and it's never been more popular.&lt;/em&gt; &lt;p&gt;&lt;!--ONJava MPU Ad --&gt;&lt;em&gt;The only catch seems to be this: how can you configure Tomcat to do what you want it to do? Tomcat is capable, as long as you can configure it to suit your needs. Below is my list of ten Tomcat configuration tips, taken from &lt;/em&gt;Tomcat: The Definitive Guide&lt;em&gt;, to help you do just that. -- Jason Brittain&lt;/em&gt;&lt;/p&gt; &lt;p&gt;&lt;em&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;&lt;/em&gt;&lt;/p&gt; &lt;h3&gt;1. Configuring the Admin Web Application&lt;/h3&gt; &lt;p&gt;Most commercial J2EE servers provide a fully functional administrative interface, and many of these are accessible as web applications. The Tomcat Admin application is on its way to becoming a full-blown Tomcat administration tool rivaling these commercial offerings. First included in Tomcat 4.1, Admin already provides control over contexts, data sources, and users and groups. You can also control resources such as initialization parameters, as well as users, groups, and roles in a variety of user databases. The list of capabilities will be expanded upon in future releases, but the present implementation has proven itself to be quite useful.&lt;/p&gt; &lt;p&gt;The Admin web application is defined in the auto-deployment file &lt;em&gt;CATALINA_BASE/webapps/admin.xml&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;You must edit this file to ensure that the path specified in the &lt;code&gt;docBase&lt;/code&gt; attribute of the &lt;code&gt;Context&lt;/code&gt; element is absolute; that is,  the absolute path of &lt;em&gt;CATALINA_HOME/server/webapps/admin&lt;/em&gt;. Alternatively, you could just remove the auto-deployment file and specify the Admin context manually in your &lt;em&gt;server.xml&lt;/em&gt; file. On machines that will not be managed by this application, you should probably disable it altogether by simply removing &lt;em&gt;CATALINA_BASE/webapps/admin.xml&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;If you're using a &lt;code&gt;UserDatabaseRealm&lt;/code&gt; (the default), you'll need to add a user and a role to the &lt;em&gt;CATALINA_BASE/conf/tomcat-users.xml&lt;/em&gt; file.  For now, just edit this file, and add a role named "admin" to your users database:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;role name="admin"/&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;You must also have a user who is assigned to the "admin" role. Add a user line like this after the existing user entries (changing the password to something a bit more secure):&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;user name="admin" password="deep_dark_secret" roles="admin"/&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Once you've performed these steps and restarted Tomcat, visit the URL &lt;em&gt;http://localhost:8080/admin&lt;/em&gt;, and you should see a login screen. The Admin application is built using container-managed security and the Jakarta Struts framework. Once you have logged in as a user assigned to the admin role, you will be able to use the Admin application to configure Tomcat.&lt;/p&gt; &lt;h3&gt;2. Configuring the Manager Web Application&lt;/h3&gt; &lt;p&gt;The Manager web application lets you perform simple management tasks on your web applications through a more simplified web user interface than that of the Admin web app.&lt;/p&gt; &lt;p&gt;The Manager web application is defined in the auto-deployment file &lt;em&gt;CATALINA_BASE/webapps/manager.xml&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;You must edit this file to ensure that the path specified in the &lt;code&gt;docBase&lt;/code&gt; attribute of the &lt;code&gt;Context&lt;/code&gt; element is absolute; that is,  the absolute path of &lt;em&gt;CATALINA_HOME/server/webapps/manager&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;If you're using the default &lt;code&gt;UserDatabaseRealm&lt;/code&gt;, you'll need to add a user and role to the &lt;em&gt;CATALINA_BASE/conf/tomcat-users.xml&lt;/em&gt; file. For now, just edit this file, and add a role named "manager" to your users database:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;role name="manager"/&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;You must also have a user who is assigned the "manager" role. Add a user line like this after the existing user entries (changing the password to something a bit more secure):&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&amp;lt;user name="manager" password="deep_dark_secret" roles="manager"/&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Then restart Tomcat and visit the URL &lt;em&gt;http://localhost/manager/list&lt;/em&gt; to see the plain-text manager interface, or &lt;em&gt;http://localhost/manager/html/list&lt;/em&gt; for the simple HTML manager interface. Either way, your Manager application should now be working.&lt;/p&gt; &lt;p&gt;The Manager application lets you install new web applications on a non-persistent basis, for testing. If we have a web application in &lt;em&gt;/home/user/hello&lt;/em&gt; and want to test it by installing it under the URI &lt;code&gt;/hello&lt;/code&gt;, we put "/hello" in the first text input field (for Path) and "file:/home/user/hello" in the second text input field (for Config URL).&lt;/p&gt; &lt;p&gt;The Manager also allows you to stop, reload, remove, or undeploy a web application. Stopping an application makes it unavailable until further notice, but of course it can then be restarted. Users attempting to access a stopped application will receive an error message, such as &lt;em&gt;503 - This application is not currently available&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;Removing a web application removes it only from the running copy of Tomcat -- if it was started from the configuration files, it will reappear the next time you restart Tomcat (i.e., removal does not remove the web application's content from disk).&lt;/p&gt; &lt;h3&gt;3. Deploying a Web Application&lt;/h3&gt; &lt;p&gt;There are two ways of deploying a web application on the filesystem:&lt;/p&gt; &lt;p&gt;1. Copy your WAR file or your web application's directory (including all of its content) to the &lt;em&gt;$CATALINA_BASE/webapps&lt;/em&gt; directory.&lt;/p&gt; &lt;p&gt;2. Create an XML fragment file with just the &lt;code&gt;Context&lt;/code&gt; element for your web application, and place this XML file in &lt;em&gt;$CATALINA_BASE/webapps&lt;/em&gt;. The web application itself can then be stored anywhere on your filesystem.&lt;/p&gt; &lt;p&gt;If you have a WAR file, you can deploy it by simply copying the WAR file into the directory &lt;em&gt;CATALINA_BASE/webapps&lt;/em&gt;. The filename must end with an extension of ".war". Once Tomcat notices the file, it will (by default) unpack it into a subdirectory with the base name of the WAR file. It will then create a context in memory, just as though you had created one by editing Tomcat's &lt;em&gt;server.xml&lt;/em&gt; file. However, any necessary defaults will be obtained from the &lt;code&gt;DefaultContext&lt;/code&gt; element in Tomcat's &lt;em&gt;server.xml&lt;/em&gt; file.&lt;/p&gt; &lt;p&gt;Another way to deploy a web app is by writing a Context XML fragment file and deploying it into the &lt;em&gt;CATALINA_BASE/webapps&lt;/em&gt; directory. A context fragment is not a complete XML document, but just one &lt;code&gt;Context&lt;/code&gt; element and any subelements that are appropriate for your web application. These files are like &lt;code&gt;Context&lt;/code&gt; elements cut out of the &lt;em&gt;server.xml&lt;/em&gt; file, hence the name "context fragment."&lt;/p&gt; &lt;p&gt;For example, if we wanted to deploy the WAR file &lt;em&gt;MyWebApp.war&lt;/em&gt; along with a realm for accessing parts of that web application, we could use this fragment:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;!--&lt;br /&gt;Context fragment for deploying MyWebApp.war&lt;br /&gt;--&amp;gt;&lt;br /&gt;&amp;lt;Context path="/demo" docBase="webapps/MyWebApp.war"&lt;br /&gt;     debug="0" privileged="true"&amp;gt;&lt;br /&gt;&amp;lt;Realm className="org.apache.catalina.realm.UserDatabaseRealm"&lt;br /&gt;     resourceName="UserDatabase"/&amp;gt;&lt;br /&gt;&amp;lt;/Context&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Put that in a file called "MyWebApp.xml," and copy it into your &lt;em&gt;CATALINA_BASE/webapps&lt;/em&gt; directory.&lt;/p&gt; &lt;p&gt;These context fragments provide a convenient method of deploying web applications; you do not need to edit the &lt;em&gt;server.xml&lt;/em&gt; file and, unless you have turned off the default &lt;code&gt;liveDeploy&lt;/code&gt; feature, you don't have to restart Tomcat to install a new web application.&lt;/p&gt; &lt;h3&gt;4. Configuring Virtual Hosts&lt;/h3&gt; &lt;p&gt;The &lt;code&gt;Host&lt;/code&gt; element normally needs modification only when you are setting up virtual hosts. Virtual hosting is a mechanism whereby one web server process can serve multiple domain names, giving each domain the appearance of having its own server. In fact, the majority of small business web sites are implemented as virtual hosts, due to the expense of connecting a computer directly to the Internet with sufficient bandwidth to provide reasonable response times and the stability of a permanent IP address.&lt;/p&gt; &lt;p&gt;Name-based virtual hosting is created on any web server by establishing an aliased IP address in the Domain Name Service (DNS) data and telling the web server to map all requests destined for the aliased address to a particular directory of web pages. Since this article is about Tomcat, we don't try to show all of the ways to set up DNS data on various operating systems. If you need help with this, please refer to &lt;a href="http://www.oreilly.com/catalog/dns4/index.html?CMP=IL7015"&gt;DNS and Bind&lt;/a&gt;, by Paul Albitz and Cricket Liu (O'Reilly). For demonstration purposes, I'll use a static hosts file, since that's the easiest way to set up aliases for testing purposes.&lt;/p&gt; &lt;p&gt;To use virtual hosts in Tomcat, you just need to set up the DNS or hosts data for the host. For testing, making an IP alias for localhost is sufficient. You then need to add a few lines to the &lt;em&gt;server.xml&lt;/em&gt; configuration file:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;Server port="8005" shutdown="SHUTDOWN" debug="0"&amp;gt;&lt;br /&gt;&amp;lt;Service name="Tomcat-Standalone"&amp;gt;&lt;br /&gt;&amp;lt;Connector className="org.apache.coyote.tomcat4.CoyoteConnector"&lt;br /&gt;port="8080" minProcessors="5" maxProcessors="75"&lt;br /&gt;enableLookups="true" redirectPort="8443"/&amp;gt;&lt;br /&gt;&amp;lt;Connector className="org.apache.coyote.tomcat4.CoyoteConnector"&lt;br /&gt;port="8443" minProcessors="5" maxProcessors="75"&lt;br /&gt;acceptCount="10" debug="0" scheme="https" secure="true"/&amp;gt;&lt;br /&gt;  &amp;lt;Factory className="org.apache.coyote.tomcat4.CoyoteServerSocketFactory"&lt;br /&gt;clientAuth="false" protocol="TLS" /&amp;gt;&lt;br /&gt;&amp;lt;/Connector&amp;gt;&lt;br /&gt;&amp;lt;Engine name="Standalone" defaultHost="localhost" debug="0"&amp;gt;&lt;br /&gt;  &amp;lt;!-- This Host is the default Host --&amp;gt;&lt;br /&gt;  &amp;lt;Host name="localhost" debug="0" appBase="webapps"&lt;br /&gt;    unpackWARs="true" autoDeploy="true"&amp;gt;&lt;br /&gt;    &amp;lt;Context path="" docBase="ROOT" debug="0"/&amp;gt;&lt;br /&gt;    &amp;lt;Context path="/orders" docBase="/home/ian/orders" debug="0"&lt;br /&gt;                   reloadable="true" crossContext="true"&amp;gt;&lt;br /&gt;    &amp;lt;/Context&amp;gt;&lt;br /&gt;  &amp;lt;/Host&amp;gt;&lt;br /&gt;&lt;strong&gt;&lt;br /&gt;  &amp;lt;!-- This Host is the first "Virtual Host": www.example.com --&amp;gt;&lt;br /&gt;  &amp;lt;Host name="www.example.com" appBase="/home/example/webapp"&amp;gt;&lt;br /&gt;    &amp;lt;Context path="" docBase="."/&amp;gt;&lt;br /&gt;  &amp;lt;/Host&amp;gt;&lt;br /&gt;&lt;/strong&gt;&lt;br /&gt;&amp;lt;/Engine&amp;gt;&lt;br /&gt;&amp;lt;/Service&amp;gt;&lt;br /&gt;&amp;lt;/Server&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Tomcat's &lt;em&gt;server.xml&lt;/em&gt; file, as distributed, contains only one virtual host, but it is easy to add support for additional virtual hosts. The simplified version of the &lt;em&gt;server.xml&lt;/em&gt; file in the previous example shows in bold the overall additional structure needed to add one virtual host. Each &lt;code&gt;Host&lt;/code&gt; element must have one or more &lt;code&gt;Context&lt;/code&gt; elements within it; one of these must be the default &lt;code&gt;Context&lt;/code&gt; for this host, which is specified by having its relative path set to the empty string (for example,  &lt;code&gt;path=""&lt;/code&gt;).&lt;/p&gt; &lt;h3&gt;5. Configuring Basic Authentication&lt;/h3&gt; &lt;p&gt;Container-managed authentication methods control how a user's credentials are verified when a web app's protected resource is accessed.  When a web application uses basic authentication (&lt;code&gt;BASIC&lt;/code&gt; in the &lt;em&gt;web.xml&lt;/em&gt; file's &lt;code&gt;auth-method&lt;/code&gt; element), Tomcat uses HTTP basic authentication to ask the web browser for a username and password whenever the browser requests a resource of that protected web application. With this authentication method, all passwords are sent across the network in base64-encoded text.&lt;/p&gt; &lt;p&gt;Note: using basic authentication is generally considered insecure because it does not strongly encrypt passwords, unless the site also uses HTTPS or some other form of encryption between the client and the server (for instance, a virtual private network). Without this extra encryption, network monitors can intercept (and misuse) users' passwords.  But, if you're just starting to use Tomcat, or if you just want to test container-managed security with your web app, basic authentication is easy to set up and test.  Just add &lt;code&gt;&amp;lt;security-constraint&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;login-config&amp;gt;&lt;/code&gt; elements to your web app's &lt;em&gt;web.xml&lt;/em&gt; file, and add the appropriate &lt;code&gt;&amp;lt;role&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;user&amp;gt;&lt;/code&gt; elements to your &lt;em&gt;CATALINA_BASE/conf/tomcat-users.xml&lt;/em&gt; file, restart Tomcat, and Tomcat takes care of the rest.&lt;/p&gt; &lt;p&gt;The example below shows a &lt;em&gt;web.xml&lt;/em&gt; excerpt from a club membership web site with a members-only subdirectory that is protected using basic authentication. Note that this effectively takes the place of the Apache web server's &lt;em&gt;.htaccess&lt;/em&gt; files.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;!--&lt;br /&gt;Define the Members-only area, by defining&lt;br /&gt;a "Security Constraint" on this Application, and&lt;br /&gt;mapping it to the subdirectory (URL) that we want&lt;br /&gt;to restrict.&lt;br /&gt;--&amp;gt;&lt;br /&gt;&amp;lt;security-constraint&amp;gt;&lt;br /&gt;&amp;lt;web-resource-collection&amp;gt;&lt;br /&gt;&amp;lt;web-resource-name&amp;gt;&lt;br /&gt;  Entire Application&lt;br /&gt;&amp;lt;/web-resource-name&amp;gt;&lt;br /&gt;&amp;lt;url-pattern&amp;gt;/members/*&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;&amp;lt;/web-resource-collection&amp;gt;&lt;br /&gt;&amp;lt;auth-constraint&amp;gt;&lt;br /&gt;  &amp;lt;role-name&amp;gt;member&amp;lt;/role-name&amp;gt;&lt;br /&gt;&amp;lt;/auth-constraint&amp;gt;&lt;br /&gt;&amp;lt;/security-constraint&amp;gt;&lt;br /&gt;&amp;lt;!-- Define the Login Configuration for this Application --&amp;gt;&lt;br /&gt;&amp;lt;login-config&amp;gt;&lt;br /&gt;&amp;lt;auth-method&amp;gt;BASIC&amp;lt;/auth-method&amp;gt;&lt;br /&gt;&amp;lt;realm-name&amp;gt;My Club Members-only Area&amp;lt;/realm-name&amp;gt;&lt;br /&gt;&amp;lt;/login-config&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div style="text-align: right;"&gt;&lt;span style="font-weight: bold;"&gt;O'Reilly Media&lt;/span&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4004158710435644696-5082840384923348555?l=eswebsoft.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://eswebsoft.blogspot.com/feeds/5082840384923348555/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://eswebsoft.blogspot.com/2009/02/dwasdsadasdas.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/5082840384923348555'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4004158710435644696/posts/default/5082840384923348555'/><link rel='alternate' type='text/html' href='http://eswebsoft.blogspot.com/2009/02/dwasdsadasdas.html' title='Top Ten Tomcat Configuration Tips 1'/><author><name>SuperHeo</name><uri>http://www.blogger.com/profile/06112128090184387917</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
