Monday, August 17, 2009

Splitting a directory based on first character in filename

This is a bash small script I whipped up:

#!/bin/bash

limit=26;

function do_move
{
if [ "x$selected" != "x" ]; then
if [ "$first" == "$last" ]; then
dir="$first"
else
dir="$first-$last"
fi
mkdir "$dir";
for j in $selected; do
j2=`echo "$j" | tr "[:upper:]" "[:lower:]"`
for k in $j* $j2*; do
if [ "$k" != "$dir" -a -f "$k" ]; then
mv "$k" "$dir/"
fi
done
done
fi
}

initials=`for i in *; do echo "$i" | tr "[:lower:]" "[:upper:]" | sed "s/^\(.\).*/\1/"; done | sort | uniq`;

cur=0;

first="";

selected="";

for i in $initials; do
if [ "$first" == "" ]; then
first=$i;
fi
selected="$selected $i"
numfiles=`ls|grep -i "^$i\+"|wc -l`;
(( cur = cur + $numfiles ));
last=$i
if [ $cur -ge $limit ]; then
do_move
cur=0;
first="";
selected="";
fi
done

do_move


It splits a directory into smaller directories based on the first character in the filename. An example:

$ for i in `seq 1 1000`; do touch `pwgen -n -c 10 1`; done
$ dirsplit
$ ls
A B-C D-E F-G H-I J-K L-M N-O P Q-S T U V-W X-Y Z


As you see, it splits into ranges when it can fit few enough files into one directory.
$limit as defined at the top of the script controls the number of files that should be exceeded for the script to deem it necessary to create another directory; that is, $limit is not a hard limit, there is no hard limit, and there can't be, unless you start splitting on more than the first character. In any case, adjust as necessary.
The algorithm currently used is greedy and likely quite non-optimal, but it works for me.

The script was made to be able to split directories with many files on the CF card of my NES PowerPak (which is a fabuluous creation, but that's another story).

Friday, August 14, 2009

Nightfall and selecting multiplayer levels over network

I just finished implementing support for calculating SHA1 sums of a level for Nightfall. Also, it can look up a level by SHA1 sum.
When a client joins a server, it is sent the SHA1 sum of the level the server loaded, and it can then find the appropriate level to load.
You may ask why I didn't just send the file name of the level. The reason is that if these two levels (which have the same file name) do not fully match, the client will likely go out of sync with the server sooner or later. There's no other way of detecting it.

This may need a short explanation of the networking protocol in Nightfall:
Every single small movement of a unit is not sent over the network. Instead, the overall commands the unit gets and the paths that are calculated for it are sent, and every client then calculates what small per-frame moves are necessary to achieve this goal.
Also, the map and other properties of the level are not sent over the network. In the future this may change, so that you can join a game which you do not have the level of, and your client would automatically download the level.

The main reason things are done like this is to reduce network traffic as much as possible, and thus Nightfall uses only a few kB/s of network traffic per client.

If a client and a server gets out of sync, currently the game just quits. In the future, the server could perhaps save the game as it has it, and then send the save to the clients for them to load it.
However, if the reason the game got out of sync was that the levels differ, this will obviously not solve the problem, and it may even trigger an infinite loop of re-syncs. Failing in this way is very user-unfriendly.
So that is why I implemented a way of making sure that the clients load the same level as the server did.

More PHP oddities

Apparently, true prints as 1, while false prints as the empty string.

Also, empty() considers the string and the number 0 to be empty.

Five lectures on the acoustics of the piano

Pretty interesting stuff:

http://www.speech.kth.se/music/5_lectures/contents.html