Browsing articles tagged with " code"

WebDAV revisited

In my previous endeavour with WebDAV, I ended up using a complicated lighttpd/apache proxy system to get the permissions right. This did work well enough, but it had a few limitations. In involved modifying the registry on the client systems and it required a new lighty instance running for each share with different permissions to the last (which wasn’t terribly efficient). Additionally, lighty’s mod_webdav lacked support for a few vital operations which made working with the shares a bit difficult, not to mention there were some MIME-type conflicts between lighty/apache/the webDAV client that made working with certain types of files near impossible.

I have since revised some of the methods I’ve used to implement the WebDAV server. For a start, I managed to resolve the XP SP2/Vista/7 basic vs DIGEST authentication method hacks to not need registry editing – it turns out, by default, if the share is SSL encrypted, basic authentication should be fine. I also decided on a new approach. Using Apache, PHP5, suPHP and eZ Component’s WebDAV module, I was able to implement a sturdier WebDAV solutions that still solved the problems.

First things first, Apache, PHP5, mod_php, mod_suphp all needed to be installed and configured. Once that was done, ez Component’s WebDAV module needed to be installed – on Fedora, this is php-ezc-Webdav in the Yum repositories. Then I needed to make a couple of scripts to handle authentication and module loading. These are modified right from the ezc-Webdav documentation. These scripts need to go in Apache’s DocumentRoot.

autoload.php

<?php
require_once "/usr/share/pear/ezc/Base/base.php";

/**
 * Autoload ezc classes
 *
 * @param string $className
 */
function __autoload( $className )
{
    ezcBase::autoload( $className );
}
?>

You may need to alter the path to base.php above.

authenticator.php

<?php
class ldapAuthenticator extends ezcWebdavDigestAuthenticatorBase
                   implements ezcWebdavAuthorizer
{

    public function authenticateAnonymous( ezcWebdavAnonymousAuth $data )
    {
        return false;
    }

    public function authenticateBasic( ezcWebdavBasicAuth $data )
    {
        $username = $data->username;
        $password = $data->password;

	if(strstr($username, '\\')){
		$username = strstr($username, '\\');
		$username = preg_replace("^.", '', $username);
	}

	$ldap = ldap_connect("ldap.company.tld", 389);
	$bind = @ldap_bind($ldap);
	$search = ldap_search($ldap, "dc=company,dc=tld", "(&(objectClass=shadowAccount)(uid=".$username."))", array("dn"));
	$data = ldap_get_entries($ldap, $search);
	$dn = $data[0]['dn'];

	if(!$dn){ return false; }

	$bind = @ldap_bind($ldap, $dn, $password);
	$search = ldap_search($ldap, "dc=company,dc=tld", "(&(objectClass=shadowAccount)(uid=".$username."))", array("dn"));
	$data = ldap_get_entries($ldap, $search);

	if(!$bind){ return false; }

	return true;
    }

    public function authenticateDigest( ezcWebdavDigestAuth $data )
    {
        $username = $data->username;

	error_log("User attempted unsupported DIGEST authentication: ".$username);

	return false;
   } 

    public function authorize( $user, $path, $access = ezcWebdavAuthorizer::ACCESS_READ )
    {
        return true;
    }
}
?>

You will need to modify the authenticateBasic function. The one above is to authenticate with an LDAP backend.

sharename.php

<?php
require_once 'autoload.php';
require_once 'authenticator.php';

$server = ezcWebdavServer::getInstance();

$server->options->realm = "CCPG Solutions Ltd WebDAV";

$server->auth = new ldapAuthenticator();

$backend = new ezcWebdavFileBackend(
   '/path/to/share'
);

$server->handle( $backend );
?>

In this one, you will need to modify the share path (in $backend) and the realm.

Then you need to change the owning uid/gid (chown user:group sharename.php) of the sharename.php file to the user/group you want to use for read/write permissions of the files. You can make as many sharename.php files for each share (or combination of share/permissions) you need.

With all that done, you should be able to go ahead and map the share using the URL https://dav.company.tld/sharename.php

The eZ Component’s WebDAV module seems pretty in-depth – the above is really just a very basic usage of it’s capabilities, but it does work pretty well. The only other thing you may have to do, depending how pedantic your Windows boxes are going to be, is disable the DIGEST authentication headers altogether. To do this, edit /usr/share/pear/ezc/Webdav/servers.php (or wherever your Webdav/server.php is located) and comment the following lines in the createUnauthenticatedResponse function:

$wwwAuthHeader['digest'] = 'Digest realm="' .$this->options->realm . '"'
 . ', nonce="' . $this->getNonce() . '"'
 . ', algorithm="MD5"';

Save the file and exit. That’s all it takes. With all that done, your users should be able to map the share in Windows, authenticate via LDAP and read/write files with the permissions of the PHP script owner.

uop2cal: Subscribe to your @PortsmouthUni timetable as an iCal feed (UPyoursUPlink?)

Anyone who goes to the University of Portsmouth will be familiar with UPlink, the University’s portal system, and with the timetable it contains. Most people (especially anyone under what used to be ECE) will be familiar with the frustration of lectures changing times/dates/moving room/no longer existing despite being marked as “confirmed”, and the univeristy-standard oh-so-helpful response of “You should be checking it everyday”, along with the other dozen-or-so university sites we’re supposed to be checking every day. (Can you notice my frustration yet? I was originally planning to call this UPyoursUPlink but have since decided on the less-offensive but less-descriptive uop2cal)

This script is a pretty simple one, it uses cURL to screen-scrape the portal and retrieve your timetable, an obnoxious set of regexes (<3) to parse the data into an array, and then the iCalcreator class to generate an ICS file of your timetable, complete with the details (name, lecturer, room, confirmed/unconfirmed (not that I can find a difference, mind), group, etc).

Update: Previously, I was supplying a hosted copy for anyone to use, but I have been advised by the University that you willingly giving me your username and password somehow contravenes the Computer Misuse Act 1990. Whether or not this is the case is irrelevant, as they have asked I stopped doing this. In any case, the source code is still available below (unless they later tell me that’s a problem somehow, too), so you can run your own copy.

Update update: Apparently the University still weren’t too happy with the provisions I had made the last time they contacted me, and have once again requested I remove this. As such, this source is no longer publicly available. Queries about why can be directed towards the University itself, queries about the source are welcome to me.

irc4sms.pl: I’m in your IRC, SMSing your SMSes

This script is a proof-of-concept that I wanted to try more than anything else. Lightly modelled on bitlbee, it pretends to be an IRC server, and accepts/sends SMSes via a gateway (or a gateway app on your Android phone). It’s very much not a finished product and is known to have memory leaks. It also doesn’t clean up after it’s threads.

This script requires threads, threads::shared and IO::Socket.

Config-wise, it’s fairly straightforward – it’s all done in lines 6-21 of the script source. Set $IRCport and $IRCbind to the IP and TCP port you want the server to listen on. Set $IRCpassword to be the password that the IRC client must submit to connect.

The only part that gets tricky is interacting with the gateway. $SMScommand should be set to a script to pass the SMS off to the gateway for delivery. You can use the variables %TO% and %TEXT% in the script path as parameters – these will be replaced with the recipient and the text of the message respectively before the script is called. This could be a PHP script (“/usr/bin/php send-sms.php %TO% ‘%TEXT%’”), a URL using curl or wget (“/usr/bin/wget http://localhost/cgi-bin/sms.cgi?to=%TO%&text=%TEXT%”), an email address using sendmail or similar (“echo %TEXT% | /usr/sbin/sendmail %TO%@sms.gateway.com”) or anything else you might want to use.

Likewise, receiving SMSes can be a bit tricky. $SMSreceiveKey needs to be set to a key that will be used to ensure that the SMSes are received from the real gateway. SMSes are accepted from the gateway by connecting to the IRC server socket and sending nothing more than:

DELIVER sms-receive-key 1234567890 :SMS text goes here.\r\n

The connection does not have be registered as an IRC client or authenticated to the server. It’s important to note that the IP and port you have set earlier in the configuration must be accessible by whatever is delivering the SMSes as well, or it will not be able to connect. In a lot of cases, SMS gateway services or gateway apps running on your phone will only support email forwarding or HTTP delivery of incoming texts. In either of these cases, I suggest creating a script to deliver the SMS for you. In the former case, a Perl script that gets called by a pipe from the mail server which connects to the server’s socket and deliver’s the SMS would be pretty straightforward. Likewise, a PHP script on a webserver could be configured to accept an SMS by GET/POST parameters and connect to the socket to deliver it.

Use-wise, it’s fairly straightforward. You can type ‘help’ in #sms after connecting for a short summary of this information. When you receive an SMS, the phone number it is received from will join #sms and address the text message to your attention:

* 1234567890 has joined #sms.
<1234567890> your-nickname: Hello there.

You can reply in kind:

<your-nickname> 1234567890: Hiya.

You can also send a text message to a number not presently in the channel the same way – just address the message at the number and SMSbot will ensure it’s delivered for you.

You can also message people by opening a private chat with them – either use ‘/query 123456790′ or double-click their name on the userlist of most IRC clients to open a private chat. Send your message, and replies will automatically be sent back to you in private message. To switch back to receiving their messages in #sms, just message them from the channel again and their replies will go there instead.

Finally, you can use the channel’s userlist as a sort of contact list by adding and removing users from it. When you receive an SMS from someone not already in the channel, they will join. You can also add them to the channel without sending them a message:

<your-nickname> add 1234567890
* 1234567890 has joined #sms.

Likewise, you can remove someone from the channel whether they joined because you added them or because they sent you a message:

<your-nickname> remove 1234567890
* 1234567890 has left #sms.

If you remove someone from the channel, they will join again the next time they send you a message.


Script Source

#!/usr/bin/perl
use IO::Socket;
use threads;
use threads::shared;

##### Config #####

# IRCd settings. At least change the password.
my $IRCport = "21337";
my $IRCbind = "127.0.0.1";
my $IRCpassword = "irc4sms";

# Shell command to send SMSes via gateway.
# Consider using wget or sendmail!
# Variables! %TO% and %TEXT%
my $SMScommand = "/usr/bin/wget http://localhost/cgi-bin/send-sms.cgi?to=%TO%&text=%TEXT%";

# Key to receive inbound SMSes. Change this.
my $SMSreceiveKey = "1rc45m5!";

##### End Config ######

$version = "0.2";
$crlf = "\r\n";

my @msgQueue;
share(@msgQueue);
my %joined;
share(%joined);

$SIG{CHILD} = 'IGNORE';
$SIG{PIPE} = 'IGNORE';

my $listen_socket = IO::Socket::INET->new(
	LocalPort => $IRCport,
	LocalAddr => $IRCbind,
	Listen => 10,
	Proto => 'tcp',
	Reuse => 1
);
$listen_socket->autoflush(1);
die "Can't bind local socket: $!\n" unless $listen_socket;

while(my $connection = $listen_socket->accept){
	my $child = threads->create("read_data", $connection);
}

sub deliver_data {
	my($socket, $nickname) = @_;
	while(1){
		foreach my $message(@msgQueue) {
				my ($sender, $text) = $message =~ /^(.+?)\:(.+)$/;
				if($sender && $text){
						if($joined{$sender} < 1){
								$joined{$sender} = 1;
								print $socket ':'.$sender.' JOIN #sms'.$crlf;
						}
						if($joined{$sender} == 1){ print $socket ':'.$sender.' PRIVMSG #sms :'.$nickname.': '.$text.$crlf; }
						if($joined{$sender} > 1){ print $socket ':'.$sender.' PRIVMSG '.$nickname.' :'.$text.$crlf; }
				}
		}
		@msgQueue = ();
	}
}

sub read_data {
	my($socket) = @_;
	my $registered = 0;

	print $socket ':irc4sms.local NOTICE AUTH :irc4sms ready. Login or deliver an SMS.'.$crlf;
	while(){
		$data = $_;
		print $data;
		$data =~ s/[\r\n]//ig;

		if($data =~ /^PING (.+)$/){
			print $socket ':irc4sms.local PONG irc4sms.local :'.$1.$crlf;
		}

		if($registered < 3){
			if($data =~ /^DELIVER (.+?) (.+?) \:(.+)$/ && $1 eq $SMSreceiveKey){
				$sender = $2;
				$text = $3;
				$sender =~ s/^\+//;
				$msgQueue[++$#msgQueue] = $sender.":".$text;
				print $socket 'MESSAGE OK ('.$sender.'): '.$text.' - Queued for delivery.'.$crlf;
			}elsif($data =~ /^USER (.+?) .+ (?:\:|)(.+)$/){
				$username = $1;
				$realname = $3;
				$registered++;
			}elsif($data =~ /^NICK (.+)$/){
				$nickname = $1;
				print $socket ':irc4sms.local 464 '.$nickname.' :P assword required.'.$crlf;
				$registered++;
			}elsif($data =~ /^PASS (.+)$/){
				if($1 eq $IRCpassword){
					$registered++;
				}else{
					print $socket ':irc4sms.local 464 '.$nickname.' :Invalid password.'.$crlf;
				}
			}

			if($registered == 3){
				print $socket ':irc4sms.local 001 '.$nickname.' :Welcome'.$crlf;
				print $socket ':irc4sms.local 002 '.$nickname.' :Host irc4sms.local is running irc4sms '.$version.$crlf;
				print $socket ':irc4sms.local 003 '.$nickname.' :irc4sms (http://code.cmantito.com/IrcSms)'.$crlf;
				print $socket ':irc4sms.local 004 '.$nickname.' irc4sms.local abiswRo ntC'.$crlf;
				print $socket ':irc4sms.local 005 '.$nickname.' PREFIX=(ohv)@%+ CHANTYPES=# CHANMODES=,,,ntC NICKLEN=25 CHANNELLEN=25 NETWORK=irc4sms SAFELIST CASEMAPPING=rfc1459 MAXTARGETS=1 WATCH=128 :are supported by this server'.$crlf;
				print $socket ':irc4sms.local 375 '.$nickname.' :- irc4sms.local MOTD:'.$crlf;
				print $socket ':irc4sms.local 372 '.$nickname.' :- Welcome to irc4sms '.$version.$crlf;
				print $socket ':irc4sms.local 376 '.$nickname.' :End of MOTD'.$crlf;
				print $socket ':'.$nickname.'!'.$username.'@irc4sms.local MODE cm :+sR'.$crlf;
				print $socket ':'.$nickname.'!'.$username.'@irc4sms.local JOIN :#sms'.$crlf;
				print $socket ':irc4sms.local MODE #sms +Ct'.$crlf;
				print $socket ':irc4sms.local 353 '.$nickname.' = #sms :@'.$nickname.' @SMSbot'.$crlf;
				print $socket ':irc4sms.local 366 '.$nickname.' #sms :End of /NAMES list'.$crlf;
				print $socket ':irc4sms.local 332 '.$nickname.' #sms :irc4sms '.$version.$crlf;
				print $socket ':irc4sms.local 333 '.$nickname.' #sms SMSbot!SMSbot@irc4sms.local '.(time - 60).$crlf;
				$joined{$nickname} = 1;
				$joined{'SMSbot'} = 1;
				my $delivery = threads->create("deliver_data", $socket, $nickname);
			}
		}else{
			if($data =~ /^MODE/){
				print $socket ':irc4sms.local 324 '.$nickname.' #sms +Ct'.$crlf;
			}elsif($data =~ /^WHO/){
				print $socket ':irc4sms.local 352 '.$nickname.' #sms '.$username.' irc4sms.local irc4sms.local '.$nickname.' H@ :0 '.$realname.$crlf;
				print $socket ':irc4sms.local 352 '.$nickname.' #sms irc4sms irc4sms.local irc4sms.local SMSbot H@ :0 irc4sms '.$version.$crlf;
			}elsif($data =~ /^PRIVMSG \#sms \:(.+?)(?:\:|\,) (.+)$/){
				if($joined{$1} > 0){
					$joined{$1} = 1;
				}
				$to = $1;
				$text = $2;
				$cmd = $SMScommand;
				$cmd =~ s/\%TO\%/$to/g;
				$cmd =~ s/\%TEXT\%/$text/g;
				system($cmd);
			}elsif($data =~ /^PRIVMSG ([a-zA-Z0-9\+]+?) \:(.+?)$/){
				if($joined{$1} > 0){
					$joined{$1} = 2;
				}
				$to = $1;
				$text = $2;
				$cmd = $SMScommand;
				$cmd =~ s/\%TO\%/$to/g;
				$cmd =~ s/\%TEXT\%/$text/g;
				system($cmd);
			}elsif($data =~ /^PRIVMSG \#sms :( .+?)$/){
				$command = $1;
				if($command =~ /^add (.+)/ && !$joined{$1} ){
					$joined{$1} = 1;
					print $socket ':'.$1.' JOIN #sms'.$crlf;
				}elsif($command =~ /^remove (.+?)$/ && ($joined{$1})){
					$joined{$1} = '';
					print $socket ':'.$1.' QUIT :Leaving...'.$crlf;
				}elsif($command =~ /^help/){
					print $socket ':SMSbot PRIVMSG #sms :Commands: add 442392123456, remove 442392123456'.$crlf;
					print $socket ':SMSbot PRIVMSG #sms :If you /msg a contact, responses will automatically come as a /msg'.$crlf;
					print $socket ':SMSbot PRIVMSG #sms :Otherwise, address the contact in this channel, and their replies will come here'.$crlf;
				}else{
					print $socket ':SMSbot PRIVMSG #sms :I don\'t recognise that as a valid command. Sorry. Try "help".'.$crlf;
				}
			}
		}
	}
}

WebDAV is a … pain

[Titled edited ;P]

As a concept – WebDAV is great. File shares over HTTP – no fussy protocols to deal with, security and encryption built right in.

Problems. You want to use Apache as a WebDAV server with mod_dav. Theroetically, this is fine. In my circumstances, I wanted to use it for a few specific people to reach their /home/username folders, so I thought using Apache I could just export those directories as DAV shares, using LDAP authentication. I hit several problems with this.

First, permissions. Apache obviously runs as ‘nobody’ or ‘apache’ or ‘httpd’ depending on your system. This user doesn’t have permission to access your home directory, much less, write to it. And you don’t REALLY want your front-facing web server having that permission. I solved this by installing mod_proxy and mod_proxy_http, then running a tiny little lighttpd instance as the user(s) who wanted access to their home directories, with it’s own mod_webdav inserted. I had Apache pass connections to the webdav-server.domain/username vhost/folder alias through the proxy to the lighttpd server for that user, which was exporting that user’s home directory as a single share. I also used lighttpd’s own mod_auth to do the LDAP authentication for each user instead of Apache, because I didn’t want to chance someone getting around the front-facing proxy and connecting directly to one of the DAV servers, obviously. It is worth noting that you will want to set the document-root to either /home or /home/username for the lighty instance, as COPY and MOVE will not work if you don’t. Since authentication is required for the site anyway, it’s not a huge concern to set it to the user’s home directory. This setup seems to work pretty well.

Which brings us to the second problem. Windows XP SP2, Windows Vista and Windows 7 refuse to authenticate to a WebDAV server using BASIC as it’s authentication method (instead of DIGEST). If I were using password files, this would be no problem to change to DIGEST authentication using lighty’s mod_auth, but because I’m using LDAP as a backend, it’s not feasible. You can’t use DIGEST with LDAP because, with DIGEST, the password is never actually sent – just a hash to be compared, but the comparison hash will simply never match what’s in the LDAP database, so there’s no way to do the auth. Instead, the solution seems to be dealing with Windows. After lots and lots of Googling, I found a few registry hacks:

HKLM\SYSTEM\CurrentControlSet\services\WebClient\Parameters\UseBasicAuth
HKLM\SYSTEM\CurrentControlSet\services\WebClient\Parameters\BasicAuthLevel

If you set UseBasicAuth (a DWORD value) to 0×00000001 and BasicAuthLevel (also a DWORD value) to 0×00000002, you will be able to authenticate to the DAV server with BASIC authentication. The first parameter enabled BASIC authentication altogether. The latter has three possible values: 0, which disabled BASIC authentication, 1, which enabled it for SSL-secured sites and 2, the value I’m using above, to enable it for all sites. When you’re done, restart the webclient service. (In Vista/7, you can only do this from services.msc or by using an ELEVATED privileges command prompt – not a normal one.)

Now, I wanted to use this with Active Directory to set a user’s N-drive to their home directory. I know it would be easier to use Samba, but if a user is behind a vaguely-filtered internet connection, say Comcast, Virgin Media or Verizon Fios, you can’t actually hit a Samba share over the internet. And VPNs can be even more of a … pain than WebDAV. In theory, you can address a WebDAV share with a UNC path and it works (\\webdav-server.fqdn\sharename). It doesn’t, however, seem to work when you set that UNC path to a drive letter in someone’s AD account. Which sucks.

So I thought, ooh – I know! Map it with a logon script! DERP. Logon scripts have to run from a Samba share. Which we can’t get to. Hence using WebDAV. The solution, if I say so myself, is pretty creative, if not a little over-the-top. I created a folder, say, C:\logon. In there, there is ‘wget.exe’, a Windows binary-only version of the ‘wget’ tool for Linux, which is a simple command-line based HTTP/FTP downloader. Then there’s a file called loader. bat. This file fetches a logon.bat from a web server, and runs it. Then, in their Active Directory profile, I set their logon script to \\localhost\c$\logon\loader.bat – causing them, each time they logon, to download the latest logon.bat and run it. In logon.bat, I use wget again to download a little .exe file written in AutoIT that checks if the registry has already been updated to connect to WebDAV shares using BASIC authentication as above, and if not, requests administrative privileges to update the registry and then restart the webclient service for you. After all this happens, it finally uses ‘net use’ to map the WebDAV shares (the lighty one from earlier as well as an additional SharePoint share) to their relevant drive letters. Scripts below.

The wget.exe I’m using comes from: http://pages.interlog.com/~tcharron/wgetwin.html

loader.bat

@echo off
cd C:\logon
del c:\logon\logon.bat
C:\logon\wget http://some-http-server.fqdn/logon/logon.bat
C:\logon\logon.bat

logon.bat

@echo off
cd C:\logon
del C:\logon\dav-setup.exe
C:\logon\wget http://some-http-server.fqdn/logon/dav-setup.exe
C:\logon\dav-setup.exe

net use N: "http://apache-proxy-to-lighty.fqdn/sharename" /user:username /persistent:yes /savecred
net use T: "http://sharepoint.fqdn/sharename" /user:domain\username /persistent:yes /savecred

dav-setup.au3

#RequireAdmin

$useBasicAuth = RegRead("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\WebClient\Parameters", "UseBasicAuth")
$basicAuthLevel = RegRead("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\WebClient\Parameters", "BasicAuthLevel")

$restartService = 0

if $useBasicAuth <> 1 Then
    RegWrite("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\WebClient\Parameters", "UseBasicAuth", "REG_DWORD", "0x00000001")
    $restartService = 1
EndIf

if $basicAuthLevel <> 2 Then
    RegWrite("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\WebClient\Parameters", "BasicAuthLevel", "REG_DWORD", "0x00000002")
    $restartService = 1
EndIf

if $restartService = 1 Then
    RunWait(@WindowsDir & "\System32\net stop webclient")
    RunWait(@WindowsDir & "\System32\net start webclient")
EndIf

checkdupes.pl

Script Information

This script checks files specified on the command line for duplicates based on checksums. Version 0.3a will also highlight files in different directories with the same name. Version 0.2a introduced the capability of recursion using the -a flag. See documentation.

Documentation

Usage: ./checkdupes.pl
At least two or more files must be specified. Any folders
specified will be skipped. Wildcards will work, for example:
     ./checkdupes.pl *
will check all files in the current folder. Likewise:
     ./checkdupes.pl folder/* folder-2/*
will check all files in both those folders. It will not
recurse on it's own.

Alternatively (as of 0.2a), you can pass the -a flag:
      ./checkdupes.pl -a
This will check all files, recursively, starting from the
current working directory.

As of version 0.3a, it will also find files with duplicate
names that reside in different directories. No flag is
necessary to enable this functionality.

Script Source

#!/usr/bin/perl

if($ARGV[0] =~ /\-a/i && !$ARGV[1]){
    system("find --version &>/dev/null");
    if($? != 0){
        print STDERR "FATAL: find not in \$PATH (".$ENV{'PATH'}.") - aborting.\n";
        exit(2);
    }

    $flist = `find ./`;
    @ARGV = split(/\n/, $flist);
}

if(@ARGV[0] =~ /\-(?:h|v|\?)/i || !$ARGV[0] || !$ARGV[1] || ($ARGV[0] =~ /\-a/i && $ARGV[1])){
    print STDERR "\nUsage: ".$0." \n";
    print STDERR "\tAt least two or more files must be specified. Any folders\n";
    print STDERR "\tspecified will be skipped. Wildcards will work, for example:\n";
    print STDERR "\t\t".$0." *\n";
    print STDERR "\twill check all files in the current folder. Likewise:\n";
    print STDERR "\t\t".$0." folder/* folder-2/*\n";
    print STDERR "\twill check all files in both those folders. It will not\n";
    print STDERR "\trecurse on it's own.\n\n";
    print STDERR "\tAlternatively (as of 0.2a), you can pass the -a flag:\n";
    print STDERR "\t\t$0 -a\n";
    print STDERR "\tThis will check all files, recursively, starting from the\n";
    print STDERR "\tcurrent working directory.\n\n";
    print STDERR "\tAs of version 0.3a, it will also find files with duplicate\n";
    print STDERR "\tnames that reside in different directories. No flag is\n";
    print STDERR "\tnecessary to enable this functionality.\n\n";
    print STDERR $0." 0.3a by cmantito \n\n";
    exit(1);
}

system("md5sum --version &>/dev/null");
if($? != 0){
    print STDERR "FATAL: md5sum not in \$PATH (".$ENV{'PATH'}.") - aborting.\n";
    exit(2);
}

$progress = 0;
while($file = shift(@ARGV)){
    print "Processing ".$progress."/".++$#ARGV."...\n";
    if(-e $file){
        if( !-d $file){
            $escfile = $file;
            $escfile =~ s/(.)/\\$1/ig;
            $sum = `md5sum $escfile`;
            $sum =~ s/^(.+?)\s+.+$/\1/ig;
            chomp($sum);
            $sums{$sum}++;
            push(@$sum, $file);

            $filename = $file;
            $filename =~ s/(.+)\/(.+)$/\2/;
            if(!$filenames{$filename}){
                $filenames{$filename} = $file;
            }else{
                $filenames{$filename} .= "¬".$file;
            }
            $filenamecount{$filename}++;
        }
    }else{
        print STDERR "FATAL: Couldn't open file: ".$file." - aborting.\n";
        exit(1);
    }
    $progress++;
}

print "\n--- DUPLICATE FILES ---\n";
foreach $sum(keys %sums){
    if($sums{$sum} > 1){
        print $sum.": ";
        foreach $file(@$sum){
            print $file." ";
        }
        print "\n";
    }
}
print "\n--- DUPLICATE NAMES ---\n";
foreach $filename(keys %filenamecount){
    if($filenamecount{$filename} > 1){
        print $filename.": ";
        $filelist = $filenames{$filename};
        $filelist =~ s/¬/ /ig;
        print $filelist."\n";
    }
}

print "\n";
exit(0);

Intro To Computing CW2: Text modification

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DELIMITER        "."
#define NEWLINE_DELIM    ".\n"

#define STATE_COMMAND     1
#define STATE_INPUT       2
#define STATE_EXIT        3

#define BUFFER_SIZE     1024
#define CMD_DELIMITER   ";"

#define CMD_PRINT       'p'
#define CMD_QUIT        'q'
#define CMD_APPENDS     'a'
#define CMD_REPLACE     'r'
#define CMD_DELETE      'd'
#define CMD_INSERT      'i'

int main (void) {
	// Setup whatever we need.
	char* buffer = NULL;
	int buffer_size = 0;
	int buffer_length = 0;
	int chars_read = 0;
	int appState = STATE_COMMAND;
	int lineNumber = 0;
	int char_pos = 0;

	char valid_cmds[BUFFER_SIZE + 1] = {'\0'};
	char* all_cmds = NULL;
	char* cmd_line = NULL;

	int location = 0;
	int locationB = 0;
	char commandLetter;
	char notModifier;
	char rangeModifier;
	char* string = "";

	int skipPrint = 0;

	// We're going to loop through here until we're all done. (ie, STATE_EXIT)
	do {
		// Read input into the buffer.
		chars_read = getline(&buffer, &buffer_size, stdin);

		// Make sure it exists.
		if(chars_read < 1){
			break;
		}

		// Delimiters make the state increase.
		if(strcmp(buffer, DELIMITER) == 0 || strcmp(buffer, NEWLINE_DELIM) == 0){
			appState = appState + 1;
			continue;
		}

		// Command state.
		if(appState == STATE_COMMAND){

			// Get some commands from the buffer.
			// Go through each character of the command, and see if it matches a
			// real command, if it does, put it in the valid commands buffer.
			buffer_length = strlen(buffer);
			for(char_pos = 0; char_pos < buffer_length; char_pos++){
					if(islower(buffer[char_pos])){
						if(buffer[char_pos] == CMD_PRINT ||
							buffer[char_pos] == CMD_QUIT ||
							buffer[char_pos] == CMD_APPENDS ||
							buffer[char_pos] == CMD_REPLACE ||
							buffer[char_pos] == CMD_DELETE ||
							buffer[char_pos] == CMD_INSERT ){
								strcat(valid_cmds, buffer);
								strcat(valid_cmds, CMD_DELIMITER);
								break;
						}

					appState  = STATE_EXIT;
					break;
				}
			}
		}

		// Text input state.
		if(appState == STATE_INPUT){
			skipPrint = 0; // This must must MUST get set to 0 on each loop, or everything will break.
			lineNumber = lineNumber + 1; // Increment the line number each time we go through.
			all_cmds = strdup(valid_cmds); // And now all_cmds is the same as valid_cmds.

			// Get a command from the commands buffer.
			for(cmd_line = strtok(all_cmds, CMD_DELIMITER); cmd_line != NULL; cmd_line = strtok(NULL, CMD_DELIMITER)){

				// Match this one first, because it's the most specific - put the various bits of the command
				// into the various variables. Also, make sure the range modifier is one of the valid ones,
				// and that the not modifier is what we expect.
				if(sscanf(cmd_line, "%d%c%d%c%c%s", &location, &rangeModifier, &locationB, &notModifier, &commandLetter, string) == 6
					&& (rangeModifier == ',' || rangeModifier == '~')
					&& notModifier == '!'){

					// Now check the command issued. Appends? Yeah, check the range --
					// For comma, make sure the line number isn't in the range specified. (Remember the '!'.)
					// For tilde, check that A) the line number isn't the one specified, B) the line number minux
					// the starting point divded by the second number has a remainder, C) the line number is less than
					// the starting point. If A and B or A and C are true, we'll match.
					if(commandLetter == CMD_APPENDS && (
						(rangeModifier == ',' && (location > lineNumber || lineNumber > locationB))
						|| (rangeModifier == '~' && location != lineNumber && ((lineNumber - location) % locationB > 0  || lineNumber < location))
					)){
						// Append, stick the string to the end of the existing buffer and attach a newline.
						// Append will use this same method for the rest off the programme.
						strcat(buffer, string);
						strcat(buffer, "\n");
					}

					// Same method for matching replace.
					if(commandLetter == CMD_REPLACE && (
						(rangeModifier == ',' && (location > lineNumber || lineNumber > locationB))
						|| (rangeModifier == '~' && location != lineNumber && ((lineNumber - location) % locationB > 0  || lineNumber < location))
					)){
						// Just replace the buffer with the new string and add a newline to the end.
						// Replacement will be done the same way as this for the rest of the app.
						strcpy(buffer, string);
						strcat(buffer, "\n");
					}

					// And insert.
					if(commandLetter == CMD_INSERT && (
						(rangeModifier == ',' && (location > lineNumber || lineNumber > locationB))
						|| (rangeModifier == '~' && location != lineNumber && ((lineNumber - location) % locationB > 0  || lineNumber < location))
					)){
						// Add a newline to the end of the new string, stick the buffer on after that, and then
						// move it all in to the buffer. We'll reuse this method for insert throughout the app.
						strcat(string, "\n");
						strcat(string, buffer);
						strcpy(buffer, string);
					}

				// We're moving down in order of most specific matching regime to least specific. So this one's next.
				}else if(sscanf(cmd_line, "%d%c%d%c%c", &location, &rangeModifier, &locationB, &notModifier, &commandLetter) == 5
					&& (rangeModifier == ',' || rangeModifier == '~')
					&& notModifier == '!'){

					// We're using the same matching system as before to match the ranges for print, remembering that
					// there's still an !.
					if(commandLetter == CMD_PRINT && (
						(rangeModifier == ',' && (location > lineNumber || lineNumber > locationB))
						|| (rangeModifier == '~' && location != lineNumber && ((lineNumber - location) % locationB > 0  || lineNumber < location))
					)){
						printf("%s", buffer);
					}

					// And delete.
					if(commandLetter == CMD_DELETE && (
						(rangeModifier == ',' && (location > lineNumber || lineNumber > locationB))
						|| (rangeModifier == '~' && location != lineNumber && ((lineNumber - location) % locationB > 0  || lineNumber < location))
					)){
						skipPrint = 1;
					}

				// This is the next one in order, this time no !
				}else if(sscanf(cmd_line, "%d%c%d%c%s", &location, &rangeModifier, &locationB, &commandLetter, string) == 5
					&& (rangeModifier == ',' || rangeModifier == '~')){   

					// For appends, we're checking if the range is a comma or a tilde. If it's comma, is the line number
					// in between the addresses specified? If it's a tilde, check A) is line number equal to the location
					// specified, B) does the line number minus the location, divided by the second number,
					// have NO remainder? and C) is the line number greater than the location specified?
					// If A is true, or B and C are true, we match.
					if(commandLetter == CMD_APPENDS && (
						(rangeModifier == ',' && location <= lineNumber && lineNumber <= locationB)
						|| (rangeModifier == '~' && (location == lineNumber || (((lineNumber - location) % locationB) == 0 && lineNumber > location)))
					)){
						strcat(buffer, string);
						strcat(buffer, "\n");
					}

					if(commandLetter == CMD_REPLACE && (
						(rangeModifier == ',' && location == lineNumber)
						|| (rangeModifier == '~' && (location == lineNumber || (((lineNumber - location) % locationB) == 0 && lineNumber > location)))
					)){
						strcpy(buffer, string);
						strcat(buffer, "\n");
					}

					// Using this to for the ranged replace for a comma-range only, once we've replaced the first line,
					// We make sure that none of the lines after it print until we're out of the range.
					// Ref: http://cnfolio.com/IntroComputingTutorial05#example05
					if(commandLetter == CMD_REPLACE && (
						(rangeModifier == ',' && lineNumber > location && lineNumber <= locationB)
					)){
						skipPrint = 1;
					}

					if(commandLetter == CMD_INSERT && (
						(rangeModifier == ',' && location <= lineNumber && lineNumber <= locationB)
						|| (rangeModifier == '~' && (location == lineNumber || (((lineNumber - location) % locationB) == 0 && lineNumber > location)))
					)){
						strcat(string, "\n");
						strcat(string, buffer);
						strcpy(buffer, string);
					}

				// And then this one goes next.
				}else if(sscanf(cmd_line, "%d%c%d%c", &location, &rangeModifier, &locationB, &commandLetter) == 4
					&& (rangeModifier == ',' || rangeModifier == '~')){

					// Same method as before for dealing with the different ranges.
					if(commandLetter == CMD_PRINT && (
						(rangeModifier == ',' && location <= lineNumber && lineNumber <= locationB)
						|| (rangeModifier == '~' && (location == lineNumber || (((lineNumber - location) % locationB) == 0 && lineNumber > location)))
					)){
						printf("%s", buffer);
					}

					if(commandLetter == CMD_DELETE && (
						(rangeModifier == ',' && location <= lineNumber && lineNumber <= locationB)
						|| (rangeModifier == '~' && (location == lineNumber || (((lineNumber - location) % locationB) == 0 && lineNumber > location)))
					)){
						skipPrint = 1;
					}

				// This one's next but it doesn't have a range, just the not modifier.
				}else if(sscanf(cmd_line, "%d%c%c%s", &location, &notModifier, &commandLetter, string) == 4
					&& notModifier == '!'){

					// So basically, because of the !, we only make sure that the line number DOESN'T match.
					// We don't have to use the range detection here.
					if(commandLetter == CMD_APPENDS && location != lineNumber){
						strcat(buffer, string);
						strcat(buffer, "\n");
					}

					// And here.
					if(commandLetter == CMD_REPLACE && location != lineNumber){
						strcpy(buffer, string);
						strcat(buffer, "\n");
					}

					// And here.
					if(commandLetter == CMD_INSERT && location == lineNumber){
						strcat(string, "\n");
						strcat(string, buffer);
						strcpy(buffer, string);
					}

				// We do have a not modifier here, so we need to not match everything.
				}else if(sscanf(cmd_line, "%d%c%c", &location, &notModifier, &commandLetter) == 3
					&& notModifier == '!'){

					// Same here.
					if(commandLetter == CMD_PRINT && location != lineNumber){
						printf("%s", buffer);
					}

					// And here.
					if(commandLetter == CMD_DELETE && location != lineNumber){
						skipPrint = 1;
					}

				// This time we don't have the not modifier.
				}else if(sscanf(cmd_line, "%d%c%s", &location, &commandLetter, string) == 3){

					// So we check that the line numbers DO match. Again, no worry about ranges.
					if(commandLetter == CMD_APPENDS && location == lineNumber){
						strcat(buffer, string);
						strcat(buffer, "\n");
					}

					// Same here.
					if(commandLetter == CMD_REPLACE && location == lineNumber){
						strcpy(buffer, string);
						strcat(buffer, "\n");
					}

					// And here.
					if(commandLetter == CMD_INSERT && location == lineNumber){
						strcat(string, "\n");
						strcat(string, buffer);
						strcpy(buffer, string);
					}

				// We don't really have to make sure location > 0, but it's better to
				// validate as much of the input as you can, to weed out falsely-matching
				// input.
				}else if(sscanf(cmd_line, "%d%c", &location, &commandLetter) == 2
					&& location > 0){

					// Again, it's a simple case of does the line number match?
					if(commandLetter == CMD_PRINT && location == lineNumber){
						printf("%s", buffer);
					}

					// Here too.
					if(commandLetter == CMD_QUIT && location == lineNumber){
						appState = STATE_EXIT;
					}

					// This one as well.
					if(commandLetter == CMD_DELETE && location == lineNumber){
						skipPrint = 1;
					}

				// We have a not modifier here.
				}else if(sscanf(cmd_line, "%c%c", &notModifier, &commandLetter) == 2
					&& notModifier == '!'){

					// But no line numbers, so it matches every line.
					if(commandLetter == CMD_PRINT){
						// well, if we DON'T (from the !) print every line an additional time,
						// then it just does what it normally does so this doesn't
						// actually need to do anything?
					}

					/// Same here.
					if(commandLetter == CMD_DELETE){
						// likewise, if we don't delete every line, then it acts
						// normally and again, this doesn't do anything.
						// I don't actually understand the point in !p and !d.
					}

				// We're back to a simple command that effects every line, but not ! modifier.
				}else if(sscanf(cmd_line, "%c%s", &commandLetter, string) == 2){

					// So we don't need to worry about line matching.
					if(commandLetter == CMD_APPENDS){
						strcat(buffer, string);
						strcat(buffer, "\n");
					}

					// Or here.
					if(commandLetter == CMD_REPLACE){
						strcpy(buffer, string);
						strcat(buffer, "\n");
					}

					// Or here.
					if(commandLetter == CMD_INSERT){
						strcat(string, "\n");
						strcat(string, buffer);
						strcpy(buffer, string);
					}   

				// Very simple one letter commands.
				}else if(sscanf(cmd_line, "%c", &commandLetter) == 1){

					// Which match every line.
					if(commandLetter == CMD_PRINT){
						printf("%s", buffer);
					}

					// And here.
					if(commandLetter == CMD_QUIT){
						appState = STATE_EXIT;
					}

					// Same here.
					if(commandLetter == CMD_DELETE){
						skipPrint = 1;
					}

				}

				// If nothing's told it not to print since the beginning of the loop,
				// print the buffer. By this point, the buffer may have been modified
				// by a replace, append, or insert. Doesn't matter, print it anyway as
				// long as nothing set skipPrint to 1.
				if(skipPrint == 0){
					printf("%s", buffer);
				}
			}

		}
	} while(appState != STATE_EXIT);

	return 0;
}

Digital Systems CW2: Stopwatch

C – micro

#include <reg66x.h>

/////////////////////////
//// PIN DEFINITIONS ////
/////////////////////////

// Port 0 - in - in from CPLD
#define inDigits P0

// Port 1 - all out - out to CPLD
sbit outClock = P1^0;
sbit outReset = P1^1;

// Port 2 - all out - out to display
#define outDisplay P2

// Port 3 - in/out - buttons & speaker & CPLD control
sbit btnStartStop = P3^3;  // in
sbit outAcknowledge = P3^7;// out

/////////////////////////////
//// DECLARE SUBROUTINES ////
/////////////////////////////

void setupTimer();
void setupInputs();
void runningLoop();
void timerCallback();
void digitsCallback();
void controlCallback();
void resetCPLD();

///////////////////////////
//// DECLARE VARIABLES ////
///////////////////////////

int modeState = 0;
unsigned char hunths, tenths, seconds, tenSecs;

////////////////////////
//// PROGRAMME CODE ////
////////////////////////

void main() {
    setupTimer();
    setupInputs();
    resetCPLD();
   runningLoop();
}

void runningLoop() {
    while(1){
        outDisplay = tenSecs + 64;
        outDisplay = seconds + 128;
        outDisplay = tenths + 192;
        outDisplay = hunths + 0;
    }
}

void resetCPLD(){
    outReset = 1;
    outReset = 0;

    return;
}

void setupTimer() {
    TMOD = 0x01;        // M0 = 1 (Timer mode 1 - 16 bit mode)
    TL0 = 0xFF;         // 400Hz = 2304 delay count, 65535-2304 = 63231
    TH0 = 0xF6;         // TH0 = 0xF6 :: TL0(0xFF) = 0xF6FF = 63231
    ET0 = 1;                // T0 Interrupt enabled.
    EA = 1;             // Interrupts enabled.
    TR0 = 1;                // Begin timer.

    P2 = 0x00000000;

    return;
}

void setupInputs() {
    EA = 1;             // Interrupts enabled.
    IT0 = 1;                // Set on falling edge.
    IT1 = 1;                // Set on falling edge.
    EX0 = 1;            // Enable external interrupt 0.
    EX1 = 1;                // Enable external interrupt 1.

    modeState = 0;
    outAcknowledge = 0;

    return;
}

//////// INTERRUPT CALLBACKS ////////

void timerCallback() interrupt 1 using 2 {
   TR0 = 0;
    TL0 = 0xFF;         // 400Hz = 2304 delay count, 65535-2304 = 63231
    TH0 = 0xF6;         // TH0 = 0xF6 :: TL0(0xFF) = 0xF6FF = 63231
    TF0 = 0;
    TR0 = 1;                // Begin timer.
    outClock =~ outClock;   // Invert the clock output pin.
}

void digitsCallback() interrupt 0 {
    if(modeState == 0){
        seconds = inDigits & 0x0f;
    }else if(modeState == 1){
        tenSecs = inDigits & 0x0f;
    }else if(modeState == 2){
        hunths = inDigits & 0x0f;
    }else if(modeState == 3){
        tenths = inDigits & 0x0f;
    }

    if(modeState < 3){
        modeState++;
    }else{
        modeState = 0;
    }

    outAcknowledge = 1;
    outAcknowledge = 0;
}

void controlCallback() interrupt 2 {
    TR0 =~ TR0;
}

VHDL – CPLD

----------------------------------------------------------------------------------
-- Company:
-- Engineer:
--
-- Create Date:    12:25:53 03/18/2009
-- Design Name:
-- Module Name:    clock - Behavioral
-- Project Name:
-- Target Devices:
-- Tool versions:
-- Description:
--
-- Dependencies:
--
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

---- Uncomment the following library declaration if instantiating
---- any Xilinx primitives in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity clock is
    Port ( clockSource : in STD_LOGIC;  -- clock pulse from micro
           reset : in  STD_LOGIC;        -- reset pulse from micro
           microAck : in STD_LOGIC;      -- acknowledgement from micro
              digit : out  STD_LOGIC_VECTOR (3 downto 0);
                                                      -- numbers to micro
              cpldRts : out STD_LOGIC);     -- ready to send to micro (ACTIVE LOW)
end clock;

architecture Behavioral of clock is

signal hunthsCount : std_logic_vector(3 downto 0);
signal tenthsCount : std_logic_vector(3 downto 0);
signal secondCount : std_logic_vector(3 downto 0);
signal tenSecCount : std_logic_vector(3 downto 0);
signal controlFlag : std_logic_vector(1 downto 0);

begin                                                         

process(reset, clockSource)
begin
if reset = '1' then
    hunthsCount

Digital Systems CW1: Calculator

$include (reg66x.inc)

; At the very beginning, jump to 40H...
ORG 0
TIMECOUNT EQU 8h
MBUFFER EQU 10h
KBUFFERA EQU 18h
KBUFFERB EQU 20h
KEY EQU 28h
MODE EQU 30h

DISPLAYA EQU 38H
DISPLAYB EQU 40H
DISPLAYC EQU 48H
DISPLAYD EQU 50H

DISPLAYR EQU 58H
CLEARCALLED EQU 60H
LJMP CLEAR

; ...which is where our code begins.
ORG 40H

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
MAIN:             ; Defines the MAIN loop.
ACALL INPUT       ; INPUT subroutine.
ACALL CALCULATE   ; CALCULATE subroutine.
ACALL OUTPUT      ; OUTPUT subroutine.
SJMP MAIN         ; Jump back to the beginning.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;; Subroutine: CALCULATE
;;;;; Purpose: Prepares calculations for output.
;;;;; MODE: 1 = ADD, 2 = SUB, 3 = MULT
;;;;;       4 = DIV, 9 = RESULT
CALCULATE:
MOV A, MODE
CJNE A, #0, STOREMODE
SJMP NONEOFTHEABOVE

STOREMODE:
CJNE A, #9, DOSTOREMODE
SJMP RESULT
DOSTOREMODE:
MOV MBUFFER, MODE
LJMP CALCULATEDONE

RESULT:
MOV A, MBUFFER
ADDITION:
CJNE A, #1, SUBTRACT
MOV A, KBUFFERA
ADD A, KBUFFERB
MOV R0, A
MOV B, #10
DIV AB
CJNE A, #0, ADDTWODIGITS
MOV DISPLAYA, R0
SJMP CLEANUP
ADDTWODIGITS:
MOV DISPLAYB, A
MOV DISPLAYA, B
SJMP CLEANUP

SUBTRACT:
CJNE A, #2, MULTIPLY
MOV A, KBUFFERA
SUBB A, KBUFFERB
MOV DISPLAYA, A
SJMP CLEANUP

MULTIPLY:
CJNE A, #3, DIVIDE
MOV A, KBUFFERA
MOV B, KBUFFERB
MUL AB
MOV R0, A
MOV B, #10
DIV AB
CJNE A, #0, MULTWODIGITS
MOV DISPLAYA, R0
SJMP CLEANUP
MULTWODIGITS:
MOV DISPLAYB, A
MOV DISPLAYA, B
SJMP CLEANUP

DIVIDE:
CJNE A, #4, CALCULATEDONE
MOV A, KBUFFERA
MOV B, KBUFFERB
DIV AB
MOV DISPLAYA, B
MOV DISPLAYR, #1
MOV DISPLAYC, A
SJMP CLEANUP

NONEOFTHEABOVE:
MOV A, MBUFFER
CJNE A, #0, STOREB
STOREA:
MOV KBUFFERA, KEY
SJMP STOREDONE
STOREB:
MOV KBUFFERB, KEY
STOREDONE:
MOV A, KEY
CJNE A, #0, DISPLAYKEY
SJMP CALCULATEDONE
DISPLAYKEY:
MOV DISPLAYA, KEY
SJMP CALCULATEDONE

CLEANUP:
MOV KBUFFERA, #0
MOV KBUFFERB, #0
MOV MBUFFER, #0
MOV KEY, #0
MOV MODE, #0
MOV A, #0
MOV B, #0
CALCULATEDONE:
RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;; Subroutine: OUTPUT
;;;;; Purpose: Updates the display.
OUTPUT:
MOV P2, #3
MOV P1, DISPLAYD
ACALL TIMER

MOV P2, #2
MOV P1, DISPLAYC
ACALL TIMER

MOV P2, #1
MOV A, DISPLAYR
CJNE A, #1, DISPLAYBNOW
MOV P1, #0FFh
CPL P2.4
CPL P2.5
SJMP DISPLAYANOW
DISPLAYBNOW:
MOV P1, DISPLAYB

DISPLAYANOW:
ACALL TIMER
MOV P2, #0
MOV P1, DISPLAYA
RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;; Subroutine: INPUT
;;;;; Purpose: Read buttons pressed on keypad.
INPUT:
MOV MODE, #0
MOV P0, #0FFH
CLR P0.4
ACALL TIMER
MOV A,P0
ANL A, #0FH
CJNE A, #0FH, ROWONECOLONE

SETB P0.4
CLR P0.5
ACALL TIMER
MOV A, P0
ANL A, #0FH
CJNE A, #0FH, ROWTWOCOLONE

SETB P0.5
CLR P0.6
ACALL TIMER
MOV A, P0
ANL A, #0FH
CJNE A, #0FH, ROWTHREECOLONE

SETB P0.6
CLR P0.7
ACALL TIMER
MOV A, P0
ANL A, #0FH
CJNE A, #0FH, ROWFOURCOLONE
RET

ROWONECOLONE:
CJNE A, #14, ROWONECOLTWO
MOV KEY, #1h
RET
ROWONECOLTWO:
CJNE A, #13, ROWONECOLTHREE
MOV KEY, #2h
RET
ROWONECOLTHREE:
CJNE A, #11, ROWONECOLFOUR
MOV KEY, #3h
RET
ROWONECOLFOUR:
MOV MODE, #1 ; mode 1 = ADD
RET

ROWTWOCOLONE:
CJNE A, #14, ROWTWOCOLTWO
MOV KEY, #4h
RET
ROWTWOCOLTWO:
CJNE A, #13, ROWTWOCOLTHREE
MOV KEY, #5h
RET
ROWTWOCOLTHREE:
CJNE A, #11, ROWTWOCOLFOUR
MOV KEY, #6h
RET
ROWTWOCOLFOUR:
MOV MODE, #2 ; mode 1 = SUB
RET

ROWTHREECOLONE:
CJNE A, #14, ROWTHREECOLTWO
MOV KEY, #7h
RET
ROWTHREECOLTWO:
CJNE A, #13, ROWTHREECOLTHREE
MOV KEY, #8h
RET
ROWTHREECOLTHREE:
CJNE A, #11, ROWTHREECOLFOUR
MOV KEY, #9h
RET
ROWTHREECOLFOUR:
MOV MODE, #3 ; mode 1 = MULT
RET

ROWFOURCOLONE:
CJNE A, #14, ROWFOURCOLTWO
MOV CLEARCALLED, #1
ACALL CLEAR
RET
ROWFOURCOLTWO:
CJNE A, #13, ROWFOURCOLTHREE
MOV KEY, #0h
RET
ROWFOURCOLTHREE:
CJNE A, #11, ROWFOURCOLFOUR
MOV MODE, #9 ; mode 9 = EQUALS
RET
ROWFOURCOLFOUR:
CJNE A, #7, NOKEYSCAN
MOV MODE, #4 ; mode 4 = DIVIDE
RET

NOKEYSCAN:
RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;; Subroutine: CLEAR
;;;;; Purpose: Reset the shit out of it.
CLEAR:
MOV KBUFFERA, #0
MOV KBUFFERB, #0
MOV MBUFFER, #0
MOV DISPLAYA, #0
MOV DISPLAYB, #0
MOV DISPLAYC, #0
MOV DISPLAYD, #0
MOV DISPLAYR, #0
MOV KEY, #0
MOV MODE, #0
MOV A, #0
MOV B, #0
MOV A, CLEARCALLED
CJNE A, #0, CLEARRET
LJMP MAIN
CLEARRET:
MOV A, #0
MOV CLEARCALLED, #0
RET

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;; Subroutine: TIMER
;;;;; Purpose: Produce a 58ms-ish delay on ACALL.
TIMER:
MOV R0, #255
TIMELOOPA:
DJNZ R0, TIMELOOPA
TIMEDONE:
RET

END

Intro To Computing CW1: 20 Questions

User Guide

Contents

  1. Installation, compilation and execution
  2. Main menu
  3. Gameplay
  4. About the database file

Installation, compilation and execution

Before playing the game, it must be installed and compiled. There are a few requirements to do this:

  • UNIX-like environment, including:
    • Linux
    • BSD
    • Mac OS X
    • Cygwin on Windows
  • GNU C Compiler (gcc)
  • Various standard C header files:
    • stdio.h
    • stdlib.h
    • string.h
    • termios.h
    • regex.h

Once all these requirements are met, download the myguess.c and animals.db files from below this userguide, and save them into the same directory. Then you need to use ’gcc’ to build the source file into an executable from within that directory:
gcc -o ./myguess ./myguess.c

Once the executable is built, executing it is as simple as typing ./myguess and pressing return – after that it should launch and you can go ahead and move on to the next section of this guide.

Main menu

Main menu

From this menu, you have three options. Simply press the coloured letter to select that option. In this case, pressing ’n' will start a new game, pressing ’l' will re-list all the animals the game knows, and pressing ’q' will quit the game. It doesn’t mater if the letter you press is uppercase or lowercase when you select it – the game is case-insensitive.

It’s also worth noting that all menus in this game work the same way. When you are presented with a selection, your options will have a bolded letter and pressing that letter on the keyboard will select that option.

Gameplay

When you begin a new game by selecting the new game option from the main menu, you need to think of one of the animals the game knows. The game will then ask you questions in an attempt to determine which animal you have selected in your mind:
Gameplay: Questions
Answer the questions, and when it has reached a conclusion, it will guess the animal you have thought of:
Gameplay: Guessing
If it fails to reach a conclusion, then you have tricked the game (or cheated!) and the game will admit defeat:
Gameplay: Defeated

About the database file

The animals.db file contains a list of animals and their attributes. The format of this file is controlled by several parameters in the source of myguess.c, but the default format is quite simple. There is one animal per line. The line begins with the name of the animal, followed by it’s attributes (space-separated). There is a single colon (:) separating the animal’s name from it’s attributes. This results in the database file shown towards the bottom of this page.

This file allows the game to be expanded to additional animals without needing to be recompiled. Only certain attributes are recognised by the game at this time. These include:

  • swimming
  • flying
  • underground
  • feline
  • arachnid
  • insect
  • canine
  • jungle
  • farm
  • desert
  • herbivore
  • domesticated
  • eggs
  • camouflage
  • milk

Source Code: myguess.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <regex.h>

/* Animal database setup */

// In the DB file, this separates the animal from it's attributes.
#define S_DBFILE_KEY_DELIM ":"
// This separates each attribute from the others.
#define S_DBFILE_FLAG_DELIM " "
// This separates each entry containing attributes and animals.
#define S_DBFILE_ENTRY_DELIM "\n"
/*
Under default settings, the DB file would look like:
animal1:attribute1 attribute2
animal2:attribute2 attribute3
[..etc..]
*/

// The path to the DB file itself.
#define P_DBFILE "./animals.db"
// You can download the default file from:
//     http://kevinsnet.com/subsites/uni/IntroComputing/animals.db

// The number of entries in the DB file. The default file has 30.
#define I_TOTAL_ANIMALS 30;

/* Game setup */

// The name of the game.
#define S_NAME "20 Questions"
// The version of the game.
#define S_VERSION "0.1c"
// Enable debug mode?
#define I_DEBUG 0
/* Debug mode, when set to 1, outputs extra information about
 * the data loaded from the database at startup and the process
 * of deducting the impossible answers during gameplay. Enable it
 * to see this information.
 /*

/* Output setup */

// Prefix status output with this:
#define S_STATUS "-\e[97;1m:\e[0m- "
// Prefix informational output with this:
#define S_INFO "-\e[94;1mi\e[0m- "
// Prefix error output with this:
#define S_ERROR "-\e[91;1m!\e[0m- "
// Prefix requests for input from the user with this:
#define S_QUESTION "-\e[93;1m?\e[0m- "
// Prefix win-notices with this:
#define S_WINNER "-\e[92;1m$\e[0m- "
// Prefix lose-notices with this:
#define S_LOSER "-\e[93;1m|\e[0m- " 

// ANSI code to clear the screen:
#define A_CLEAR "\e[2J"
// ANSI code for bold text:
#define A_BOLD "\e[93;1m"
/* Note: ANSI bolded text doesn't look right on all the systems
 * tested on, so instead I'm colouring it yellow. It seems to work
 * better than bolding it on some terminals, plus, it looks nice.
 */
// ANSI code to reset output to normal:
#define A_NORM "\e[0m"

/* End of configuration */

// Declare the functions we're gonna make later.
int unbufGetc();
void startGame();
void quitGame();
void listAnimals();
void doMainMenu();
int getUserAnswer();
void endGame();

// The following variables are defined in a global context,
// because these won't always pass between function correctly.
// Their data is actually defined in the configuration section above.
int totalAnimals = I_TOTAL_ANIMALS;
int debugMode = I_DEBUG;

// The main subroutine.
int main( void ){
	// Variable declaration. These are all variables we're gonna use in main().
	FILE *dbHandle;
	char *stringBuffer;
	char fileBuffer[128];
	char strAnimalList[1024] = "";
	char tmpBuffer[128] = "";
	char dbAnimals[totalAnimals][128];
	char dbFlags[totalAnimals][128];
	int lineCount = 0;
	int cycleCount = 0;
	int phaseCount = 0;
	int loopCount = 0;
	int tmpInt = 0;
	fpos_t dbPosition;

	printf("%s%s%s v%s starting up...\n", A_CLEAR, S_STATUS, S_NAME, S_VERSION);

	// Open the DB file for reading, or prompt the user to download it if it can't be read.
	if((dbHandle = fopen(P_DBFILE, "r")) == NULL){
		printf("%sCannot open the DB file. Enter the following URL in a web browser,\n", S_ERROR);
		printf("%sand save the resulting file into the directory you're currently in:\n", S_ERROR);
		printf("%shttp://kevinsnet.com/subsites/uni/IntroComputing/animals.db\n", S_ERROR);
		// We output our error, so exit with an error code.
		exit(1);
	}

	// Set &dbPosition to the beginning of the file, so that we can read some data from it, and then
	// set it back to this position to re-read some of it.
	fgetpos(dbHandle, &dbPosition);	

	// While we're not at the end of the DB file,
	while(!feof(dbHandle)){
		if(fgets(fileBuffer, 126, dbHandle)){
			// increase the linecount by one if it's not null.
			lineCount++;
		}
	}

	// And now we put ourself back at the beginning of the DB file.
	fsetpos(dbHandle, &dbPosition);

	// Set some counters to zero.
	cycleCount = 0;
	phaseCount = 0;

	// While we're not at the end of the DB file,
	while(!feof(dbHandle)){
		// read some of the data into fileBuffer
		if(fgets(fileBuffer, 126, dbHandle)){
			// then use strtok to split it by the entry delimiter defined above
			stringBuffer = strtok(fileBuffer, S_DBFILE_KEY_DELIM);
			// and if the result's not empty,
			while(stringBuffer != NULL){
				// and the phase counter is zero
				if(phaseCount == 0){
					// then we have an animal name. store it in the array.
					strcpy(dbAnimals[cycleCount], stringBuffer);
					// and toggle the phase for the next strtok operation.
					phaseCount = 1;
				// but if the phase count isn't zero
				}else{
					// then we have attribute flags. store it in the flags array.
					strcpy(dbFlags[cycleCount], stringBuffer);
					// and toggle the phase for the next strtok operation.
					phaseCount = 0;
				}
				// repeat strtok til we've read in all the entries in the DB.
				stringBuffer = strtok(NULL, S_DBFILE_ENTRY_DELIM);
			}
		// and increase the cycleCount.
		cycleCount++;
		}
	}
	// when we're done, we close the DB file.
	fclose(dbHandle);

	// more counters go back to zero.
	tmpInt = 0;
	phaseCount = 0;
	loopCount = 0;

	// while the loop counter is less than the number of lines in the file,
	while(loopCount < lineCount){ 		// print an animal and it's attributes, but only if debug mode is on. 		printf(debugMode>0?"%sAnimal '%s' with attributes '%s' loaded.\n":"",
			S_STATUS, dbAnimals[loopCount], dbFlags[loopCount]);
		// if this isn't the first iteration of the loop,
		if(loopCount > 0){
			// and the phase counter is more than one
		 	if(phaseCount > 1){
		 		// enter a newline and a tab into a temporary buffer.
				strcpy(tmpBuffer, "\n\t");
				// then set the phase counter back to zero.
				phaseCount = 0;
			// but if the phase counter isn't more than one,
			}else{
				// check the temporary integer (which should contain a strlen)
				// if it's more than seven,
				if(tmpInt > 7){
					// enter a single tab in the temporary buffer.
					strcpy(tmpBuffer, "\t");
				// if it's less than seven
				}else{
					// enter two tabs in the temporary buffer.
					strcpy(tmpBuffer, "\t\t");
				}
				// then increase the phase counter by one.
				phaseCount++;
			}
		// otherwise, if it is the first iteration of the loop,
		}else{
			// enter a single tab in the temporary buffer.
			strcpy(tmpBuffer, "\t");
		}
		// set the temporary integer to the length of the name of the current iteration numbered animal
		tmpInt = strlen(dbAnimals[loopCount]);
		// and add that animal's name to the temporary buffer.
		strcat(tmpBuffer, dbAnimals[loopCount]);
		// then put the contents of the temporary buffer into the animal list
		strcat(strAnimalList, tmpBuffer);
		// and increase the loop count by one.
		loopCount++;
	}
	/* This whole while loop results in the animal list
	 * string containing a nice, evenly formatted,
	 * three-column list of all the animals in the DB file.
	 * the strlen is used to calculate the amount of space
	 * needed between animal names to space the columns easily.
	 */

	if(cycleCount < 1 || strlen(dbAnimals[0]) == 0 || strlen(dbFlags[0]) == 0){
		printf("%sThe DB file is corrupt. Enter the following URL in a web browser,\n", S_ERROR);
		printf("%sand save the resulting file into the directory you're currently in:\n", S_ERROR);
		printf("%shttp://kevinsnet.com/subsites/uni/IntroComputing/animals.db\n", S_ERROR);
		exit(1);
	}

	// Display the number of animals loaded.
	printf("%s%u of %u animals loaded from the database.\n", S_STATUS, cycleCount, lineCount);
	// And call the function to output the nicely formatted list.
	listAnimals(strAnimalList);
	// Then just print out some help information.
	printf("\n%sTIP: At any menu, press the bolded key to select that option.\n", S_INFO);

	// And show them the main menu, passing it some useful information needed to generate the menu.
	doMainMenu(lineCount, dbAnimals, dbFlags, strAnimalList);	

	return 0;
}

// This function generates the main menu. It should never return, because it should always be there somewhere.
// It takes an integer containing the number of DB entries, an array of animals, an array of flags, and
// the stringed animal list as parameters.
void doMainMenu(int lineCount, char dbAnimals[totalAnimals][128], char dbFlags[totalAnimals][128], char strAnimalList[1024]) {
	// Give us an empty integer please.
	int keypress = 0;

	// Infinite loop. Really, if this exists, then maths as *I* know it has come to an end.
	while(1 == 1){
		// Print the menu, nicely formatted please.
		printf("\n%s[GAME MENU] %sN%sew game, %sQ%suit, %sL%sist known animals.\n",
			S_QUESTION, A_BOLD, A_NORM, A_BOLD, A_NORM, A_BOLD, A_NORM);

		// Get the user's selection from the standard input using unbufGetc().
		// unbufGetc is a custom function for getting input.
		keypress = unbufGetc(stdin);	

		// If the keypress is...
		switch(keypress) {
			// .. lowercase n
			case 78:
				// start the game, pass it some useful parameters.
				startGame(lineCount, dbAnimals, dbFlags);
				break;
			// .. uppercase n
			case 110:
				// start the game, pass it some useful parameters.
				startGame(lineCount, dbAnimals, dbFlags);
				break;
			// .. lowercase q
			case 81:
				// go to where we exit the application.
				quitGame();
				break;
			// .. uppercase q
			case 113:
				// go to where we exit the application.
				quitGame();
				break;
			// .. lowercase l
			case 76:
				// list the animals, pass it our animal list string
				listAnimals(strAnimalList);
				break;
			// .. uppercase l
			case 108:
				// list the animals, pass it our animal list string
				listAnimals(strAnimalList);
				break;
		}
	}
}

// This function quits the game with a successful exit code.
// It returns nothing and requires no parameters -- THERE IS NO RETURN.
void quitGame() {
	// Let the user know that they wanted to exit.
	// We'll try to make them feel bad about leaving, but it's really too late.
	printf("\n%sUser is exiting... Please don't leave ;_;\n", S_STATUS);
	// Exit, successfully, since there were no errors.
	exit(0);
}

// This function actually runs the game. It takes a couple parameters -- the number of files in the DB
// and the arrays of animals and flags. It returns nothing, as when it returns, the game ended somehow
// (either the user ended it or someone won/lost) and it returns to the main menu.
void startGame(int lineCount, char dbAnimals[totalAnimals][128], char dbFlags[totalAnimals][128]) {
	// Setup our variables.
	// For the most part, these are integers or empty strings/arrays we need.
	int userResponse = 0;
	// stateInt is special. We start this at 11, see below for the reasoning.
	int stateInt = 11;
	int oldInt = 0;
	int loopCount = 0;
	int possibleCount = 0;
	int tmpCount = 0;
	int questionCount = 0;
	// This is a multidimensional array. Each state will have it's own entry of
	// totalAnimals entries in the array. See below for information about the states.
	char possibleAnimals[99][totalAnimals][128];
	char possibleFlags[99][totalAnimals][128];
	// Also declare regex and regmatch pointers. These are used for the regexing.
	// .. I'll explain a below.
	regex_t *regex;
	regmatch_t *matches;

	/* Information:
	 * A lot happens below. For a start, there's going to be a series of while loops based
	 * around an integer, stateInt. Originally this was just a way to control the asking of
	 * questions, to allow us to just loop through a question until we got some kind of valid
	 * input, and the integers were just gonna be sequential from 0 up.

	 * In the end, it was decided that the state integer could also be used to prevent
	 * contradicting questions from being asked, so, questions in similar "groups" will have
	 * a similar digits in the tens-place (ie, transportation method - swimming/flying/underground
	 * all have a 1 in the tens-place) and then the question number is in the ones place.
	 * This makes it easier to keep track of.

	 * Since, as a result of this state numbering system, the tens place can go up to 9, the
	 * animals and flags arrays declared above require 99 arrays rather than the just-under-20
	 * that would otherwise be required.

	 * In addition, I decided to switch from using strtok() to parse up the parameters to
	 * using regexes. strtok() requires a lot of repetition and there is simply nothing better
	 * than a good cup of regexes when you're sifting through strings.
	 */

	// While the state is 11,
	while(stateInt == 11){
		// get the user's response to the question using a custom function, getUserAnswer().
		userResponse = getUserAnswer("Can your animal swim?");

		// If their answer was ...
		switch(userResponse){
			// ... yes,
			case 1:
				// zero some counters,
				possibleCount = 0;
				loopCount = 0;
				// then loop through the lines in the db, and for each one,
				while(loopCount < lineCount){ 					// compile a simple regex: /swimming/ 					regcomp(regex, "swimming", 0); 					// if the regex matches 					if(regexec(regex, dbFlags[loopCount], 0, matches, 0) == 0){ 						// and the entry is blank, 						if(strlen(dbAnimals[loopCount]) == 0){ 							// skip this cycle 							break; 						}; 						// but if it matches and it's not blank, print out some debug info if we want, 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, dbAnimals[loopCount]);
						// and then copy the animal and it's flags into the new arrays.
						strcpy(possibleAnimals[stateInt][possibleCount], dbAnimals[loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], dbFlags[loopCount]);
						possibleCount++;
					}
					// and the loop counter goes up one.
					loopCount++;
				}

				// if there are no animals that match at all,
				if(possibleCount == 0){
					// we lose
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					// print some stats
					endGame(questionCount);
					// and go back to the main menu.
					return;
				}
				// if there's one possible match,
				if(possibleCount == 1){
					// we won!
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					// print some stats
					endGame(questionCount);
					// and go back to the main menu.
					return;
				}
				// set oldInt to our current state integer,
				oldInt = stateInt;
				// increase our question count, and then set the state integer to the next state.
				questionCount++;
				stateInt = 21;
				break;
			// ... no,
			case 2:
				// zero some counters
				possibleCount = 0;
				loopCount = 0;
				// loop through each DB entry,
				while(loopCount < lineCount){ 					// compile a simple regex: /swimming/ 					regcomp(regex, "swimming", 0); 					// and then if it DOESN'T match, 					if(regexec(regex, dbFlags[loopCount], 0, matches, 0) != 0){ 						// and it's blank, 						if(strlen(dbAnimals[loopCount]) == 0){ 							// skip this one. 							break; 						}; 						// but if it's not blank, spit out some debug info, 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, dbAnimals[loopCount]);
						// and then copy it to the new arrays.
						strcpy(possibleAnimals[stateInt][possibleCount], dbAnimals[loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], dbFlags[loopCount]);
						possibleCount++;
					}
					// loop counter goes up.
					loopCount++;
				}
				// we lose again...
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				// we win again...
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				// oldInt is set to our state.
				oldInt = stateInt;
				// question counter goes up.
				questionCount++;
				// change state.
				stateInt = 12;
				break;
			// ... quit,
			case 3:
				// then quit back to the main menu
				return;
				break;
		}
	}

	// We're going to repeat this for each state, using new flags, and we're going to regex it
	// against the results in the arrays from the last state, rather than the array with the animals
	// from the DB. This results in the arrays getting smaller and smaller, resulting in a win or a
	// lose through the process of elimination.
	while(stateInt == 12){
		userResponse = getUserAnswer("Can your animal fly?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "flying", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 21;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "flying", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 13;
				break;
			case 3:
				return;
				break;
		}
	}

	// And again.
	while(stateInt == 13){
		userResponse = getUserAnswer("Does your animal live underground?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "underground", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 21;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "underground", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 21;
				break;
			case 3:
				return;
				break;
		}
	}

	// .... aaaand again.
	while(stateInt == 21){
		userResponse = getUserAnswer("Is your animal a feline?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "feline", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){
							break;
						};
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 31;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "feline", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 22;
				break;
			case 3:
				return;
				break;
		}
	}

	// ... Guess what we're doing again!?
	while(stateInt == 22){
		userResponse = getUserAnswer("Is your animal an arachnid?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "arachnid", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){
							break;
						};
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 31;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "arachnid", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 23;
				break;
			case 3:
				return;
				break;
		}
	}

	// Third time's a charm, but hey, we're well past our third run.
	while(stateInt == 23){
		userResponse = getUserAnswer("Is your animal an insect?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "insect", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){
							break;
						};
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 31;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "insect", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 24;
				break;
			case 3:
				return;
				break;
		}
	}

	// Again and again, and again and again.
	while(stateInt == 24){
		userResponse = getUserAnswer("Is your animal a canine?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "canine", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){
							break;
						};
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 31;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "canine", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 31;
				break;
			case 3:
				return;
				break;
		}
	}

	// Do it again, do it again! -- these are song lyrics, just so you know.
	while(stateInt == 31){
		userResponse = getUserAnswer("Can your animal be found in a jungle?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "jungle", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){
							break;
						};
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 91;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "jungle", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 32;
				break;
			case 3:
				return;
				break;
		}
	}

	// It's called "Again and again".
	while(stateInt == 32){
		userResponse = getUserAnswer("Can your animal be found on a farm?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "farm", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){
							break;
						};
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 91;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "farm", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 33;
				break;
			case 3:
				return;
				break;
		}
	}

	// "Again and again" which is by The Bird and the Bee.
	while(stateInt == 33){
		userResponse = getUserAnswer("Can your animal be found in the desert?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "desert", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){
							break;
						};
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 91;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "desert", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 91;
				break;
			case 3:
				return;
				break;
		}
	}

	// The song was used in a popular Mac music video. Oddly enough, it's called "Again and again"
	while(stateInt == 91){
		userResponse = getUserAnswer("Is your animal a herbivore?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "herbivore", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){
							break;
						};
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 92;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "herbivore", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 92;
				break;
			case 3:
				return;
				break;
		}
	}

	// Look it up on YouTube -- search for "Again and again".
	while(stateInt == 92){
		userResponse = getUserAnswer("Can your animal be domesticated?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "domesticated", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){
							break;
						};
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 93;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "domesticated", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 93;
				break;
			case 3:
				return;
				break;
		}
	}

	// In fact, I've done it for you -- "Again and again": http://uk.youtube.com/watch?v=6kxDxLAjkO8
	while(stateInt == 93){
		userResponse = getUserAnswer("Does your animal lay eggs?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "eggs", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){
							break;
						};
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 94;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "eggs", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 94;
				break;
			case 3:
				return;
				break;
		}
	}

	// You can also buy the song on iTunes -- it's called "Again and again"
	while(stateInt == 94){
		userResponse = getUserAnswer("Can your animal camouflage itself?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "camouflage", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){
							break;
						};
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 95;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "camouflage", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 95;
				break;
			case 3:
				return;
				break;
		}
	}

	// This is the last one. That's good, cause I'm running out of witty commentary.
	while(stateInt == 95){
		userResponse = getUserAnswer("Can you get milk from your animal?");

		switch(userResponse){
			case 1:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "milk", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) == 0){ 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){
							break;
						};
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 100;
				break;
			case 2:
				possibleCount = 0;
				loopCount = 0;
				while(loopCount < lineCount){ 					regcomp(regex, "milk", 0); 					if(regexec(regex, possibleFlags[oldInt][loopCount], 0, matches, 0) != 0){ 						if(strlen(possibleAnimals[oldInt][loopCount]) == 0){ 							break; 						}; 						printf(debugMode>0?"%sIt could be '%s'.\n":"", S_INFO, possibleAnimals[oldInt][loopCount]);
						strcpy(possibleAnimals[stateInt][possibleCount], possibleAnimals[oldInt][loopCount]);
						strcpy(possibleFlags[stateInt][possibleCount], possibleFlags[oldInt][loopCount]);
						possibleCount++;
					}
					loopCount++;
				}
				if(possibleCount == 0){
					printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
					endGame(questionCount);
					return;
				}
				if(possibleCount == 1){
					printf("%sYour animal is: %s!\n", S_WINNER, possibleAnimals[stateInt][0]);
					endGame(questionCount);
					return;
				}
				oldInt = stateInt;
				questionCount++;
				stateInt = 100;
				break;
			case 3:
				return;
				break;
		}
	}	

	// Using the default animals.db, there should be no way to get this far. But if you did, we admit defeat.
	printf("%sNope, I can't guess your animal. You outsmarted me...this time!\n", S_LOSER);
	endGame(questionCount);
	// And then we go back to the main menu so you can defeat us again. Cheater.
	return;

}

// This function generates and generates some statistics about the game played.
// It takes the question count as a paramter and returns nothing.
void endGame(int questionCount) {
	printf("%sYou answered %u questions.\n", S_INFO, (questionCount + 1));
	return;
}

// And this function takes our nicely formatted list of animals we made when the game
// first started as a parameter, and prints the list of animals. Then it returns nothing.
void listAnimals(char strAnimalList[1024]) {
	printf("%sI know the following animals:\n%s\n", S_INFO, strAnimalList);
	return;
}

// This displays the question (passed as parameter) we're asking the user, and their choices.
// It returns 1 if they selected YES, 2 if they selected NO, 3 if they selected QUIT, or
// 0 if the button they pressed didn't match any of the available selections.
int getUserAnswer(char *userQuestion) {
	// Output the question & choices.
	printf("%s[MENU] %s %sY%ses/%sN%so/%sQ%suit to menu\n",
		S_QUESTION, userQuestion, A_BOLD, A_NORM, A_BOLD, A_NORM, A_BOLD, A_NORM); 

	// Set the integer keypress to the number identifying the button the user pressed.
	// Call our own unbufGetc to do this, and get it from the standard input.
	int keypress = unbufGetc(stdin);

	// If the user pressed...
	switch(keypress){
			// .. lowercase y
			case 89:
				// we give a 1.
				return 1;
				break;
			// .. capital y
			case 121:
				// we give a 1.
				return 1;
				break;
			// .. lowercase n
			case 78:
				// we give a 2.
				return 2;
				break;
			// .. capital n
			case 110:
				// we give a 2.
				return 2;
				break;
			// .. lowercase q
			case 81:
				// and a 3...
				return 3;
				break;
			// .. capital q
			case 113:
				// and a 3...
			 	return 3;
			 	break;
			// or, if the user can't follow directions...
			default:
				// they get NOTHING.
				return 0;
				break;
	}

	// if we get this far, switch() broke, so we'll give you nothing.
	return 0;
}

// and the function you heard so much about before, unbufGetc.
// this takes a file point as a parameter (it'll usually be stdin)
// and returns a keypress code. it's a little more complicated though.
int unbufGetc(FILE *getcHandle) {
	// this function is why I included termios.h...
	// make two structs to hold terminal data in.
	struct termios termIOold;
	struct termios termIOnew;

	// store the current terminal atrributes in one of those structs.
	tcgetattr(0, &termIOold);

	// and make the other struct match the first.
	termIOnew = termIOold;
	// now we're going to change some of the terminals parameters so that
	// when the user presses a button, it is directly passed to us without
	// waiting for them to press return/enter to pass it to us.
	// (this is what the unbuf means, it's an unbuffered input.)
	termIOnew.c_lflag &= ~(ICANON|ECHO);
	tcsetattr(0, TCSANOW, &termIOnew);

	// get the keypress! it'll only be a single one since we're not buffering
	// keypresses at this point.
	int keypress = getc(getcHandle);	

	// put the terminal back to normal.
	tcsetattr(0, TCSANOW, &termIOold);

	/* we could just leave the terminal in an unbuffered state for the duration of
	 * the execution and set it back to normal on exit, but if we do that then we
	 * risk the user having a cocked up terminal if they stop execution uncleanly
	 * (for example by pressing ^C or killing our PID)... so changing it back and
	 * forth several dozen times is a slightly more friendly way to do it.
	 */

	// and return the keypress as an integer.
	return keypress;
}

Database file: animals.txt

giraffe:herbivore
trout:jungle herbivore swimming domesticated eggs
tiger:jungle feline
elephant:jungle herbivore
jellyfish:herbivore swimming eggs
eagle:flying eggs
piranha:jungle swimming eggs
hummingbird:herbivore flying eggs
parrot:jungle flying
dolphin:swimming
spider:arachnid eggs
housecat:domesticated feline farm
dog:domesticated canine farm
wolf:canine
squid:swimming camouflage herbivore eggs
iguana:camouflage herbivore desert domesticated eggs
camel:desert milk herbivore
cow:farm milk
rabbit:herbivore desert domesticated
chicken:flying domesticated herbivore eggs
fly:flying insect eggs
squirrel:herbivore
hamster:herbivore domesticated
roadrunner:flying eggs desert
sheep:herbivore farm milk
butterfly:insect flying
badger:underground
ferrets:domesticated
ant:insect eggs
toucan:domesticated flying eggs

tinyurlfs, file2tinyurl & tinyurl2file

IRC Log (or, initial idea conception)

<Ignite> :)
<cmantito> ok, this is gonna sound crazy
<cmantito> really, really crazy
<cmantito> as you might have (or not) seen when it was in the /t, I wrote a set of scripts that could encode files
as URLs to be stored in a tinyurl, and then extracted again
<LeaChim> yes, that was quite cool
<cmantito> now, I'm writing a mountable filesystem, when you copy a file into it, it uploads it to tinyurl, grabs
the tinyurlid, and passes it to a PHP API running on my server and it stores the filename and the tinyurl ID
<LeaChim> why a php script?
<LeaChim> why can't it store this locally?
<cmantito> I'm getting there ;)
<cmantito> and when you read a file, it queries the API running on my server with the filename, gets the urlID, and
allows you to open the file in place
<cmantito> ideally, it's a write-once filesystem, you can put files into it, but not remove them, overwrite them, or
modify them.
<cmantito> the reason for using a remote API for it, is so that it's a unified filesystem
<cmantito> ie, I copy a file into it, and you can copy it right back out again
<cmantito> it's a giant filestore!
<LeaChim> heh
<cmantito> for *everyone*
<LeaChim> quite cool
<cmantito> and
<cmantito> if someone wants to bitch at me for "storing" files of naughty materials
<cmantito> I can use the bittorrent defense
<cmantito> "I only store metadata"
<Ignite> rofl
<LeaChim> heh
<LeaChim> well, you'll be implemented this as fuse right?
<cmantito> yes.
<cmantito> it's half-done
<Ignite> cmantito, so like
<Ignite> cmantito, we can all use it?
<cmantito> yes :D
<Ignite> So we can sort of share warez and shits?
<Ignite> :x
<cmantito> if the links are already in the database, it can read them and list them
<LeaChim> cmantito, for rmdir unlink rename chmod chown truncate you should just return permission denied
<cmantito> LeaChim: exactly! :D
<cmantito> fserr(13)
<cmantito> ENOACCES
<LeaChim> so all you've got left are mkdir symlink link utime, and file creation
<cmantito> well those 4 are unnecessary
<LeaChim> in fact, you might as well not have symlink or link
<LeaChim> no, keep mkdir
<cmantito> exactly
<cmantito> no, it's a flat filesystem
<Ignite> No
<LeaChim> boring
<Ignite> mkdir
<LeaChim> and horrible to list
<Ignite> WE WANT MKDIR
<LeaChim> add mkdir
* Ignite chants
<cmantito> ffs *fine*
<LeaChim> besides, mkdir is just php side
<Ignite> Rofl
<cmantito> now it's gonne take even longer.
<Ignite> xD
<cmantito> I *was* trying to keep it relatively simple
<cmantito> since if it takes off, tinyurl will kinda notice
<cmantito> Upload a DVD image "What's that, a 4.93921239x10^9 character URL!?"
<cmantito> actaully more
<cmantito> uuencode pads.
<cmantito> butokfine
<LeaChim> cmantito, you might want to add some code to allow you to split up files
<cmantito> I'll put in the ability to add one level of directories.
<cmantito> ONE XD
<LeaChim> lol
<LeaChim> why not more than one?
<LeaChim> it's just going to get horrible to list directories ;
<LeaChim> and maybe you should add something to expire things, so the directory listing doesn't end up being
gigantic
<cmantito> I'm trying to limit the amount of HTTP queries I have to make for any given requests.
<cmantito> s/(request)s/\1/

Original Scripts

The original file2tinyurl and tinyurl2file scripts were originally published at RackLoad (now defunct) and then at the Xelix wiki, followed by my own CodeWiki to continue development into a FUSE mountable filesystem. They now live here, source available below.

Development Roadmap

It actually works. By using file2tinyurl, and then putting the relevant values into the database manually. the files are accessible. It is not directory-structured yet, and file upload isn’t complete yet either. Please keep adding feature requests, and as soon as a usable version (even without ALL the features) is alpha, I’ll chuck it up here.

Original Proof of Concept Source

These scripts requires the following Perl modules:

  • LWP::UserAgent
  • Convert::UU
  • HTML::Entities
  • URI::Escape

These modules all have dependencies of their own. To easily install the modules and all their dependencies, execute the following command as root and it will prompt you to install the dependencies. Pressing return repeatedly will generally suffice.

cpan install LWP::UserAgent Convert::UU HTML::Entities URI::Escape

file2tinyurl
Purpose
This script will upload a file to TinyURL. Yeah, you heard that right.

Theory
TinyURL takes a given URL, and sticks it, with a pointer, into it’s database. Why does the URL have to be a URL? It could be any text. Well why not binary data? Just encode the binary data as text, like your mail client does every time you send a binary attachment, and upload that. This script does that for you, too. Then you just need a way to get it out again. See the next post.

What this script does

  1. When given a path to a file name, this will:
  2. Read the file
  3. UUencode the data in the file, converting it to “safe” ASCII.
  4. Strip the newlines from that, since UUencoded data is set at a certain width, and replace them with flags.
  5. Escape and non-URL safe text in the %xx form (ie, ‘~’ becomes %7E, ‘ ‘ becomes %20).
  6. POST the data to TinyURL, and strip the TinyURL and TinyURL ID from the results, displaying it to a console.

As of yet, I have not found a length limit on the original URLs that TinyURL will shorten. Therefore, as of yet, I have not found a filesize limit for “uploaded” files.

Source: file2tinyurl.pl

#!/usr/bin/perl

### This script was written by cmantito
### cmantito@cmantito.com
### http://cmantito.com
### This script can be freely modified/distributed/used and is open-source.
### However, I'd appreciate some credit on derivative works.

use LWP::UserAgent;
use Convert::UU qw(uudecode uuencode);
use HTML::Entities;
use URI::Escape;

## If this script runs with strict, you're fucked. So don't add strict.

$filename = shift(@ARGV);

if(!$filename){
   print "Usage: file2tinyurl /path/to/file\n";
   exit(15);
}

$relFn = $filename;
$relFn =~ s/^(.+)\///ig;

if((-e $filename) && (-r $filename)){
   if(-d $filename){
      fatal("Specified file must not be a directory.");
   }
}else{
   fatal("Specified file doesn't exist or isn't readable.");
}

open(TUFILE, "){
   $rawData .= $_;
}
close(TUFILE);

$uuData = uuencode($rawData);
$uuData =~ s/\n/_NWLN_/ig;

$urlData = uri_escape($uuData);

$url = "http://".$relFn."/".$urlData;
$formData{'url'} = $url;
$formUrl = "http://tinyurl.com/create.php";

$browser = LWP::UserAgent->new;
$response = $browser->post($formUrl, Content => \%formData);

if($response->is_success){
   $tinyData = $response->content;
   ($tinyId) = $tinyData =~ /\http:\/\/tinyurl\.com\/(.+?)\/;
   print "File uploaded successfully.\n";
   print "TinyURL ID (used with tinyurl2file for download): ".$tinyId."\n";
   print  "TinyURL (for reference): http://tinyurl.com/".$tinyId."\n";
   exit(0);
}else{
   print $formUrl;
   fatal("Couldn't contact TinyURL: ".$response->status_line);
}

sub fatal {
   $errorString = shift(@_);
   print STDERR "Fatal error: ".$errorString."\n";
   exit(5);
}

tinyurl2file
Purpose
This script will download a file that was uploaded to TinyURL with file2tinyurl.

Theory
TinyURL takes a given URL, and sticks it, with a pointer, into it’s database. Why does the URL have to be a URL? It could be any text. Well why not binary data? Just encode the binary data as text, like your mail client does every time you send a binary attachment, and upload that. Then you just need a way to get it out again. And this is that portion of it. ^_^

What this script does

  1. When given a TinyURL ID (the part of the TinyURL after the tinyurl.com/),
  2. Retrieves the preview page (preview.tinyurl.com/)
  3. Strips the “original URL” portion out of the page
  4. Extracts the original filename from our “URL”
  5. Looks for newline flags, and puts them back where they belong
  6. Gets rid of any HTML entities, replacing them with the correct ASCII (ie, < should be < and & should be &)
  7. Converts the URL-encoded characters back to ASCII (ie, %20 becomes a space, %7E becomes a ‘~’)
  8. Un-UUencodes the ASCII, getting it back to it’s original form (likely binary data)
  9. Writes that back out to a file, using the original file’s filename which was encoded into it with file2tinyurl.

Source: tinyurl2file.pl

#!/usr/bin/perl

### This script was written by cmantito
### cmantito@cmantito.com
### http://cmantito.com
### This script can be freely modified/distributed/used and is open-source.
### However, I'd appreciate some credit on derivative works.

use LWP::UserAgent;
use Convert::UU qw(uudecode uuencode);
use HTML::Entities;
use URI::Escape;

## If this script runs with strict, you're fucked. So don't add strict.

$urlId = shift(@ARGV);

if(!$urlId){
   print "Usage: tinyurl2file [TinyURL ID from file2tinyurl]\n";
   exit(15);
}

$tinyUrl = "http://preview.tinyurl.com/".$urlId;

$browser = LWP::UserAgent->new;
$response = $browser->get($tinyUrl);

if($response->is_success){
   $tinyData = $response->content;
}else{
   fatal("Couldn't contact TinyURL: ".$response->status_line);
}

($uuUrl) = $tinyData =~ /\
\(.+?)\
\<\/b\>\<\/blockquote\>/; $uuUrl =~ s/\
//ig; ($filename, $uuData) = $uuUrl =~ /http:\/\/(.+?)\/(.+)$/; $uuData =~ s/_NWLN_/\n/ig; $uuData = decode_entities($uuData); $rawData = uri_unescape($uuData); $rawData = uudecode($rawData); open(TUFILE, ">".$filename) or fatal($!); print TUFILE $rawData; close(TUFILE); print "File downloaded to ./".$filename."\n"; sub fatal { $errorString = shift(@_); print STDERR "Fatal error: ".$errorString."\n"; exit(5); }
Pages:12»