Text
Using jQuery with JSONP, a brief example
Since I couldn't find a jquery/jsonp example that I liked, here is my stab at it.
JSONP is a way of getting around cross-domain restrictions browsers levy on javascript HTTP calls. The protocol makes the server return a JS code fragement of a function that it then executes. Sneaking, right?
Imagine a server with this bit of perl CGI code:
#!/usr/local/bin/perl -- use strict; use CGI; sub main { my $q = CGI->new; my $callback = $q->param('callback') || 'foo'; my $data = q[{id:0, name:'jjohn'}]; my $ret = qq[$callback ($data);]; print $q->header("-content-type" => "text/javascript"), $ret; } main();
When run, the output looks like this:
Content-Type: text/javascript; charset=ISO-8859-1 foo ({id:0, name:'jjohn'});
This script is called "ws.pl". It is smart enough to call use a different function name if one is requested by the client.
On the client/JS side, you can use this data this way:
<html> <head> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script> <script type="text/javascript"> $(document).ready(function () { $.getJSON('http://games.taskboy.com/ws.pl?callback=?', function (d) { alert("got: " + d.name); }); }); </script> </head> </html>
Here, an alert box will appear. Notice that the callback function producing the alert has received the decoded JSON object and is able to use it directly. No need to worry about injecting SCRIPT tags, etc.
jQuery takes the huge pain of javascript programming out of javascript programming.
0 notes
Text
Shorty turns 40
Well, it had to happen. I reached my fortieth birthday this week.
Given that my son is a year and change old, I don't have as much time for blogging as I once did.
They say parenthood changes your perspective on many things. Despite my best efforts, I am afraid that has happened to me too.
I launched two web services this year in the social media space: Nestor and Linksnest. While neither is wildly popular, these tools helped me learn the latest web design principals and tools.
Looking back at the start of the year, I see that I began with a stealth project for a friend that involved collaborative filtering and ajax. Need to get back to that at some point.
I sponsored a few kickstarter projects and contributed a prize to the Interactive Fiction Competition. I guess if I can't do stuff myself, I can at least help those who are.
I got a mole on my face removed, but am experiencing some blow-back. Stupid organic body.
I worked for three different companies this year, one of which I worked for twice. We got our house painted and traded in our cars for new ones. I tasted 30 year old Laphroaig. Materially, it was a better year than many others had and I know how lucky I am.
We lost a great old lady cat, Miss Lulu, this year. She will be missed.
I guess I'm not as reflective as I have been in the past. Not enough time.
0 notes
Text
Modeling RPG-style combat
Following on to yesterday's post about economics, I'd like to present this brief note about how to model role-playing game (RPG) style combat easily with math.
Anyone familiar with Dungeons and Dragons will recall the very tortured set of combat tables broken out by class and level. A simpler method for resolving combat is found in Freeciv.
Assuming a two party combat, you create two attributes for each member: an attack stat and a defense stat. In this system, the bigger the number, the better.
To determine the chance of an attacker hitting the defender, use the following formula:
P(hit) = Attacker->att/(Attacker->att + Defender->def)
This is to say, the chance of an attacker hitting a defender is the proportion of the attacker's 'attack' stat over the sum of the attacker's attack stat plus the defender's defense stat.
If the attack succeeds, some amount of damage is subtracted from the defender's health stat.
I like this formula because it is simple and scales well to opponents of wildly differing strengths.
Attack and defense stats must be greater than 0.
Hope this helps.
0 notes
Text
Modeling supply and demand in computer games
update: Removed magic scalar from supply and demand calculations.
This is more of a note to be expanded on later. However, I believe some will find it useful in this raw form.
Many interesting games, like M.U.L.E. and Trade Wars, involve the mechanic of supply and demand. To model this in code, where you calculate demand and supply to arrive at a price, can be done through the following forumlae.
Calculate demand: This is usually a simple count of your potential consumers. Perhaps the number of players will equal demand. This depends on the context of your game. This number will be referred to as 'd'.
Calculate supply: This is a simple count of the number of widgets available that are to be sold. Again, this is contextual. This will be called 's'.
Calculate relative demand: You will need to pick an arbitrary equilibrium number that presents the perfect balance between supply and demand. This number is going to be arbitrary, so let's use 100.
Relative Demand = (d / (100 + d))
Calculate relative supply: This looks a lot like demand:
Relative Supply = (s / 100 + s))
Calculate price: You will pick a price that represents the price at equilibrium. Here, that is 100 currency units. Your number will be different:
Price = 100 * (Relative Demand / Relative Supply)
You must code around the case where supply is 0. The price becomes not-a-number.
The result of these maths is that when demand is high and supply is low, the price increases over some baseline. When the supply is higher than demand, the price lowers. That's mostly how life works.
These formulae will give you a very linear relationship between supply and demand. You may need to tweak this for your game. Inverse logs might be more realistic or even exponential functions. This is most certainly an exercise for the reader.
Please note that this supply and demand model is far from perfect. Most economists will tell you that the variable to look at is price, which then controls supply and demand (this might be a "freshwater" bias). However, most games will want to compute a price rather than predict supply and demand.
0 notes
Text
Simple maze generation in Perl
Sometimes, you just want to generate mazes. The code presented here is a depth-first search.
A couple of notes about the code below. It works with vanilla, 2D grids, but these can be of varying sizes. The maze data is stored is a simple 2D array. Each room is presented as a hash. The algorithm needs to track which rooms have been visited (more on this later). Any room may also not have a south (bottom) wall or an east (right) wall.
The following code initializes the maze structure:
sub init_maze { my ($max_row, $max_col) = @_; my $maze = []; for (my $r=0; $r < $max_row; $r++) { for (my $c=0; $c < $max_col; $c++) { push @{$maze->[$r]}, {'visited' => 0, 'bottom' => 1, 'right' => 1, }; } } return $maze; }
No surprises there.
The real work happens in the next bit of code, make_maze(). Given a starting point specified by a row and column coordinate in the 2D maze array, it marks the room as "visited". It then looks for neighboring rooms that have not yet been visited. It randomly selects one of these and knocks down the wall between them. It then recursively calls make_maze() with the new room.
Get it? No? That's OK. Recursive code takes a long time to trust.
sub make_maze { my ($maze, $row, $col) = @_; $maze->[$row]->[$col]->{'visited'} = 1; while (my $unvisited = get_unvisited($maze, $row, $col)) { last unless @$unvisited; # Randomly select a neighbor my $choice = $unvisited->[ rand(@$unvisited) ]; # Knock down the wall between them remove_wall($maze, [$row, $col], $choice); # move to this new cell make_maze($maze, $choice->[0], $choice->[1]); } }
There are two details worth investigating. The first is how unvisited neighbors are selected. The second is how to figure out which wall to remove. Here's the unvisited neighbor code:
sub get_unvisited { my ($maze, $row, $col) = @_; my @found; # look for neighbors in cardinal directions; # be mindful of maze bounderies if ($row == 0) { push @found, [$row + 1, $col] unless $maze->[$row + 1]->[$col]{'visited'}; } elsif ($row == @$maze - 1) { push @found, [$row - 1, $col] unless $maze->[$row - 1]->[$col]->{'visited'}; } else { if ($row + 1 < @$maze) { push @found, [$row + 1, $col] unless $maze->[$row + 1]->[$col]->{'visited'}; } push @found, [$row - 1, $col] unless $maze->[$row - 1]->[$col]->{'visited'}; } if ($col == 0) { push @found, [$row, $col + 1] unless $maze->[$row]->[$col + 1]->{'visited'}; } elsif ($col == (@{$maze->[0]} - 1)) { push @found, [$row, $col - 1] unless $maze->[$row]->[$col - 1]->{'visited'}; } else { if ($col + 1 < @{$maze->[0]}) { push @found, [$row, $col + 1] unless $maze->[$row]->[$col + 1]->{'visited'}; } push @found, [$row, $col - 1] unless $maze->[$row]->[$col - 1]->{'visited'}; } return \@found; }
This code is pretty straight-forward. It observes the boundary rooms and makes sure that these rooms have not yet been visited. There are clever ways of reducing this code, but this is easier to understand, I think.
Removing the right wall is simply a matter of looking at two rooms and figuring out if the rooms are joined horizontally or vertically. It is then pretty easy to remove the right or bottom of wall of the correct room.
sub remove_wall { my ($maze, $r1, $r2) = @_; my $selected; if ( $r1->[0] == $r2->[0] ) { # Rows are equal, must be East/West neighbors $selected = ($r1->[1] < $r2->[1]) ? $r1 : $r2; $maze->[ $selected->[0] ]->[ $selected->[1] ]->{'right'} = 0; } elsif ( $r1->[1] == $r2->[1] ) { # Columns are the same, must be North/South neighbors $selected = ($r1->[0] < $r2->[0]) ? $r1 : $r2; $maze->[ $selected->[0] ]->[ $selected->[1] ]->{'bottom'} = 0; } else { die("ERROR: bad neighbors ($r1->[0], $r1->[1]) and ($r2->[0], $r2->[1])\n"); } return; }
It would be useful to print out a bird's eye view of the maze and that's done (admittedly awkwardly) in the following routine.
sub print_maze { my ($maze) = @_; my $screen = []; my $screen_row = 0; for (my $r=0; $r < @$maze; $r++) { if ($r == 0) { # Top border push @{$screen->[$r]}, '+'; for (@{$maze->[0]}) { push @{$screen->[$r]}, '--', '+'; } } for (my $c=0; $c < @{$maze->[0]}; $c++) { my @middle; if ($c == 0) { push @middle, "|"; } push @middle, " "; # room center if ($maze->[$r]->[$c]->{'right'}) { push @middle, "|"; } else { push @middle, " "; } push @{$screen->[$screen_row + 1]}, @middle; my @bottom; if ($c == 0) { push @bottom, "+"; } if ($maze->[$r]->[$c]->{'bottom'}) { push @bottom, "--"; } else { push @bottom, " "; } push @bottom, "+"; push @{$screen->[$screen_row + 2]}, @bottom; } $screen_row += 2; } for (my $r=0; $r < @$screen; $r++) { for (my $c=0; $c < @{$screen->[0]}; $c++) { print $screen->[$r]->[$c]; } print "\n"; } print "\n"; }
Finally, here's the main line:
my $dimension = $ARGV[0] || 5; my $maze = init_maze($dimension, $dimension); my ($startRow, $startCol) = (int(rand($dimension)), int(rand($dimension))); make_maze($maze, $startRow, $startCol, undef); print_maze($maze);
Hope this helps get you started in the wonderful world of maze building!
0 notes
Text
Linksnest: storing social media links for you
Linksnest is a new web tool I have developed to help you collect and manage URLs mentioned by you or your friends on social media sites, like Twitter or LinkedIn.
You can even see your latest collected links as an RSS feed.
It's free to use and I think pretty easy to understand. Please give it a try and tell me what you think.
0 notes
Text
MySQL: Creating BIGINT unique random IDs
The inspiration for this post comes from the mysql forum.
The problem is common enough: you want your SQL tables to have unique, secure primary keys. You also don't want to kill your database performance by using string UUIDs as keys. You also don't want serialized auto numbering for keys that may present security risks later on.
One very solid solution to this problem is an IDs table. That is, create a table that holds the list of all important IDs to be used by all other tables in your schema. Such a table might look like this:
create table ids ( id bigint unsigned not null primary key, taken tinyint default 0 );
Any time we run out of IDs, we can generate new ones with this stored procedure:
CREATE PROCEDURE create_ids (create_id_num INT) BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE '42000' BEGIN END; SET @i = 1; loop1: LOOP INSERT INTO ids (id) VALUES (FLOOR(1 + RAND() * POW(2,63))); SET @i = @i + 1; IF @i > create_id_num THEN LEAVE loop1; END IF; END LOOP loop1; END;
Now when one of your tables needs a new id, simply grab a random open one from the IDs table:
SELECT id FROM ids WHERE taken = 0 LIMIT 100; UPDATE ids SET taken=1 WHERE id=? AND taken=0;
In scripting language land, grab a set of ids and attempt to make the UPDATE with one of them. If the update fails, then some other process grabbed that ID. That's no biggie, just try another.
What if we run out of IDs? Create a trigger that ensures that there are more IDs created when the last one is taken:
CREATE TRIGGER make_additional_ids AFTER UPDATE ON ids FOR EACH ROW BEGIN IF (SELECT COUNT(*) FROM ids WHERE taken = 0) = 0 THEN CALL create_ids(1000); END IF; END;
While there are some race conditions I've glossed over, this is a pretty good framework for having solid numeric IDs, which most databases want to fast comparisons.
0 notes
Text
Simple HTTP handling with urllib2
I am continuing to poke around python this week looking the "standard way" to make HTTP requests and process HTTP responses. In Perl, there is the very complete LWP set of modules. In Java, there is the Apache HTTP client library. Python has several modules with related functionality, including httplib, urllib and urllib2 (which will change with Python 3.0).
What I need to do is create HTTP headers in the request and later read headers from the response. Oh, and I will want to look at the response's body too.
The code below makes an HTTP to a small PHP script I have on this server (use it as needed). It sets a header, which is echoed by the PHP script. Later all the headers from the response are also echoed.
One of the things I like about urllib2 is the Request class. In the things I want to do (web services, OAuth), it is very helpful to build the request and be able to inspect it apart from the mechanism that performs the HTTP fetch.
import urllib2 import pprint import sys url = "http://taskboy.com/blog/h.php" R = urllib2.Request(url) R.add_header("User-agent", "jjohn/1.0") try : f = urllib2.urlopen(R) headers = f.info().headers c = f.read() # content except : e = sys.exc_info()[0] print "Oops: ", e exit; print "Content: " + c + "\n" print "HEADERS: "; for k in headers : print "'" + k + "'"
0 notes
Text
Accessing Twitter's API through Python
If I understand the Internet correctly, the new hottness for doing oauth stuff in Python is this oauth2 library. This twitter doc is a little out of date, so here's my version of it.
import oauth2 as oauth from pprint import pprint import json def req (url=None, http_method="GET", post_body='', http_headers='', key='', secret='' ) : con = oauth.Consumer(key='ConsumerKey' secret='ConsumerSecret') token = oauth.Token(key=key, secret=secret) client = oauth.Client(con, token) res, content = client.request( url, method=http_method, body=post_body, headers=http_headers, ) return content c = req(url="http://api.twitter.com/1/statuses/home_timeline.json", key='AccessToken', secret='TokenSecret') if len(c) > 0 : pprint(json.loads(c))
You get the consumer key and secret by registering an app on Dev.twitter.com. The Access Token and Token Secret come either from dev.twitter.com (for debugging only) or through the oauth login process by which a request token is exchanged for the access token and token secret.
Note that client.request() performs HMAC_SHA1() signing of the paramters. It looks like if you pass in post_body, the parameters will be parsed and signed too. Still, it is weird that this method doesn't simply take a dictionary object for parameters.
The real difference is how the library is included. Confusingly, the script in the examples directory of this library does not correctly include itself (the project was forked and not all the cruft was cleaned up :-)
0 notes
Text
Beta testers wanted for social media analytics project
Over the past several months, I have been slowly pulling together a project that I hope others will find useful. It is called Nestor.
Nestor is an analytics tool that provides trending information for followers and retweets on Twitter.
The service is free and not spammy. Since authentication is done through Twitter, I don't even collect email addresses.
What I need are users on the system who are not afraid to give me feedback.
I am looking to improve the following areas:
User experience
Quality of data
Improving data visualizations
Security
Protecting user privacy
If this is something you are interested in, please send mail to [email protected] for a free registration code to get on to the system. Or message me on twitter: @taskboy3000
UPDATE: Registration codes are boring! Just login, accept the terms of service and pass the captcha test. Easy, right?
I want to be clear: this is not an open source project. It could become that at some point, but it is not one now.
UPDATE: Nestor is closed for a while. Thanks for your interest.
0 notes
Text
RSS feed has broken links
I just discovered that the taskboy RSS feed has some broken links. I fix that as soon as I can.
update: Looks like that's fixed now.
0 notes
Text
A simple image viewer with pygame
The following python code demonstrates the power of pygame to make even non-game tasks easy.
This command line program is to be invoked with a filename of a graphic file (any supported by pygame including jpg, png, gif, etc). To exit, press escape. To reduce the image by half, press the minus key. To increase the image by x2, press the equals/plus button.
Sure, the scaling is lossy, but this is a quick and dirty program.
Enjoy!
import os import sys import pygame def reset_screen(res) : screen = pygame.display.set_mode(res) pygame.display.set_caption("pyview") return screen imgfile = "" if len(sys.argv) < 2 : print "USAGE: %s [FILENAME]" % sys.argv[0] sys.exit() imgfile = sys.argv[1] if not os.path.exists(imgfile): print "USAGE: %s [FILENAME]" % sys.argv[0] sys.exit() S = pygame.image.load(imgfile) screen = reset_screen((S.get_width(), S.get_height())) S = S.convert() # Need screen init while True : for e in pygame.event.get() : if e.type == pygame.QUIT : sys.exit() if e.type == pygame.KEYDOWN : if e.key == pygame.K_ESCAPE : sys.exit() if e.key == pygame.K_MINUS : S = pygame.transform.scale(S, (S.get_width()/2, S.get_height()/2)) screen = reset_screen((S.get_width(), S.get_height())) if e.key == pygame.K_EQUALS : S = pygame.transform.scale(S, (S.get_width()*2, S.get_height()*2)) screen = reset_screen((S.get_width(), S.get_height())) screen.blit(S, (0,0)) pygame.display.flip() pygame.time.delay(25)
0 notes
Text
Not quite dead yet
This extended hiatus brought to by my son, Angus.
I hope to return with new technical articles in the coming months. But for know, I commend you to my archives.
0 notes
Text
Collaborative Filtering in PHP
I recently completed a POC project for a friend that called for a recommendation engine. That is, a mechanism was needed for the application to suggest domain-specific macguffins that similar users are interested in that might also interest the current user. The technique I used is called collaborative filtering. You can read more about this technique and others designed to extract wisdom from crowds in O'Reilly's excellent Programming Collective Intelligence and Ron Zacharski's work in progress called A Programmer's Guide to Data Mining.
Collaborative filtering essentially boils down to two steps:
Finding credible critics
Finding the difference in the set of the things liked by the user and the crediable critics
Credible critics are a cute way of labeling that set of users whose tastes are (perhaps) similar to the target user. There are many ways to determine this set, but the method I chose is processor friendly and easy to code.
First, you need a user-specified metric. This could be an actually rating (1-5) of your domain-specific widgets or simple "like" buttons. Whatever the shape of the rating, it must be represented a number.
After a while, your users will have generated a lot of rating data about your widgets. If you take two domain-specific widgets and plot their ratings on a cartesian plane, you'll begin to see patterns. Users who rate these items "closely" to the target user will make excellent credible critics (in theory). The game then is how to determine "close."
The most brain-dead simple method for the calculating distance of two cartesian points is called the Manhattan method. Very briefly, the formula to calculate this is to add the absolute value of the difference of each dimensions and divide these by the number of dimensions. In this case, dimensions are widgets that both users have rated. Let's look at some code.
function manhattan_distance($rating1=array(), $rating2=array()) { $MAX_DISTANCE = PHP_INT_MAX; $distance = $total = 0; foreach ($array_keys($rating1) as $widget_id) { if (array_key_exists($widget_id, $rating2) { $distance += abs($rating1[$widget_id] - $rating2[$widget_id]); $total += 1; } } if ($total > 0) { return $distance / $total; } return $MAX_DISTANCE; }
This function expects parameters to be associative arrays mapping widget IDs to ratings. Here's an example of calling this function:
$user1 = array("widget-1" => 3, "widget-2" => 1, "widget-3" => 5, ); $user2 = array("widget-2" => 5, "widget-3" => 1,); printf("Distance of user1 from user2: %0.2f\n", manhattan_distance($user1,$user2));
You will need to compare every user to each other, which is an algorithm with a runtime of O(n**2). This is not good for large user populations, so some future optimization will be needed. However, that optimization is not covered here.
To make a recommendation, we need to find a few critics with similar tastes. This is to say, we need to find users with the smallest distance from the target user.
function computeNearestNeighbor ($user_id, $user_list=array(), $count=1) { $your_ratings = get_ratings($user_id); $distances = array(); foreach ($user_list as $uid) { $distances[] = array($uid, manhattan_distance($your_ratings, get_ratings($uid)) ); } usort($distances, 'compare_recs'); $tmp = array(); while ($count > 0) { $i = array_shift($distances); $tmp[] = $i[0]; $count -= 1; } return $tmp; } function compare_recs ($a, $b) { if ($a[1] == $b[1]) { return 0; } return ($a[1] < $b[1]) ? -1 : 1; } function get_ratings ($user_id) { // Assume $data is held in a data store $tmp = array(); foreach ($data as $row) { $tmp[$row["wiget_id"]] = $row["cnt"]; } return $tmp; }
Finding the set of closests neighbors really is a fairly mechanical process of calculating the distances of all users to the target one, sorting that set by distance and using some number of the first several users in the list.
That completes the first part of the recommendation process: get the list of critics. Now we need to see what widgets those critics use that our target user is not. With the infrasture we've built, it is straight-forward to get the set of widgets not currently used by the target user.
function recommend_widgets ($user_id, $user_list=array()) { $recs = array(); $seen = array(); $your_ratings = get_ratings($user_id); $nearest_users = computeNearestNeighbor($user_id, $user_list, 3); foreach ($nearest_users as $uid) { $neighbor_ratings = get_invites($uid); foreach ($neighbor_ratings as $widget_id => $rating) { if ($seen[$widget_id]) { continue; } if (!array_key_exists($widget_id, $your_ratings)) { $recs[] = array($widget_id, $rating); $seen[$widget_id] = True; } } } usort($recs, "compare_ratings"); return $recs; } function compare_ratings ($a, $b) { if ($a[1] == $b[1]) { return 0; } return ($b[1] < $a[1]) ? -1 : 1; }
Notice that the sort routine here orders the records by rating is descending order. This means that recommendations with high ratings will be listed first. You should consider whether this is the behavior you want your recommendation engine to have.
Collaborative filter is simply algorithm that invites all kinds of tweaking. There are many better ways to find critics or even to calculate "distance." You might want to add other weighting mechanisms to the recommendations of specific items. The key is to be able to articulate exactly how you want your recommendations to work.
0 notes
Text
Taskboy by the numbers
I decided to do some year end number crunching on this web site's usage numbers. These are, of course, generated from the servers access log. I present them here as a benchmark for the future.
Monthly Averages for 2010 Gigabytes served 2.9 GB Visitors 20,834 Pages served 53,423
In late November, I put an end to remote linking to images hosted here. It was a small drain on resources, but an annoying one.
Frankly, a good bit of the traffic listed above can be attributed to google, yahoo, bing and their moral equivalents spidering this site.
While I'm generally proud of my blog output this year, I still haven't found an audience for this content. Or I haven't found content for this audience. :-/
In any case, this was the year that was.
0 notes
Text
Using python to update PuTTY sessions
Putty has been the gold standard for free Windows SSH utilities for almost a decade now. As you might expect from a win32 application, it keeps its stored sessions (the connection meta-data used to connect to various hosts) in the Windows Registry:
HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\Sessions
You may find it desirable to create or update a stored session. While you can use many tools to do this, Python offers a convenient way to do this using the _winreg module. The following is an example of how to iterate through the list of existing Putty sessions.
import _winreg subkey = 'Software\SimonTatham\PuTTY\Sessions' H = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, subkey) info = _winreg.QueryInfoKey(H); if info[0] < 1 : print "No saved sessions\n"; exit; print "Found the following saved sessions:" i=0; trg = None; while i < info[0] : s = _winreg.EnumKey(H, i) k2 = subkey + '\\' + s H2 = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, k2, 0, _winreg.KEY_ALL_ACCESS) host = _winreg.QueryValueEx(H2, "HostName") print "\t%s - %s" % (s, host[0]) _winreg.CloseKey(H2) i += 1;
The Windows Registry has five "databases" that you can connect to:
HKEY_CLASSES_ROOT
HKEY_CURRENT_USER
HKEY_LOCAL_MACHINE
HKEY_USERS
HKEY_CURRENT_CONFIG
In this case, the app data is stored in HKEY_CURRENT_USER, so that's the base to connect to. Then the subkey can be appended to get to the right place in the hierarchy for the Putty sessions.
Of course, sometimes there are no sessions, so that case must be handled.
The Registry is also a little weird in that what it calls "keys" actually contain many subkeys. Each subkey of "Sessions" will be a saved session. Here's what my registry looks like:
The subkeys are enumerated in the info hash in order. So, it is a simple matter to walk through the this list, get the meta-data for that key and extract useful values from it. Each saved session, for example, as a string "value" called "HostName" that contains the address used to connect to the host. There are many, many values for each saved session, but you'll have to consult the putty source code to figure out what each does.
To change a "value", you need to open the key that owns it. Then invoke the right SetValue() method on it. See the _winreg docs for more. In this case, to change the address of a saved session, to code might look like this:
newIP = "1.2.3.4" _winreg.SetValueEx(KeyToSession, "HostName", None, _winreg.REG_SZ, newip) _winreg.CloseKey(KeyToSession)
This code assumes that KeyToSession has already been opened.
0 notes
Text
39: Time to party (before it's too late)
As is my tradition, I make a little blog post to note the milestone of my birthday. Today makes my 39th year on this planet. This year, the deaths in the family were few. Indeed, the arrival of my son, Angus, means that my duty to genetics and family is pretty much over. I guess I can kickback and coast through the rest of life.
Perhaps not quite yet.
My employer, ATG, was acquired this year by Oracle. This means that I will begin the new year as a member of a 100,000 person company. That's sure different.
I am slowly working on a new interactive fiction called "Escape from the House of Dragons." I have the plot roughly outlined, but given my very limited free time, I don't expect a beta version until Q2 of 2011.
Finally, I'm getting inspired to resurrect my old web game, State Secrets. I think I finally understand how to make it fun to play. Unfortunately, I need to scrap the code I've written and start again. That will be will the fourth rewrite of the game.
So, I suppose I'll see you next year at this time? Great.
0 notes