New Plan: Small, Educational Projects

I’ve had this idea in my head the last few weeks that I’m going to just jump into my next major project and learn the skills required to make it along the way. That’s kind of what I did with Domain Pigeon and since it got the job done, I didn’t consider any alternative processes. But, after reading the first few chapters of the Django and Flash books, I think it’s a much better idea to successfully complete a few baby steps before I try the long jump.

So, instead of diving headfirst into a large project I’m going to work on several small ones that will each require me to learn something new. Breaking it up into small, manageable components will let me focus on mastering a few skills at a time. One will be a small Django project, one will be a Flash project, etc. I might even do several of each, depending on how much I’m enjoying the work.

And who knows, maybe one of them will turn into something big and I’ll abandon my current plans.

We’ll see what happens.

Hecho en Mexico

There are things all around you that you don’t notice and don’t question. We all have preconceived ideas and notions about what things mean and why certain things are the way they are. But every now and then you learn something that changes the way you see things… something that just blows your mind.

Growing up we had a small, colorful tapestry that my parents used to help decorate our laundry room. It was about two feet wide and three feet high and had a woman and a boy on it and the woman was holding a fruit basket on her head with one hand. On the bottom it said Hecho en Mexico.

Until I was about 15 I thought this meant Woman in Mexico. I must have passed it hundreds of times growing up, never questioning what it meant. Then one day in high school Spanish class I learned that hecho actually meant made in, not woman. It said Made in Mexico. Doh.

I went home and told my mom who had a similar “hrmph” reaction. We had held this idea in our head for such a long time and just like that, with a little bit of new information, the tapestry changed.

Here’s an interesting little exercise that Andy Hunt told us to try at the Philly Emerging Tech conference:

Close your eyes for a few seconds and think of the color red.

Open your eyes and look around.

Everything that’s red should stick out.

How do you know what to look for if no one tells you to look for it?

Fortunately, there’s an easy, often neglected way to broaden your knowledge: try something new.

Read the same blog everyday? Try a new one.

Been using the same programming language for 10 years? Been there… Try a new one.

Take the same route to work everyday? Try a new one.

Is English the only language you know? Try a new one.

Same wife everyday? Try a new one. (Just kidding honey)

Bottom line: the more experiences have the more things you’ll learn along the way. And the more you know, the more Hecho en Mexicos you’ll discover in your own life.

Creating a TetriNET Bot


A Tetris Artificial Intelligence system can be created to play TetriNET, an online multiplayer Tetris game, with the speed and skill necessary to play competitively against human opponents. The program must act as intelligently and as human as possible so as to not be noticed as any different from any other adroit TetriNET player.

Rewind about seven years to the end of my Junior year in high school and you probably would have found me immersed in an addictive online game called TetriNET.

TetriNET, for the unenlightened, is an multiplayer online Tetris game that lets up to six people to compete against each other for Tetris glory. Unlike regular Tetris, you obtain special blocks throughout the match that have the power to help you (such as clearing your entire field) or to push your opponents closer to losing (such as adding a line to their field). The goal is to be the last man standing. If you or your team are the only ones left playing after everyone else has lost, you earn points. Most servers kept track of your points and the more you played and the better you were, the higher up the leaderboard you moved.

I played on Tetridome along with four or five people that I went to school with. We were Team Wellington and we had a bitter rivalry with our arch nemsises, Team Lithuania.

By that time I had a fair amount of experience writing add-on software (see: Meridian and AOL-Files) and decided it would be an interesting challenge to write a bot that could play TetriNET for us. With a bot, we could leave the software on overnight, rake up a few thousand points, and then boast of our success to the wretched Lithuanians.

With encouragement from my fellow TetriNETers, in late 2002, I started working on a TetriNET bot.

Most of the time I spent working on it is a blur at this point. Somehow, after a few dozen iterations and a few hundred hours of work later, I had not only a working TetriNET bot, but also, remarkably, a girlfriend (who is now my lucky wife–hi honey).

In the following weeks team Wellington rocketed to the top of the leaderboards, but our success was short lived.

The problem was that the bot was too good.

The first problem was its speed. Normally there is a 1 second delay between when you placed a block and when the next one appeared. Well there’s a nifty little program called Resource Hacker which lets you edit resources in some Windows applications. With its help, I set that 1 second delay to zero:

The second problem was the bot’s duration. I let the bot play some marathon 12-hour sessions, which no human could possibly pull off. Our opponents caught on, notified the administrators, and Team Wellington was no more.

At the time, Eugene, one of the members of Team Wellington, was enrolled in a science fair class at school. He suggested I put a cute little outfit on the TetriNET bot and submit it to the science fair as a research project in Tetris Artificial Intelligence. I already had the thing written so all I had to do was put together some documentation (which was a pain in the ass–see below) and create a project board (thanks mom) and I was in the game.


The 2003 South Florida Science and Engineering Fair was held at the South Florida Fairgrounds in early 2003.  I don’t remember a lot about it, other than feeling awkward explaining to some women from IEEE that I built the thing to cheat online. It all worked out though because a few days later they announced that I had won first in the computer science category as well as a few hundred dollars in scholarship money (some of which was from IEEE). Go me!

A few weeks later the winners from each of the categories went to DC to compete in the International Science Fair.

There were some really amazing projects. One guy had built some facial recognition software using his mother’s camcorder; another was such a talented graphic designer that no one could tell the difference between his work and real photos; and the guy that eventually wound up taking first had created a robot dog or something mindblowingly complicated like that.

Though the bot’s run on TetriNET was short lived, I learned a ton in the process. The experience laid the foundation for a PokerStars No Limit Hold’em bot that I would undertake a few years later–but that’s a long story for another day.

Below you’ll find all of the original science fair documentation for the TetriNET bot as well as the source code for the project.

Cheers —

Source Code

The project was written in Visual Basic 6 and after lots of refactoring and polishing up for the science fair, it weighs in at a modest 1,374 lines of code.

View on Github

Note: The project is not commented very well (in fact there are a total of 20 lines of comments), but the documentation below should be plenty for anyone interested analyzing it.


  • TetriNET has a customizable background. To make it easier for the bot to analyze, I created a custom one, as shown below. You can download it here.

  • There are two methods of play. One is called “Standard” which means that it plays normally. The other is called “Skilled” which is a euphemism for “Cheat”. With the help of hex editor, I changed the TetriNET executable so that I could skip blocks I didn’t want to use. For example, if I needed a long piece, I could hit the down arrow until one showed up without having to place any of the intermediate blocks. Cool huh? Since I originally built the bot to win points, I had it take advantage of the exploit by only choosing blocks that it could play well.

  • You can download the hacked version of TetriNET v1.13, which lets you skip unwanted pieces, here.

Science Fair Documentation


In 1985 Alexey Pazhitnov created Tetris. Tetris became an immediate hit throughout the world. Tetris is one of the few games that achieved ultimate popularity. It is remarkably simple, yet remarkably difficult. It’s been converted to every computer and game console, and has sold millions of cartridges, tapes, and disks across the world since its inception. In 1997 St0rmCat created a new kind of Tetris called Tetrinet.

Tetrinet is an online multiplayer Tetris game that allows a player to compete against up to five other players. Several important additions to this version of Tetris are:

  • Multiplayer capabilities
  • The addition of specials and weapons for offense and defense
  • Lines added to all feature
  • A chat area

Tetris appears to be a straightforward game, but the skill of the player has a huge affect on the game. Block speed, measured in BPM (Blocks per Minute), knowing how to place the pieces, and knowing how to use the specials are all of critical importance. These features also make Tetrinet unique.

I became interested in TetriNET in around May 2002. Many of my friends played, and I greatly enjoyed playing. After a break over the summer, I began to consider the idea of building an Artificial Intelligence program to play TetriNET for me.

The question that consumed my thoughts and eventually became the hypothesis for this project was “Can a computer program be created to play TetriNET with the speed and skill necessary to play competitively against a human player?” The idea of building an Artificial Intelligence program seemed daunting to me at first, but after a week of designing concepts on my binders and papers at school and at home, I decided to go ahead with the construction.

What follows is the development of the program I dubbed P.A.W.N. – The Piece and Weapon Navigator.


The Tetris Artificial Intelligence project is result of several months of work to develop a program that can play an online, multiplayer Tetris game called TetriNET. The initial idea was to build a program that would play cometitively against the other opponents and that would act as human as possible.

The program was named P.A.W.N., the Piece And Weapon Navigator.

Research and design resulted in two different methods of play. The first, which was dubbed the Skilled Method, was the method that was first developed. It functions by creating two matrices: one that is the combination of heights of the playing field and the other is the bottom of the piece to be placed. These two can be compared and a position for the piece can be determined. The second method analyzes the entire field block by block as well as the piece, block by block. A simulation of the piece falling in every possible position with every possible number of rotations is created, and based on information like the piece’s height, leftward position, width, number of lines cleared, number of problems caused by the block, and number of holes created the optimum position can be found.

The Skilled Method is a much more efficent and competitive player than the Standard Method.

Live game statistics are displayed on the main form.

This project is another example of how computers can be made to automate tasks that humans perform and perform them with such speed and skill that humans, even the creators, are outplayed and outperformed by their creations.


A Tetris Artificial Intelligence system can be created to play Tetrinet, an online multiplayer Tetris game, with the speed and skill necessary to play competitively against human opponents. The program must act as intelligently and as human as possible so as to not be noticed as any different from any other adriot Tetrinet player.

Artificial Intelligence

No true AI system truly exists. Humans have not yet made the leap from a computer following its own code to an actually possessing a consciousness. However, the term Artificial Intelligence is now used to describe any system that makes decisions intelligently.

According to, “Knowledge Based AI is also programmed to modify it’s own programming based on the feedback it gets from its own programmed senses. Those senses can be mechanical such as those used in robots, light sensitive such as those used in SIGHT or data sensitive such as those used in the worlds largest databases.”

Rules Based Artificial Intelligence, as opposed to Knowledge Based, uses a defined set of rules for a limited number of possibilities to determine what course of action to take.

PAWN uses a combination of both forms of Artificial Intelligence. It uses Knowledge Base AI because it analyzes the field by pixilating the screen (that is, analyzing it pixel by pixel). There can be millions of combinations of different field combinations as well as different available specials. Determining where to place the current piece is an example of Knowledge based AI. Determining which special to use and on who is an example of how PAWN utilizes Rules Based AI. Each special has a set of rules it follows to determine which opponent to use the special on or whether to use it on itself.

Several examples of other applications of AI systems are insurance programs, large scale database programs, vacuum cleaners that analyze the layout of a room in order to vacuum it efficiently, robots that build cards, and parts of the national defense system.

Artificial Intelligence, although not near a level as shown in movies like The Matrix, are becoming much more advanced. One source explains how true AI systems (systems that are conscious) may be developed within the next 150 years. For now, Artificial Intelligence systems will continue to be developed to make our lives and jobs easier.


1. Load TetriNET v1.13 when the Pawn loads, if Tetrinet is not already loaded.

2. Set the location of Pawn agains the far right of the screen.

3. Click RUN

4. Make sure the game is on. If not display message box with problem.

5. Place TForm2 (Playing Field form) at correct position on screen.

6. Reset statistics on screen.

a. Reset lblBlocksDropped’s caption
b. Set lblStart’s equal to the current TickCount
c. Reset lblSticks’s caption
d. Reset the special’s history window in TForm2

7. Enable the drop timer.

8. Make sure that TForm2 is still the active window.

9. Make sure the game is still on.

10. Determine whether the user chose to use the Skilled Option or the Standard Analyzation method.

Skilled Method

11. Make sure the field is still active and the game hasnt ended.

12. Drop the block.


13. Determine the field by MatrixFieldSkilled

14. Determine the lowest column, highest column

15. Analyze the lowest row.

16. If a piece isnt up on the screen, press DOWN and wait till one appears.

17. Determine dLevel

To understand the imortance of dLevel, it is essential to understand how         this analyzation method works.

Example field:

MatrixFieldSkilled returns a matrix of

3 3 2 0 3 4 5 2 2 5 3 4

Say our block is

MatrixBlockSkilled returns a matrix of

2 0

The Field Matrix contains the Block Matrix. Esentially, that is how the skilled analyzation method works.

dLevel changes the level that functions as the 0 level of the matrix.

For example, look at the following field and block.

MatrixFieldSkilled = 0 1 3 3 3 3 2 1 0 1 2 1


MatrixBlockSkilled = 0 0

Based on the field matrix and the block matrix, it doesnt appear that the block fits. However, just looking at the field shows that it does. On top of the second and third column, for one example. Pawn finds this by offsetting the original matrix by subtracting one from the original matrix each time. It tests different levels where the zero level shift up one each time.

The second level would be an offset of 1 creating a new matrix of

-1 0 2 2 2 2 1 0 -1 0 1 0

Now does the block 0 0  fit? No, still not. Try one more level.

-2 -1 1 1 1 1 0 -1 -2 -1 0 -1

How about now? Still not. One more:

-3 -2 0 0 0 0 -1 -2 -3 -2 -1 -2

Now, 0 0 does go.

dLevel determines how many times, how many levels, we try before we stop. The higher dLevel is, the longer the program will take to analyze all the rows.

dLevel is based on how high the field is. The higher the field is, the lower dLevel, because Pawn tries to lower the field as much as possible. At the bottom, when the field is low, Pawn can take his time; he can analyze a few levels up. At the top, he all Pawn wants to do is move the field down. Also, if there is an N (Nuke), S (Switch), or O (Block Bomb) in range, we want to get to those as quickly as possible, so dLevel would be low.

18. Determine how many times we need to rotate the block. We figure this out based on the structure of the block, determined by MatrixBlock.

19. Now we test the block at different levels. We test each level with however many different rotations is possible for the block.

20. Update the number of seconds the game has been playing.

21. Now we perform another check. This check has to do with what is under the spot where we place the piece. Look at the following field:

With block:

A possibility would be:

What this check does is assure that it doesnt go there. The reason is that under the block is a blank space. Putting the piece there would cover up that hole and make reducing the field all the more difficult. So, even if the block fit in that position, Pawn would not place it there because of this check.

22. If the piece fits and passes the checks, then we drop the block. If it doesn’t fit anywhere in the current row, then we try again at the next dLevel and so on until the last dLevel. If it fits no where at any level, then Pawn simulates pressing DOWN, which skips the current piece and goes to the next piece. It starts from     the beginning, and attempts this all again.


23. Determine where in the matrix that was provided the piece fits.

24. Move the piece to the left 8 spots to ensure it is against the left wall.

25. Move it right the number of spots that was calculated from matrix and the block.

26. Simulate pressing Space to drop the piece via the PressKey function.

Standard Method

27. If the user chose the Standard Analysis method, then the function DropSkilled will be called.


28. Make sure the 18th row doesnt have any blocks in it. If it does, then stop the program, because Pawn died (game over). If not, continue on.

29. Set the variable initial_piece = MatrixBlock. This piece will be used throughout the rest of this analysis.

Example: The following field and block will be used as an example throughout the explanation of how this method works.



30 The block is tested in every possible position. For this example, we will use only the one shown above, but keep in mind the other possibilities, which are tested when actually determining the optimum position of the piece are:


31. The piece is simulated in every possible position from left to right. The first position would be:


And so on. The position that will be used throughout this example is:

32. The first step of simulating the fall is the FallBlock function.


33. Determine the part of the matrix before and after where we are going to simulate the block fall.

34. Determine the middle part

35. Use that middle part to call the CompleteFall function.

In this case, the middle part is


The block is


36. CompleteFall simulates moving the block down that field until it hits the bottom. It returns the result:


37. By looking at it (dont forget it is rotated) it can be seen that the characters are:

Bottom: 1
Top: 3
Left: 7
Lines Cleared: 2 (see the two bottom rows are complete with either 1’s or a P’s)

Pawn must determine this though. It calls the function AnalyzeField.

38    AnalyzeField analyzes the matrix returned in CompleteFall. It returns the result:

mHole: False LC: 2 H: 00 PRB: False T: 03 L: 07 P: 010/111

This function is carefully designed. The most important characters are first.

Variable Value Meaning
mHole False When dropping the block, it may clear a line or two, however, if clearning the lines results in a block above a hole, causing more problems, the position is not taken.
LC 2 Lines Cleared. All the values of AnalyzeField are added to a list. The top item in the list is the optimum place. The list is alphabetized, so if the block clears 1 line, we might put LC:1, and if it clears 4, we would put LC: 4. However, since the list is alphebetized, Pawn would take the 1 line clear over the 4 line clear. To solve this problem, all the values returned are 4 – the true value. That way, if 4 lines are cleared, a 0 is returned and it is placed at the top of the list.
H 0 Number of holes below the piece
HPRB False Problem variable. If the piece causes large holes that can only be filled with a stick, we dont take it since sticks are rare.
T 3 Top row of the piece
L 7 Position/Left value of the piece
P The piece. This is used when dropping the block.

These are calculated for every possible position and rotation of the piece. For example, this piece, on this field, returns 42 values:

mHole: False LC: 2 H: 00 PRB: False T: 03 L: 07 P: HPH-PPP-
mHole: False LC: 4 H: 00 PRB: False T: 04 L: 06 P: HPH-PPP-
mHole: False LC: 4 H: 00 PRB: False T: 07 L: 04 P: HPH-PPP-
mHole: False LC: 4 H: 00 PRB: False T: 07 L: 11 P: HPH-PPP-
mHole: False LC: 4 H: 00 PRB: False T: 08 L: 01 P: PPP-HPH-
mHole: False LC: 4 H: 00 PRB: False T: 08 L: 02 P: HPH-PPP-
mHole: False LC: 4 H: 01 PRB: False T: 03 L: 06 P: PH-PP-PH-
mHole: False LC: 4 H: 01 PRB: False T: 04 L: 07 P: PPP-HPH-
mHole: False LC: 4 H: 01 PRB: False T: 04 L: 08 P: PPP-HPH-
mHole: False LC: 4 H: 01 PRB: False T: 05 L: 06 P: PPP-HPH-
mHole: False LC: 4 H: 01 PRB: False T: 05 L: 08 P: HPH-PPP-
mHole: False LC: 4 H: 01 PRB: False T: 06 L: 04 P: PH-PP-PH-
mHole: False LC: 4 H: 01 PRB: False T: 06 L: 05 P: HPH-PPP-
mHole: False LC: 4 H: 01 PRB: False T: 07 L: 02 P: PH-PP-PH-
mHole: False LC: 4 H: 01 PRB: False T: 07 L: 03 P: HP-PP-HP-
mHole: False LC: 4 H: 01 PRB: False T: 07 L: 05 P: PPP-HPH-
mHole: False LC: 4 H: 01 PRB: False T: 07 L: 09 P: HP-PP-HP-
mHole: False LC: 4 H: 01 PRB: False T: 07 L: 09 P: PPP-HPH-
mHole: False LC: 4 H: 01 PRB: False T: 07 L: 10 P: HP-PP-HP-
mHole: False LC: 4 H: 01 PRB: False T: 08 L: 03 P: HPH-PPP-
mHole: False LC: 4 H: 01 PRB: False T: 08 L: 03 P: PPP-HPH-
mHole: False LC: 4 H: 01 PRB: False T: 08 L: 04 P: PPP-HPH-
mHole: False LC: 4 H: 01 PRB: False T: 08 L: 09 P: HPH-PPP-
mHole: False LC: 4 H: 01 PRB: False T: 08 L: 11 P: PPP-HPH-
mHole: False LC: 4 H: 01 PRB: False T: 09 L: 01 P: HPH-PPP-
mHole: False LC: 4 H: 01 PRB: True T: 08 L: 10 P: HPH-PPP-
mHole: False LC: 4 H: 01 PRB: True T: 08 L: 10 P: PPP-HPH-
mHole: False LC: 4 H: 01 PRB: True T: 09 L: 02 P: PPP-HPH-
mHole: False LC: 4 H: 02 PRB: False T: 03 L: 07 P: PH-PP-PH-
mHole: False LC: 4 H: 02 PRB: False T: 04 L: 06 P: HP-PP-HP-
mHole: False LC: 4 H: 02 PRB: False T: 04 L: 07 P: HP-PP-HP-
mHole: False LC: 4 H: 02 PRB: False T: 05 L: 05 P: PH-PP-PH-
mHole: False LC: 4 H: 02 PRB: False T: 06 L: 05 P: HP-PP-HP-
mHole: False LC: 4 H: 02 PRB: False T: 06 L: 08 P: PH-PP-PH-
mHole: False LC: 4 H: 02 PRB: False T: 07 L: 03 P: PH-PP-PH-
mHole: False LC: 4 H: 02 PRB: False T: 07 L: 04 P: HP-PP-HP-
mHole: False LC: 4 H: 02 PRB: False T: 07 L: 08 P: HP-PP-HP-
mHole: False LC: 4 H: 02 PRB: False T: 07 L: 10 P: PH-PP-PH-
mHole: False LC: 4 H: 02 PRB: False T: 08 L: 01 P: HP-PP-HP-
mHole: False LC: 4 H: 02 PRB: False T: 08 L: 01 P: PH-PP-PH-
mHole: False LC: 4 H: 02 PRB: False T: 08 L: 02 P: HP-PP-HP-
mHole: False LC: 4 H: 02 PRB: True T: 07 L: 09 P: PH-PP-PH-

39. Update the status window showing the optimum block.

40. Based on the top value in this list, the block is dropped.

41. Now back to tmrRun.

Specials Procedure

42. Use the front special. Call UseWeapon function.



Determine whether Pawn’s field is very high and close to death. If it is, then use all remaining specials to help reduce field and destroy opponents.

If there is not a N (Nuke or Gravity) or S (Switch) then loop through all the     specials and use any C (Clear Lines) on himself and any other weapons on the     LowestPlayer.

If there is an S (Switch) then use it on the lowest player (if the lowest player’s     field is above 10 rows high then just drop the switch and continue on).

If there is an N (Nuke or Gravity) then use it on itself.

Otherwise, use the weapons on the lowest player and hope that player dies before     Pawn or that Pawn reduces his field.

44. If there is only 1 player remaining, and Pawn has acquired enough A’s to destroy     the other player, then do so, dropping any unnecessary specials.

45. Use the first special:

B (Clear Special Blocks) See AnalyzeWRTB
R (Random Clear Blocks) See AnalyzeWRTR
Q (Quake) Switches between AnalayzeWRTQ and GetQuakable
C (Clear Line) See AnalyzeWRTC
A (Add Line) See AnalyzeWRTA
O (Block Bomb) See AnalyzeWRTO
N (Nuke or Gravity) Use on himself only if the CriticalLevel has been reached.
S (Switch) Destroy own field and switch with the lowest player.

46. Go back to tmrRun and calculate stats:

47. BPM – Blocks per Minute – This is an average that changes gradually throughout the game.

48. Instantaneous BPM – Different between each block. This is not dependent  on the blocks from before.

49. APM – Lines Added Per Minute. See APM Function

50. Finished – The timer then repeats itself until something turns it off.

modNormal Functions


Variables: None

Purpose: Returns a matrix composed of 0’s and 1’s that represents the block in its standard position. The block matrix is with respect to the bottom of the field. Imagine the bottom of the field being to the right of the matrix shown.

Operation: Uses the BlockColumn to determine each column in the four by four grid that the block goes on. After a matrix of all four columns is constructed, the additional column of zeros, if present, needs to be removed, since it is not part of the block. These are removed, and an accurate matrix is returned.


MatrixBlock =


Variables: Column

Purpose: Returns a series of four digits composed of 0’s and 1’s that represent the presence (a 1) or the absence (a 0) of a specific column of a block in its standard position.

Operation: Determines the color of each block in the column by using the GetColor function. If the color is white, the value is 0, if blue, then 1. These four values are combined into a single string. The string represents the entire column.


BlockColumn(2) = 0110


Variables: None

Purpose: Returns a 18 x 12 matrix that represents the presence of blocks with 1’s and 0’s of the Tetris field. Keep in mind that the matrix is rotated, so that it is easier to simulate the block falling later on.

Operation: Uses GetColor to determine the color of every block in the matrix. These values are combined into a single matrix which represents the entire field.


MatrixField =


Variables: matrixSource – The matrix whose value you want to return the number of rows it contains.


Operation: Counts the number of line breaks are in the source.


MatrixRowCount = 3



matrixSource – Matrix to analyze
rowNum – Row to return

Purpose: Returns a specific row in a matrix

Operation: Loops through the source until the number of line breaks equal to rowNum have passed. Then it parses that row and returns the string value of it.


MatrixRow(3) = 00000111



matrixSource – Matrix to analyze
columnNum – Column to return

Purpose: Returns a specific column in a matrix

Operation: Analyzes each row, then parses that row to find a specific location in that row. It combines this character in every row, forming a column.


MatrixColumn(4) = 0


Variables: columnNum – The column in the playing field whose height is to be returned

Purpose: Returns the height of a column in the playing field

Operation: Determines the color of each block in that row, starting from the top. When it encounters a block that is not white, it returns the number of blocks from the bottom that is. This is equal to that column’s height.


FieldColumnHeight(3) = 5


Variables: None

Purpose: Drops the block it its most optimum position. Keep in mind this is form Standard Analysis only.

Operation: The most optimum position is the string at the top of the lstPositions list. This string is parsed so that the position and orientation of the block can be found. Then, the block on the actual playing field is rotated so that it is equal to this block. It is then dropped by moving it all the way to the left, then a specific number of spots right. It is then dropped by simulating pressing the Space bar.


‘Straight forward



Variables: matrixSource -the matrix that was formed by simulating the placement of the Block
Block – the matrix of the block that was simulated falling
Position -the position where the block was placed

Purpose: This is the key to the Standard Analyzation method. This function returns a string that represents all the pertinent characteristics of a block in any position on the field.

Operation: This function performs a series of analysis on the matrixSource, the matrix that has the block already simulated fallen. For more explanation, see the procedure.


‘See procedure.



matrixSource -Field Matrix
Block -Block to simulate falling
Position – Position where to simulate falling

Purpose: This function returns a matrix of how the field would be if a block was dropped in a specified position. This is fed into AnalyzeField, which analyzes it and returns the statistics on the matrix returned by this function.

Operation: Combines 3 matrices. The first is the part of the matrix before the part to simulate falling. The second is the part where the piece was simulated fallen and the last is the part after the simulation.


‘See procedure.



Field – section of the matrix where to simulate the fall
Block -block to simulate falling

Purpose: This function actually simulates dropping a block at a specified position of the field, represented by the Field variable.

Operation: See procedure


‘See procedure



matrixSource -matrix to parse
StartCol – first row (but field column on the field) to start with
EndCol – last row (again, field column) to end with.
For more info, see the procedure.

Purpose: Returns a set of rows contained in a matrix.

Operation: Goes through the matrix and combines the set of rows that is specified by StartCol and EndCol.


matrixSource =
startCol = 2
endCol = 3
MatrixPart(matrixSource, 2,3) =



Source – String to search
Character – Character to search for

Purpose: Returns the last occurance of a character in the source string

Operation: Parses the string and records where each occurance of the Character is. The greatest value is the last occurrence of the character.


LastOccurance("abcdeafedcba", "a") = 12



Matrix -the matrix that represents the block
xRotations – the number of times to rotate it

Purpose: Simulates rotating a block. Used in simulating a fall.


The original block:

xRotations = 1:

Actual appearance:



xRoations = 2:

Actual appearance:



xRoations = 3:

Actual appearance:



xRoations = 4:

Actual appearance:




Variables: None

Purpose: Drops the current piece in its optimum position.

Operation: See procedure

Example: See procedure

modSkilled Functions


Variables: Matrix – matrix to analyze

Purpose: Returns the lowest numerically valued item in a matrix

Operation: Parses through the matrix and determines if each item is the lowest.


MatrixItemLowest(" 8 6 4 10 120 5 1 80 ") = 1


Variables: Matrix – matrix to analyze

Purpose: Returns the highest numerically valued item in a matrix

Operation: Parses through the matrix and determines if each item is the highest.


MatrixItemLowest( 8 6 4 10 120 5 1 80 ") = 120



MatrixOrig – matrix to subtract from
offSet – number to subtract from each item in the matrix

Purpose: Subtracts a number from every item in a matrix

Operation: Parses the matrix to determine each value and then forms a new one by subtracting the value specified by offSet from each value.


MatrixSubtract(" 10 8 5 3 ", 2) = " 8 6 3 1 "


Variables: None

Purpose: Returns a matrix that represents the playing field. The matrix is a single row where each item represents the height of that particular column.

Operation: The program each column on the playing field, starting from the top. Once it encounters a block that is not white, it adds that to a matrix. This matrix is added after each column is analyzed. The final matrix contains the height of every column.


MatrixFieldSkilled = " 0 1 2 3 3 4 5 6 5 3 4 4 0 "


Variables: None

Purpose: Returns a one row matrix that represents the current piece to drop.

Operation: The lowest block in the piece is the base row. Any block at this level is represented by a 0. Each level above it adds one value. See example below.


 MatrixBlockSkilled = " 1 0 "
 MatrixBlockSkilled = " 1 0 0 "
 MatrixBlockSkilled = " 0 "
 MatrixBlockSkilled = " 1 0 1 "


Variable: Row-Number of row to analyze

Purpose: Analyzes a row on the field.

Operation: Determines the color for each block in the row. If there is a empty space, a 0 is added to the string. If a block or a special, a 1 is added to the string.


AnalyzeRow(1) = " 1 1 1 0 0 1 1 1 1 1 0 1 "



Purpose: Determines if the current piece fits anywhere in the field. If it does, then it drops it.

Operation: See procedure

‘See procedure



mField – matrix to analyze
mBlock -block to place in the field

Purpose: Drops the block in the field

Operation: Determines the placement of mBlock in mField by using the InStr function. To drop the block, the block, it is moved to the far left, then moved right by whatever value the InStr function returns.

See procedure

modSpecials Functions


Variables: Position – Position, not number, of the player whose field matrix is to be returned

Purpose: Returns the field matrix of a specific player

Operation: Like MatrixField, it analyzes the field by analyzing the color of the pixels on that part of the screen.


OpponentFieldMatrix = " 6 5 6 7 7 6 6 7 7 7 7 7 "


Variables: None

Purpose: Analyzes every other player’s field to determine which player to use the specials B, Q, or R on.

Operation: The way the program determines who to use the special on is as follows:

1) If an S (Switch) is present
2) If an N or G (Nuke or Gravity) is present
3) If an O (Block Bomb) is present
4) Whoever has the most specials on his or her field


AnalyzeWRTBQR = 3

because number 3’s field has an O (Block Bomb – Red) on it.


Variables: None

Purpose: Analyzes every other player’s field to determine which player to use the special A (Add Line) on.

Operation: The way the program determines who to use the special on is as follows:
1) Whoever has the lowest field


AnalyzeWRTA = 4

because the player in position 4 has the lowest field


Variables: None

Purpose: Analyzes every other player’s field to determine which player to use the special O (Block Bomb) on.

Operation: The way the program determines who to use the special on is as follows:
1) Whoever has an O (a red square) on his or her field.
2) If more than one player has an O on his or her field, then the player who has the lowest field.


AnalyzeWRTO = 3

because the player in position 3 has an O (a red square) on the field


Variables: None

Purpose: Analyzes every other player’s field to determine which player to use the special C (Clear Line) on. At first it would seem like the best place to use a clear line on would be one’s own field. However, if another player has a good special on the bottom of their field and he or she is able to get it, the Clear Line should be used on him or her, so that he or she cannot get that special.

Operation: The way the program determines who to use the special on is as follows:
1) If a player has an N, G, or S on the bottom of his or her field.
2) If a player has an O (Block Bomb – red square) on the bottom of his or her field and is able to get it.
3) If a player has any special on the bottom of his or her field.
4) If none of these other conditions is true, then use it on one’s own field.


AnalyzeWRTC = 1

because the player in position 1 has 2 weapons on the bottom of his field and is capable of getting them.


Variables: None

Purpose: Returns a string containing each of the player’s numbers who are playing.

Operation: First, the program determines the numbers of the players who are in the room by analyzing the player list on TForm1. Then it goes through each of these players and determines who is still playing (not dead) by analyzing their fields through the IsPlaying function.


NumbersPlaying = "136"


Variables: None

Purpose: Returns my number

Operation: Goes through each of the panels that contain the numbers and nick name. If nick is mine, then parse that string and returns the number, which is mine.


myNumber = 5


Variables: Position – Position of the player whose field is to be analyzed

Purpose: Determines whether a player is playing or not

Operation: Analyzes the bottom row of the player’s field. If the field contains both holes and blocks, then he or she is playing.


IsPlaying(1) = False'Only background on bottom row
IsPlaying(2) = True 'Has both grey and background on the bottom row
IsPlaying(6) = False'Has only grey on the bottom row


<!– /* Font Definitions / @font-face {font-family:”Cambria Math”; panose-1:2 4 5 3 5 4 6 3 2 4; mso-font-charset:1; mso-generic-font-family:roman; mso-font-format:other; mso-font-pitch:variable; mso-font-signature:0 0 0 0 0 0;} / Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-unhide:no; mso-style-qformat:yes; mso-style-parent:””; margin:0in; margin-bottom:.0001pt; mso-pagination:widow-orphan; mso-layout-grid-align:none; punctuation-wrap:simple; text-autospace:none; font-size:10.0pt; font-family:”Times New Roman”,”serif”; mso-fareast-font-family:”Times New Roman”;} .MsoChpDefault {mso-style-type:export-only; mso-default-props:yes; font-size:10.0pt; mso-ansi-font-size:10.0pt; mso-bidi-font-size:10.0pt;} @page Section1 {size:8.5in 11.0in; margin:1.0in 1.0in 1.0in 1.0in; mso-header-margin:.5in; mso-footer-margin:.5in; mso-paper-source:0;} div.Section1 {page:Section1;} –>

Variables: Number – Number of the player whose position is needed

Purpose: Converts a player’s number to his position. The difference is explained below.

Operation: See below.


In yellow is the player’s position. Position is always constant. The player’s number depends on
my number. To go from number to position, do the following:

In the example above, my number is 5.
If the number is less than mine, then the position equals the number.
If the number is greater than mine, then subtract one from the number.


Variables: Position – Position whose number is needed

Purpose: Converts a player’s position to his or her number.


GetNumberOfPosition(5) = 6 'On the example above


Variables: None

Purpose: Determines whether or not a C (Clear Line) should be used on Pawn’s own field.

Operation: Analyzes the first and second row in Pawn’s field. If there is an empty space in a spot in the first row, and a block in the same position in the second row, then a C can be used.



Variables: None

Purpose: Determines who has the lowest field. Returns his or her number.

Operation: Analyzes each player’s field by using OpponentFieldMatrix. Whoever has the lowest value on their field matrix has the lowest field.


LowestPlayer = 2


Variables: None

Purpose: Returns a list of every special that Pawn has.

Operation: Analyzes the colors of each special in my list. Determines the function of the weapon based on the color.




Variables: None

Purpose: If Pawn’s field is at or above the 14th row, then the Critical Level has been reached.

Operation: Simply Analyze the 14th and 15th row and see if any blocks are there. Normally the 14th row would do, but since Block Bomb’s might put a block above the 14th row, and not the 14th row, to be safe the 15th row is analyzed too.


CriticalLevel = True


Variables: None

Purpose: Determines whose field a quake will do the most damage to.

Operation: Analyzes each player’s field by using OpponentFieldMatrix. Each item in the matrix is compared with the ones to each side of it. Since Quakes work best on fields where there is a high column with shorter ones next to it, this characteristic is searched for by Pawn.


GetQuakable = 1


Variables: None

Purpose: Determines how to use the current special that Pawn has

Operation: See procedure

Example: See procedure


Variables: None

Purpose: Determines which weapons, on Pawn’s field, are capable of being obtained.

Operation: First, the lowest column is determined by MatrixFieldSkilled. Then Pawn analyzes each column from top to bottom, until it reaches that lowest level. This returns a pretty accurate results of which weapons are capable of being obtained.


SpecialsInRange = OBACO

modNormal Functions


Variables: sSeconds – The number of seconds to pause for

Purpose: Pauses the program for a specified number of seconds.

Operation: This function utilize API function GetTickCount which returns
a value in milliseconds for how long the computer has been running. Find a value for what it is, then set up a loop until the number of seconds specified has gone by.


Timeout 5- Pauses the program for 5 seconds


Variables: hWnd-The handle of the window whose text is to be returned

Purpose: Returns the text from a specified window

Operation: Uses the API functions SendMessage using the WM_GETTEXTLENGTH and WM_GETTEXT to return the value of the text in a specific handle.


tPanel = FindWindowEx(tForm2, 0, "TPanel", vbNullString)
myNick = GetText(tPanel)
Msgbox "My number and nick are " & myNick, vbInformation


Variables: iconHWnd-Handle of the button to be clicked

Purpose: Simulates clicking a button/icon.

Operation: Uses SendMessage to simulate clicking a button utilizing WM_LBUTTONDOWN and WM_LBUTTONUP


‘Simulates clicking the Start Game button

tPanel = FindWindowEx(tForm1, 0, "TPanel", vbNullString)
tButton = FindWindowEx(tPanel, 0, "TButton", "Start Game")
ClickIcon tButton


Variables: xCoor, yCoor-The coordinate on the screen

Purpose: Returns, in hex, the value of a specified pixel on the screen

Operation: Determines the handle of the window from the specified point by utilizing WindowFromPoint. Then it determines the color from the specified point by using the GetPixel and GetDC functions.


colorTopLeft = GetColor(0, 0)



sourceString – String to search
sChr – Character to search for

Purpose: Returns the number of times a specified character is in another string. It is case dependent.

Operation: Parses the sourceString character by character, determining if each one is equal to sChr


Msgbox "The word 'tetrinet' contains " & CountChr(“tetrinet”, “t”) = 3 & " t's", vbInformation


Purpose: Returns the handle of the main Tetrinet window titled “TetriNET v1.13”


Purpose: Returns the handle of the Tetrinet game window titled “TetriNET Playing Fields”


Purpose: Moves TForm2 to a position that makes it 10 pixels from the left side of the screen and 100 pixels from the top of the screen. This is vital because all the matrix functions that analyze the field by pixel use set locations based on this position of TForm2.


Variables: CategoryName – Title of the category (“Show Fields”, “Partyline”, etc)

Purpose: Clicks one of the categories (Show Fields, Party Line, Win List…) at the bottom of TForm1. Primarily used to activate the game window (TForm2)

Operation: Goes through each category by using the GetWindow function and ermines if that category’s title is equal to the specified category name. If is, then return that category’s handle.


'Clicks the "Show Fields" button
ClickCategory "Show Fields"


Variables:lstBox – The listbox to search
strSearch – The string to search for

Purpose: Searches a specified list box for a specified string. Returns a boolean value true if the listbox does contain the string and False if it does not.

Operation: Goes through a listbox and determines the listbox’s entry.


Variables: Key – Value of the key to press

Purpose: Simulates the pushing of a speified keyboard key.

Operation: Uses the API function keybd_event


PressKey Asc(“D”)'Simulates pressing D
PressKey VK_DOWN 'Simulates pressing the Down arrow
PressKey VK_SPACE'Simulates pressing the Space bar

Game On

Variables: None

Purpose: Returns a boolean TRUE or FALSE value that represents whether the game on on or not.

Operation: The easiest way to determine whether a game is on or not is to determine whether the Pause Game button is enabled or not. If the game is on, regardless of whether Pawn has died or not, the Pause button will be enabled. If the game is off, it will be disabled.


'True of False value
Msgbox "The game is running: " & GameOn, vbInformation



vColor:  Color of the new text
vUpdate : Text that is the update
vBold (Optional):Whether the text is bold or not

Purpose: On the main form at the bottom, is the status box. This updates text in there.

Operation: Sets the Rich Text Box’s properties SelColor, SelBold, and SelText


'Updates the status window
UpdateStatus vbBlue, "This text is blue and bold", True



bBlock – the matrix of the block to analyze
addInfo – additional info to display in the text box

Purpose: Displays, for Standard Analyzation method, the block that is optimal while determining the position and orientation of all the pieces

Operation: Analyzes the matrix row by row, the assigns each picBlock a color based on the value of a specific spot in that matrix. Results is an image of the block that the matrix represents.


'For a 4piece horizontal stick
ShowNormDisplay "P P P P " & vbCrLf, "Just an example."


Variables:Block – Matrix of the block to check

Purpose: Updates the display on the Statistics section of the main form that has the number of sticks and percentage of overall blocks that stick’s represent.

Operation: Determines whether the Block represents a stick. If it does, then it updates the label on the main page by adding one to the value. The percentage is then updated afterwards, whether it is a stick or not.


'For skilled analyzation method -- A horizontal stick
CheckSticks  "0 0 0 0 "


Variables: None

Purpose: Updates on the Statistic section of the main form that displays the number of seconds the PAWN has been playing.

Operation: Uses GetTickCount, a function the returns the number of milliseconds the computer has been running. When the game is started, that value is set on a label on the main form. Any time after, the difference between the current TickCount and the initial TickCount can be calculated, and divided by 1000 to return a value of how many seconds the game has been running.


'Straight forward


Variables: None

Purpose: APM stands for Added Per Minute. This is how many lines, added to others, Pawn causes. This is a good representation of how skilled the player is, for the more lines added to others, the quicker and better the player is.

Operation: The easiest way to do this is to analyze the special’s history window in the center of TForm2. This has a history of all specials used, and lines added to all. This text can be parsed and the number of lines added by any player can be summed up. With this total, and the number of seconds playing, the APM can be calculated.


Msgbox "Your lines added per minute equals " & APM, vbInformation



Purpose: If PAWN dies, this function ensures that the program recognizes the death, and ceases game play.

Operation: Determines the color of the top right box in the playing field. If this value is not white, then Pawn has died. It ensures the death by pressing space twice and losing focus on TForm2 by setting focus on TForm1.


'Straight forward


PAWN was a success. The engineering goal to create a competitive TetriNET playing system was achieved. PAWN playing ability became so competitive that it proved to be a daunting opponent for even the best human players.

When playing other players, PAWN plays at approximately the same speed as the best players. For players that want more of a challenge, PAWN can play up to seven times the speed of a good opponent. The demand to play Pawn became so heavy that after completion I set up a room for people to compete against it to increase their skill level and to practice.

The code, while tedious and time consuming, is the final product of five versions of development. I estimate PAWN took approximately 300 hours developing, designing, and testing.

The Skilled version proved to be much a much more efficient player because the time spent analyzing is much shorter than the Standard analyzation method. However, if the computer was faster, the Standard analyzation method would be more efficient because it can drop pieces where they don’t fit and complete more complicated lines. Also, a real Tetris game could not be modified so that the current piece could be skipped. Humans use a method that is between the Skilled Method (seeing where pieces best fit based on the shape of the bottom of the block and the top of the field) and Standard Analyzation (seeing where the piece goes when it doesn’t fit anywhere, where it can complete lines).

If the development of PAWN were to be continued, the Standard Analyzation method would be sped up and made more practical for game use and more competitive. Based on the data, it is much slower and less reliable than its partner, the Skilled version.


A new domain name tool just launched called Wordoid.

The thing I like the most about it is how it lets you enter in a word and then it generates available domain names around that word. Domain Pigeon, on the other hand, only lets you search names that already exist on the site.

There were a few interesting mentions of Domain Pigeon in the Hacker News thread on Wordoid’s launch:

Yeah, I was a fan of for a while, but that got a bit too restricted for me (as it became more commercial), not to mention a bit unwieldy to browse through. Bookmarked!

Wow, it’s like domain pigeon only way better. Awesome.

It’s nice. It reminds me of domain pigeon, but the words it creates seem a lot more natural.

The first point is valid (visitors can’t explore a lot of the names until they sign up). The problem, in my experience, is that its very hard to make money from a site like this purely as an affiliate. Jorge, from, can attest to that fact too.

I’ve had a new feature on the todo list since the site launched that would let you do exactly that, but I’ve let a few technical hurdles stop me from implementing it.

Maybe this is the kick I need to get around to doing it.