In this update, I was confusing myself with (0,0) being in the upper left corner of the screen, so I changed it so that (0,0) is at the center. No noticeable effect on the actual sim, but makes thinking about angles and things a little easier (for me). I also implemented the orbit point functionality – pretty easy because it uses the already created MoveToPoint function. I wanted to make the dots automatically space themselves evenly out along the orbit by speeding up if there is a dot close behind or slowing down if another dot is close in front, but couldn’t get it to work very well, so left that bit commented out.
I also redid MoveToPoint to be a better example of a PID controller. (I tried to implement the diagram found here, fig 6.7.) The gains now are set good enough for the default max velocity and acceleration; probably would need retuning if those are changed.
Download: smartdots_v0.2.zip. Prerequisites are Python and Pygame. Main script to run is smartdots.py. Controls are listed in the status area above the dot box; also run with the -h option to see all other command line options.
I’m not really sure where smart dots will go from here. “Avoid obstacles” is unimplemented, but after that I’m kind of out of ideas. It’s not really a game… I guess the “game” portion of this project was for me getting the dots to do what I want them to – it was kind of fun watching them move and orbit a point. (If you turn on orbit point, then move the action point around, there is some interesting behavior as the dots move while rotating…) But for now “development” on Smart Dots will probably be on hold, unless I can think of somewhere to go with it.
My latest project is not quite a game. (Not yet, at least.) But it is kind of fun to play around with. Smart Dots is a simple, physics-based simulation of a multi-agent system – each agent is autonomous, has a limited view of the system, and is decentralized. For Smart Dots, this means that each dot contains its own kinematics equations and behavioral algorithms, each dot only can “see” a limited distance away, and there is no higher level logic controlling what goes on. (The only thing the higher level UI and “world” classes do is set dot behaviors.)
When the smartdots.py script is run, you will see several dots bouncing around in a window. They each have their own velocity, assigned randomly at the start of the simulation. The user can select a dot to see its position (pixel location), velocity (pixels/sec), and acceleration (pixels/sec^2). There are several inputs the user may enter:
- Left click on a dot to select it (the selected dot will turn green.) Then, by pressing F2, the “Search and Follow” behavior is enabled. The dots will start to follow the selected dot, if they can “see” it (ie, if it is in what I call in the program the “comm range”). If the selected dot is traveling sufficiently slower than the others, then they kind of dance around it.
- Right click on any spot of the window to set the “action point” (designated by a yellow point). Nothing will happen unless F4 is pressed and the “Move to Action Point” behavior is enabled for the dots. The dots will then move to the action point.
- p will toggle pausing for the simulation
- c will toggle a display of the comm range for each dot (displayed as a red circle)
- s will “scatter” the dots, giving each one a new random velocity
- the mouse wheel can be used to apply positive or negative gravity
- Escape will cancel any gravity settings
I have place holders for adding an “Avoid Obstacles” behavior (avoid other dots), and also an “Orbit Action Point” behavior…to be completed in a future version.
Rather than a single screenshot, check out this youtube video I made of Smart Dots:
What is so cool about Smart Dots? Well, it’s not much right now. But I think it can be an interesting framework for testing out simple multi-agent algorithms…and just maybe it can be useful in a future game. We shall see….
Download: smartdots_v0.1.zip. Prerequisites are Python and Pygame. Main script to run is smartdots.py. Controls are listed in the status area above the dot box; also run with the -h option to see all other command line options.
Someone brought to my attention that Python Chess was using up a lot of CPU cycles, even when (actually, especially when) it was just sitting waiting for user input. I checked on my system and found it was using 30-40% of the CPU time…way too much for such a simple game!
Some googling revealed that I was using pygame’s event loop in a reckless manner. I was using pygame.event.get() to get user mouse clicks or other input — evidently, this will return a list of events that have queued up since the last time the get() function was called (not sure if there is a limit to the queue size….) Since the call to get() is basically a while(1) loop, it was being called just as fast as it could, which is pretty unecessary for a game like chess. Actually, probably for any game this is overkill. Luckily, there are a few easy solutions.
The option which I used for Python Chess is to use pygame.event.wait() rather than pygame.event.get(). The wait() function will cause the program to go into an idle state (ie. not hogging the processor) until it gets a single event. This is much more suited for the game of chess, where the user needs time to ponder their move, anyway. Replacing my event.get() calls to event.wait() makes the CPU usage of Python Chess go down to 0-1% on my system, with occassional spikes during the AI’s turn of up to 10% (these are just rough numbers seen via Windows Task Manager). Quite an improvement!
Another option for limiting CPU usage is to create a pygame.time.Clock() object, then calling the object’s tick(fps) function every time through your while(1) loop. This will limit the loop’s execution to fps (frames per second) times per second, allowing it to go idle in between loop processing. (Note that on slower systems, the desired fps may not be achievable, so it will still use up a healthy chunk of processing time if the value is too high.) Using time.Clock seems to be more suited to “action” style games, where there is screen movement going on every time through the loop, for instance.
No big changes in this version of Python Chess, just a few compatibility fixes. I decided version 0.5 was ready to be released to the world, so I posted it on the Pygame website: http://www.pygame.org/project/1099/?release_id=1960. And, at least one person actually downloaded and played! He pointed out some issues/fixes with running on Linux (I had just been developing with Windows).
So here’s the really, truly, cross-platform, Python Chess: PythonChess_v0.6.zip
I’ve added in some better AI functionality. In addition to version 0.4’s random AI, which just picks any random legal move, there is now a defense AI and an offense AI.
Defense AI’s GetMove() Priority
- Only make a “protected move” — don’t move a piece to a space where the enemy can capture it on the next turn.
- Put enemy king in check if possible.
- If a piece is in danger of being captured by the enemy next turn, move it to a safe place. There is a priority mechanism here — the AI will move its queen to safety before moving a pawn to safety, for instance.
- Pick a protected move that captures another piece. The same priority mechanism applies — capture the queen or other high-value piece before capturing a pawn.
- If no moves from prior steps, pick any remaining protected move.
- If no protected moves, revert to random AI and pick any legal move.
The offense AI is the same as the defense AI, except steps 3 and 4 are reversed. In other words, it will ignore its own pieces’ safety if there is an opportunity to harm the enemy. Maybe it should be called “berserk” mode. 🙂
In expanding the ChessAI module, I took the opportunity to play around with inheritance in Python. The ChessAI_defense class extends the ChessAI_random class, while the ChessAI_offense class extends ChessAI_defense. They each have a GetMove() functions, so I don’t have to worry which class my AI object is from when I need to get a move from it in the main game loop.
I also created a sideline program called PythonChessAIStats.py. Since I can pit one AI vs. a different AI, I thought it would be cool to see how well they do versus each other. This could be used in the future to rank new AI approaches. The AIs all rely on random numbers to some extent, so in order to make the tests repeatable, the script reads in numbers from a file and uses them to seed Python’s random number generator. (I used RANDOM.ORG to generate 100 random seeds.) Sometimes the AI vs. AI won’t end (they just end up moving the same pieces back and forth), so I end the game after 200 turns and call it a tie if no one is in checkmate yet.
Here’s a summary of some trials I did (X = no trial):
|Win – Loss – Tie||AI Opponent (black)|
|AI Player (white)||Random AI||Defense AI||Offense AI|
|Random AI||X||0 – 78 – 22||0 – 81 – 19|
|Defense AI||X||32 – 44 – 24||21 – 47 – 32|
|Offense AI||X||32 – 44 – 24||21 – 47 – 32|
The new AIs are clearly better than just randomly moving about, so that’s good. Interesting that numbers for Defense vs. Defense are the same as for Offense vs. Defense….
This version of Python Chess also has better command line parsing. I found out about Python’s OptionParser class and used it. Both PythonChessMain.py and PythonChessAIStats.py can be run from the command line with a number of options — the -h option should display all of the other available options and usage.
I tried to make a standalone windows executable using py2exe (there are a couple of scripts in the .zip file that try to do that), which is supposed to wrap up the code along with all of the required libraries and the python interpreter into an executable, so it can be run on a Windows machine without the user having to have everything pre-installed. Would be useful for producing a game for the masses, but it wouldn’t work for me. The script runs and builds an executable, but it crashes when I try to run it. I’ll have to investigate further.